From 54168af790a40b593dae927880bedd1eee9696ae Mon Sep 17 00:00:00 2001 From: qmzhang <578967933@qq.com> Date: Tue, 3 Jul 2018 17:25:08 +0800 Subject: [PATCH 001/360] add create database support --- .../inbound/mysql/ddl/DruidDdlParser.java | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java index eda66921..243a9c74 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java @@ -9,26 +9,7 @@ import com.alibaba.fastsql.sql.ast.SQLExpr; import com.alibaba.fastsql.sql.ast.SQLStatement; import com.alibaba.fastsql.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.fastsql.sql.ast.expr.SQLPropertyExpr; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableAddConstraint; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableAddIndex; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableDropConstraint; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableDropIndex; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableDropKey; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableItem; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableRename; -import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLConstraint; -import com.alibaba.fastsql.sql.ast.statement.SQLCreateIndexStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLCreateTableStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLDeleteStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLDropIndexStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLDropTableStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource; -import com.alibaba.fastsql.sql.ast.statement.SQLInsertStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLTableSource; -import com.alibaba.fastsql.sql.ast.statement.SQLTruncateStatement; -import com.alibaba.fastsql.sql.ast.statement.SQLUnique; -import com.alibaba.fastsql.sql.ast.statement.SQLUpdateStatement; +import com.alibaba.fastsql.sql.ast.statement.*; import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement; import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item; import com.alibaba.fastsql.sql.parser.ParserException; @@ -157,6 +138,13 @@ public class DruidDdlParser { processName(ddlResult, schmeaName, delete.getTableName(), false); ddlResult.setType(EventType.DELETE); ddlResults.add(ddlResult); + } else if (statement instanceof SQLCreateDatabaseStatement){ + // add create database support + DdlResult ddlResult = new DdlResult(); + SQLCreateDatabaseStatement createDatabase= (SQLCreateDatabaseStatement) statement; + ddlResult.setType(EventType.QUERY); + processName(ddlResult,schmeaName,null,false); + ddlResults.add(ddlResult); } } From bb8fec963b8feff9a1737a80eaa9140a951dd89d Mon Sep 17 00:00:00 2001 From: qmzhang <578967933@qq.com> Date: Thu, 5 Jul 2018 16:57:36 +0800 Subject: [PATCH 002/360] add create/drop databases event support --- .../parse/inbound/mysql/ddl/DruidDdlParser.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java index 243a9c74..efb3870a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java @@ -15,6 +15,7 @@ import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlRenameTableState import com.alibaba.fastsql.sql.parser.ParserException; import com.alibaba.fastsql.util.JdbcConstants; import com.alibaba.otter.canal.protocol.CanalEntry.EventType; +import org.apache.commons.lang.StringUtils; /** * @author agapple 2017年7月27日 下午4:05:34 @@ -139,12 +140,16 @@ public class DruidDdlParser { ddlResult.setType(EventType.DELETE); ddlResults.add(ddlResult); } else if (statement instanceof SQLCreateDatabaseStatement){ - // add create database support DdlResult ddlResult = new DdlResult(); - SQLCreateDatabaseStatement createDatabase= (SQLCreateDatabaseStatement) statement; ddlResult.setType(EventType.QUERY); + //只设置schema processName(ddlResult,schmeaName,null,false); ddlResults.add(ddlResult); + } else if(statement instanceof SQLDropDatabaseStatement){ + DdlResult ddlResult = new DdlResult(); + ddlResult.setType(EventType.QUERY); + //只设置schema + processName(ddlResult,schmeaName,null,false); } } @@ -153,6 +158,9 @@ public class DruidDdlParser { private static void processName(DdlResult ddlResult, String schema, SQLExpr sqlName, boolean isOri) { if (sqlName == null) { + if (StringUtils.isNotBlank(schema)){ + ddlResult.setSchemaName(schema); + } return; } From 3efd589cb169f8f5f70c856427a905cda8c7ee57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 16 Jul 2018 22:45:16 +0800 Subject: [PATCH 003/360] fixed issue #724 , retry for broke pipe --- .../inbound/mysql/tsdb/DatabaseTableMeta.java | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index 585f1de9..12cc46f6 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -298,13 +298,24 @@ public class DatabaseTableMeta implements TableMetaTSDB { createDDL = packet.getFieldValues().get(1); tableMetaFromDB.setFields(TableMetaCache.parseTableMeta(schema, table, packet)); } - } catch (IOException e) { - if (e.getMessage().contains("errorNumber=1146")) { - logger.error("table not exist in db , pls check :" + getFullName(schema, table) + " , mem : " - + tableMetaFromMem); - return false; + } catch (Throwable e) { + try { + // retry for broke pipe, see: + // https://github.com/alibaba/canal/issues/724 + connection.reconnect(); + ResultSetPacket packet = connection.query("show create table " + getFullName(schema, table)); + if (packet.getFieldValues().size() > 1) { + createDDL = packet.getFieldValues().get(1); + tableMetaFromDB.setFields(TableMetaCache.parseTableMeta(schema, table, packet)); + } + } catch (IOException e1) { + if (e.getMessage().contains("errorNumber=1146")) { + logger.error("table not exist in db , pls check :" + getFullName(schema, table) + " , mem : " + + tableMetaFromMem); + return false; + } + throw new CanalParseException(e); } - throw new CanalParseException(e); } boolean result = compareTableMeta(tableMetaFromMem, tableMetaFromDB); From c1805a4831311cc4146db5b9c966f2a6add317a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 16 Jul 2018 22:54:35 +0800 Subject: [PATCH 004/360] format DruidDdlParser --- .../inbound/mysql/ddl/DruidDdlParser.java | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java index efb3870a..d82e5eff 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java @@ -4,18 +4,40 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.apache.commons.lang.StringUtils; + import com.alibaba.fastsql.sql.SQLUtils; import com.alibaba.fastsql.sql.ast.SQLExpr; import com.alibaba.fastsql.sql.ast.SQLStatement; import com.alibaba.fastsql.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.fastsql.sql.ast.expr.SQLPropertyExpr; -import com.alibaba.fastsql.sql.ast.statement.*; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableAddConstraint; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableAddIndex; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableDropConstraint; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableDropIndex; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableDropKey; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableItem; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableRename; +import com.alibaba.fastsql.sql.ast.statement.SQLAlterTableStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLConstraint; +import com.alibaba.fastsql.sql.ast.statement.SQLCreateDatabaseStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLCreateIndexStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLCreateTableStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLDeleteStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLDropDatabaseStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLDropIndexStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLDropTableStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLExprTableSource; +import com.alibaba.fastsql.sql.ast.statement.SQLInsertStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLTableSource; +import com.alibaba.fastsql.sql.ast.statement.SQLTruncateStatement; +import com.alibaba.fastsql.sql.ast.statement.SQLUnique; +import com.alibaba.fastsql.sql.ast.statement.SQLUpdateStatement; import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement; import com.alibaba.fastsql.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item; import com.alibaba.fastsql.sql.parser.ParserException; import com.alibaba.fastsql.util.JdbcConstants; import com.alibaba.otter.canal.protocol.CanalEntry.EventType; -import org.apache.commons.lang.StringUtils; /** * @author agapple 2017年7月27日 下午4:05:34 @@ -139,17 +161,17 @@ public class DruidDdlParser { processName(ddlResult, schmeaName, delete.getTableName(), false); ddlResult.setType(EventType.DELETE); ddlResults.add(ddlResult); - } else if (statement instanceof SQLCreateDatabaseStatement){ + } else if (statement instanceof SQLCreateDatabaseStatement) { DdlResult ddlResult = new DdlResult(); ddlResult.setType(EventType.QUERY); - //只设置schema - processName(ddlResult,schmeaName,null,false); + // 只设置schema + processName(ddlResult, schmeaName, null, false); ddlResults.add(ddlResult); - } else if(statement instanceof SQLDropDatabaseStatement){ + } else if (statement instanceof SQLDropDatabaseStatement) { DdlResult ddlResult = new DdlResult(); ddlResult.setType(EventType.QUERY); - //只设置schema - processName(ddlResult,schmeaName,null,false); + // 只设置schema + processName(ddlResult, schmeaName, null, false); } } @@ -158,7 +180,7 @@ public class DruidDdlParser { private static void processName(DdlResult ddlResult, String schema, SQLExpr sqlName, boolean isOri) { if (sqlName == null) { - if (StringUtils.isNotBlank(schema)){ + if (StringUtils.isNotBlank(schema)) { ddlResult.setSchemaName(schema); } return; From bef6a66eaf107f6451a352b7d9cb71919e442fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 16 Jul 2018 23:00:35 +0800 Subject: [PATCH 005/360] fixed issue #296, support getWithoutAck/ack/rollback for NPE --- .../otter/canal/client/impl/ClusterCanalConnector.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/ClusterCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/ClusterCanalConnector.java index 6ea3a5cd..7ec1781a 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/ClusterCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/ClusterCanalConnector.java @@ -184,7 +184,7 @@ public class ClusterCanalConnector implements CanalConnector { return msg; } catch (Throwable t) { logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s", - currentConnector.getAddress()), t); + currentConnector != null ? currentConnector.getAddress() : "null"), t); times++; restart(); logger.info("restart the connector for next round retry."); @@ -201,7 +201,7 @@ public class ClusterCanalConnector implements CanalConnector { return msg; } catch (Throwable t) { logger.warn(String.format("something goes wrong when getWithoutAck data from server:%s", - currentConnector.getAddress()), t); + currentConnector != null ? currentConnector.getAddress() : "null"), t); times++; restart(); logger.info("restart the connector for next round retry."); @@ -218,7 +218,7 @@ public class ClusterCanalConnector implements CanalConnector { return; } catch (Throwable t) { logger.warn(String.format("something goes wrong when rollbacking data from server:%s", - currentConnector.getAddress()), + currentConnector != null ? currentConnector.getAddress() : "null"), t); times++; restart(); @@ -236,7 +236,7 @@ public class ClusterCanalConnector implements CanalConnector { return; } catch (Throwable t) { logger.warn(String.format("something goes wrong when rollbacking data from server:%s", - currentConnector.getAddress()), + currentConnector != null ? currentConnector.getAddress() : "null"), t); times++; restart(); @@ -255,7 +255,7 @@ public class ClusterCanalConnector implements CanalConnector { return; } catch (Throwable t) { logger.warn(String.format("something goes wrong when acking data from server:%s", - currentConnector.getAddress()), + currentConnector != null ? currentConnector.getAddress() : "null"), t); times++; restart(); From 441d2d244929f6b28aacdf1c6c58a749cbe87c60 Mon Sep 17 00:00:00 2001 From: winger Date: Tue, 17 Jul 2018 12:04:27 +0800 Subject: [PATCH 006/360] fix issue #751 --- .../inbound/mysql/dbsync/LogEventConvert.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index f99d551c..153a0ecd 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -504,9 +504,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar throw new CanalParseException("unsupport event type :" + event.getHeader().getType()); } - TableMapLogEvent table = event.getTable(); - Header header = createHeader(event.getHeader(), table.getDbName(), table.getTableName(), eventType); - RowChange.Builder rowChangeBuider = RowChange.newBuilder(); rowChangeBuider.setTableId(event.getTableId()); rowChangeBuider.setIsDdl(false); @@ -517,6 +514,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar BitSet changeColumns = event.getChangeColumns(); boolean tableError = false; + int rowsCount = 0; while (buffer.nextOneRow(columns)) { // 处理row记录 RowData.Builder rowDataBuilder = RowData.newBuilder(); @@ -537,9 +535,13 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar tableError |= parseOneRow(rowDataBuilder, event, buffer, changeColumns, true, tableMeta); } + rowsCount ++; rowChangeBuider.addRowDatas(rowDataBuilder.build()); } + TableMapLogEvent table = event.getTable(); + Header header = createHeader(event.getHeader(), table.getDbName(), table.getTableName(), eventType, rowsCount); + RowChange rowChange = rowChangeBuider.build(); if (tableError) { Entry entry = createEntry(header, EntryType.ROWDATA, ByteString.EMPTY); @@ -793,7 +795,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString()); } + private Header createHeader(LogHeader logHeader, String schemaName, String tableName, EventType eventType) { + return createHeader(logHeader, schemaName, tableName, eventType, -1); + } + + private Header createHeader(LogHeader logHeader, String schemaName, String tableName, EventType eventType, Integer rowsCount) { // header会做信息冗余,方便以后做检索或者过滤 Header.Builder headerBuilder = Header.newBuilder(); headerBuilder.setVersion(version); @@ -818,6 +825,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar String gtid = gtidSet.toString(); headerBuilder.setGtid(gtid); } + + // add rowsCount suppport + if (rowsCount > 0) { + Pair pair = createSpecialPair("rowsCount", String.valueOf(rowsCount)); + headerBuilder.addProps(pair); + } return headerBuilder.build(); } From df8fe4a9eb0f82a3b43f974fcfacc0c92e522f35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 17 Jul 2018 22:15:42 +0800 Subject: [PATCH 007/360] fixed issue #756 , improve exception logger --- .../common/utils/NamedThreadFactory.java | 5 +++ .../mysql/MysqlMultiStageCoprocessor.java | 41 +++++++++++++++---- 2 files changed, 39 insertions(+), 7 deletions(-) diff --git a/common/src/main/java/com/alibaba/otter/canal/common/utils/NamedThreadFactory.java b/common/src/main/java/com/alibaba/otter/canal/common/utils/NamedThreadFactory.java index 866664a1..eeb94c4a 100644 --- a/common/src/main/java/com/alibaba/otter/canal/common/utils/NamedThreadFactory.java +++ b/common/src/main/java/com/alibaba/otter/canal/common/utils/NamedThreadFactory.java @@ -23,6 +23,11 @@ public class NamedThreadFactory implements ThreadFactory { public void uncaughtException(Thread t, Throwable e) { + if (e instanceof InterruptedException + || (e.getCause() != null && e.getCause() instanceof InterruptedException)) { + return; + } + logger.error("from " + t.getName(), e); } }; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 6e02e22d..88278d84 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -18,7 +18,7 @@ import com.lmax.disruptor.BatchEventProcessor; import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; -import com.lmax.disruptor.FatalExceptionHandler; +import com.lmax.disruptor.ExceptionHandler; import com.lmax.disruptor.LifecycleAware; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.Sequence; @@ -85,11 +85,12 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement this.stageExecutor = Executors.newFixedThreadPool(2, new NamedThreadFactory("MultiStageCoprocessor-other-" + destination)); SequenceBarrier sequenceBarrier = disruptorMsgBuffer.newBarrier(); - + ExceptionHandler exceptionHandler = new SimpleFatalExceptionHandler(); // stage 2 BatchEventProcessor simpleParserStage = new BatchEventProcessor(disruptorMsgBuffer, sequenceBarrier, new SimpleParserStage()); + simpleParserStage.setExceptionHandler(exceptionHandler); disruptorMsgBuffer.addGatingSequences(simpleParserStage.getSequence()); // stage 3 @@ -100,7 +101,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement } WorkerPool workerPool = new WorkerPool(disruptorMsgBuffer, dmlParserSequenceBarrier, - new FatalExceptionHandler(), + exceptionHandler, workHandlers); Sequence[] sequence = workerPool.getWorkerSequences(); disruptorMsgBuffer.addGatingSequences(sequence); @@ -110,6 +111,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement BatchEventProcessor sinkStoreStage = new BatchEventProcessor(disruptorMsgBuffer, sinkSequenceBarrier, new SinkStoreStage()); + sinkStoreStage.setExceptionHandler(exceptionHandler); disruptorMsgBuffer.addGatingSequences(sinkStoreStage.getSequence()); // start work @@ -161,7 +163,10 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void reset() { - stop(); + if (isStart()) { + stop(); + } + start(); } @@ -231,7 +236,9 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - stop(); + if (isStart()) { + stop(); + } } } @@ -267,7 +274,9 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - stop(); + if (isStart()) { + stop(); + } } } @@ -306,7 +315,9 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - stop(); + if (isStart()) { + stop(); + } } } @@ -369,6 +380,22 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement } + class SimpleFatalExceptionHandler implements ExceptionHandler { + + @Override + public void handleEventException(final Throwable ex, final long sequence, final Object event) { + throw new RuntimeException(ex); + } + + @Override + public void handleOnStartException(final Throwable ex) { + } + + @Override + public void handleOnShutdownException(final Throwable ex) { + } + } + class MessageEventFactory implements EventFactory { public MessageEvent newInstance() { From 8e856acac595a3d37c5d086fb3776eeccaf764b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 17 Jul 2018 22:32:57 +0800 Subject: [PATCH 008/360] fixed issue #742, improve h2 config --- deployer/src/main/resources/spring/tsdb/h2-tsdb.xml | 4 ++-- deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml b/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml index 6f8ba05d..beb61236 100644 --- a/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml +++ b/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml @@ -31,8 +31,8 @@ - - + + diff --git a/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml b/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml index 555eec44..58cab638 100644 --- a/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml +++ b/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml @@ -31,8 +31,8 @@ - - + + From 89726a636530b73a6b97cecc2b5bcee4fb464f86 Mon Sep 17 00:00:00 2001 From: winger Date: Wed, 18 Jul 2018 17:07:39 +0800 Subject: [PATCH 009/360] append gtid lastCommitted and sequenceNumber support --- .../dbsync/binlog/event/GtidLogEvent.java | 19 +++++++++++++++++++ .../inbound/mysql/dbsync/LogEventConvert.java | 6 ++++++ 2 files changed, 25 insertions(+) diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java index 064f60bf..b645e954 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java @@ -17,10 +17,13 @@ public class GtidLogEvent extends LogEvent { public static final int ENCODED_FLAG_LENGTH = 1; // / Length of SID in event encoding public static final int ENCODED_SID_LENGTH = 16; + public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2; private boolean commitFlag; private UUID sid; private long gno; + private Long lastCommitted; + private Long sequenceNumber; public GtidLogEvent(LogHeader header, LogBuffer buffer, FormatDescriptionLogEvent descriptionEvent){ super(header); @@ -40,6 +43,14 @@ public class GtidLogEvent extends LogEvent { gno = buffer.getLong64(); + // support gtid lastCommitted and sequenceNumber + // 42 = 1+16+8+1+8+8 + if (buffer.capacity() > 42 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) { + lastCommitted = buffer.getLong64(); + sequenceNumber = buffer.getLong64(); + } + + // ignore gtid info read // sid.copy_from((uchar *)ptr_buffer); // ptr_buffer+= ENCODED_SID_LENGTH; @@ -62,4 +73,12 @@ public class GtidLogEvent extends LogEvent { public long getGno() { return gno; } + + public Long getLastCommitted() { + return lastCommitted; + } + + public Long getSequenceNumber() { + return sequenceNumber; + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 153a0ecd..09af808f 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -167,6 +167,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar if (gtidSet != null) { gtidSet.update(value); } + if (logEvent.getLastCommitted() != null) { + builder.setKey("lastCommitted"); + builder.setValue(String.valueOf(logEvent.getLastCommitted())); + builder.setKey("sequenceNumber"); + builder.setValue(String.valueOf(logEvent.getSequenceNumber())); + } Header header = createHeader(logHeader, "", "", EventType.GTID); return createEntry(header, EntryType.GTIDLOG, builder.build().toByteString()); From 10bb800f85ac047071a942a3323fccebed3dac44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 18 Jul 2018 18:21:45 +0800 Subject: [PATCH 010/360] =?UTF-8?q?fixed=20issue=20#726=20,=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96SessionHandler=E7=9A=84=E4=BC=A0=E8=BE=93=E5=A4=84?= =?UTF-8?q?=E7=90=86,=E6=8F=90=E5=89=8D=E5=BA=8F=E5=88=97=E5=8C=96Entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/impl/SimpleCanalConnector.java | 19 +++- .../mysql/MysqlMultiStageCoprocessor.java | 37 ++++--- .../inbound/mysql/tsdb/DatabaseTableMeta.java | 4 +- .../inbound/mysql/tsdb/MemoryTableMeta.java | 2 +- .../parse/inbound/group/DummyEventStore.java | 42 +++----- pom.xml | 2 +- .../alibaba/otter/canal/protocol/Message.java | 36 +++++++ .../embedded/CanalServerWithEmbedded.java | 22 ++--- .../server/netty/handler/SessionHandler.java | 15 ++- .../canal/sink/entry/EntryEventSink.java | 16 +-- .../entry/HeartBeatEntryEventHandler.java | 4 +- .../sink/entry/group/TimelineBarrier.java | 2 +- .../group/TimelineTransactionBarrier.java | 4 +- .../canal/sink/stub/DummyEventStore.java | 12 +-- .../canal/store/helper/CanalEventUtils.java | 27 +++--- .../memory/MemoryEventStoreWithBuffer.java | 11 +-- .../otter/canal/store/model/Event.java | 97 +++++++++++++++++-- .../MemoryEventStoreMultiThreadTest.java | 7 +- 18 files changed, 252 insertions(+), 107 deletions(-) diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java index 0feb3a27..508c01dc 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java @@ -71,7 +71,7 @@ public class SimpleCanalConnector implements CanalConnector { private volatile boolean connected = false; // 代表connected是否已正常执行,因为有HA,不代表在工作中 private boolean rollbackOnConnect = true; // 是否在connect链接成功后,自动执行rollback操作 private boolean rollbackOnDisConnect = false; // 是否在connect链接成功后,自动执行rollback操作 - + private boolean lazyParseEntry = false; // 是否自动化解析Entry对象,如果考虑最大化性能可以延后解析 // 读写数据分别使用不同的锁进行控制,减小锁粒度,读也需要排他锁,并发度容易造成数据包混乱,反序列化失败 private Object readDataLock = new Object(); private Object writeDataLock = new Object(); @@ -328,8 +328,13 @@ public class SimpleCanalConnector implements CanalConnector { Messages messages = Messages.parseFrom(p.getBody()); Message result = new Message(messages.getBatchId()); - for (ByteString byteString : messages.getMessagesList()) { - result.addEntry(Entry.parseFrom(byteString)); + if (lazyParseEntry) { + // byteString + result.setRawEntries(messages.getMessagesList()); + } else { + for (ByteString byteString : messages.getMessagesList()) { + result.addEntry(Entry.parseFrom(byteString)); + } } return result; } @@ -538,6 +543,14 @@ public class SimpleCanalConnector implements CanalConnector { this.filter = filter; } + public boolean isLazyParseEntry() { + return lazyParseEntry; + } + + public void setLazyParseEntry(boolean lazyParseEntry) { + this.lazyParseEntry = lazyParseEntry; + } + public void stopRunning() { if (running) { running = false; // 设置为非running状态 diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 88278d84..76c70723 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.parse.inbound.mysql; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.locks.LockSupport; import org.apache.commons.lang.StringUtils; @@ -19,6 +20,7 @@ import com.lmax.disruptor.BlockingWaitStrategy; import com.lmax.disruptor.EventFactory; import com.lmax.disruptor.EventHandler; import com.lmax.disruptor.ExceptionHandler; +import com.lmax.disruptor.InsufficientCapacityException; import com.lmax.disruptor.LifecycleAware; import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.Sequence; @@ -141,24 +143,35 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement * 网络数据投递 */ public void publish(LogBuffer buffer) { - if (!isStart() && exception != null) { - throw exception; - } - long next = disruptorMsgBuffer.next(); - MessageEvent event = disruptorMsgBuffer.get(next); - event.setBuffer(buffer); - disruptorMsgBuffer.publish(next); + publish(buffer, null); } public void publish(LogBuffer buffer, String binlogFileName) { if (!isStart() && exception != null) { throw exception; } - long next = disruptorMsgBuffer.next(); - MessageEvent event = disruptorMsgBuffer.get(next); - event.setBuffer(buffer); - event.setBinlogFileName(binlogFileName); - disruptorMsgBuffer.publish(next); + + boolean interupted = false; + do { + try { + long next = disruptorMsgBuffer.tryNext(); + MessageEvent event = disruptorMsgBuffer.get(next); + event.setBuffer(buffer); + if (binlogFileName != null) { + event.setBinlogFileName(binlogFileName); + } + disruptorMsgBuffer.publish(next); + break; + } catch (InsufficientCapacityException e) { + // park + LockSupport.parkNanos(1L); + interupted = Thread.interrupted(); + } + } while (!interupted && isStart()); + + if (exception != null) { + throw exception; + } } @Override diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index 12cc46f6..bb2af45f 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -70,7 +70,9 @@ public class DatabaseTableMeta implements TableMetaTSDB { @Override public Thread newThread(Runnable r) { - return new Thread(r, "[scheduler-table-meta-snapshot]"); + Thread thread = new Thread(r, "[scheduler-table-meta-snapshot]"); + thread.setDaemon(true); + return thread; } }); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java index 127df1c4..9a886336 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java @@ -94,7 +94,7 @@ public class MemoryTableMeta implements TableMetaTSDB { tableMeta = tableMetas.get(keys); if (tableMeta == null) { Schema schemaRep = repository.findSchema(schema); - if (schema == null) { + if (schemaRep == null) { return null; } SchemaObject data = schemaRep.findTable(table); diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/group/DummyEventStore.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/group/DummyEventStore.java index 24f7f551..c542e5c0 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/group/DummyEventStore.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/group/DummyEventStore.java @@ -7,7 +7,6 @@ import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; -import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry.EntryType; import com.alibaba.otter.canal.protocol.position.Position; import com.alibaba.otter.canal.store.CanalEventStore; @@ -18,7 +17,7 @@ import com.alibaba.otter.canal.store.model.Events; public class DummyEventStore implements CanalEventStore { private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; - private static final String messgae = "{0} [{1}:{2}:{3}] {4} {5}.{6}"; + private static final String messgae = "{0} [{1}:{2}:{3}]"; public void ack(Position position) throws CanalStoreException { @@ -82,46 +81,40 @@ public class DummyEventStore implements CanalEventStore { public void put(List datas) throws InterruptedException, CanalStoreException { for (Event data : datas) { - CanalEntry.Header header = data.getEntry().getHeader(); - Date date = new Date(header.getExecuteTime()); + Date date = new Date(data.getExecuteTime()); SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); - if (data.getEntry().getEntryType() == EntryType.TRANSACTIONBEGIN - || data.getEntry().getEntryType() == EntryType.TRANSACTIONEND) { + if (data.getEntryType() == EntryType.TRANSACTIONBEGIN || data.getEntryType() == EntryType.TRANSACTIONEND) { // System.out.println(MessageFormat.format(messgae, new Object[] // { Thread.currentThread().getName(), // header.getLogfilename(), header.getLogfileoffset(), // format.format(date), // data.getEntry().getEntryType(), "" })); - System.out.println(data.getEntry().getEntryType()); + System.out.println(data.getEntryType()); } else { System.out.println(MessageFormat.format(messgae, - new Object[] { Thread.currentThread().getName(), header.getLogfileName(), - String.valueOf(header.getLogfileOffset()), format.format(date), header.getEventType(), - header.getSchemaName(), header.getTableName() })); + new Object[] { Thread.currentThread().getName(), data.getJournalName(), + String.valueOf(data.getPosition()), format.format(date) })); } } } public boolean put(List datas, long timeout, TimeUnit unit) throws InterruptedException, CanalStoreException { for (Event data : datas) { - CanalEntry.Header header = data.getEntry().getHeader(); - Date date = new Date(header.getExecuteTime()); + Date date = new Date(data.getExecuteTime()); SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); - if (data.getEntry().getEntryType() == EntryType.TRANSACTIONBEGIN - || data.getEntry().getEntryType() == EntryType.TRANSACTIONEND) { + if (data.getEntryType() == EntryType.TRANSACTIONBEGIN || data.getEntryType() == EntryType.TRANSACTIONEND) { // System.out.println(MessageFormat.format(messgae, new Object[] // { Thread.currentThread().getName(), // header.getLogfilename(), header.getLogfileoffset(), // format.format(date), // data.getEntry().getEntryType(), "" })); - System.out.println(data.getEntry().getEntryType()); + System.out.println(data.getEntryType()); } else { System.out.println(MessageFormat.format(messgae, - new Object[] { Thread.currentThread().getName(), header.getLogfileName(), - String.valueOf(header.getLogfileOffset()), format.format(date), header.getEventType(), - header.getSchemaName(), header.getTableName() })); + new Object[] { Thread.currentThread().getName(), data.getJournalName(), + String.valueOf(data.getPosition()), format.format(date) })); } } return true; @@ -131,23 +124,20 @@ public class DummyEventStore implements CanalEventStore { System.out.println("\n>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); for (Event data : datas) { - CanalEntry.Header header = data.getEntry().getHeader(); - Date date = new Date(header.getExecuteTime()); + Date date = new Date(data.getExecuteTime()); SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); - if (data.getEntry().getEntryType() == EntryType.TRANSACTIONBEGIN - || data.getEntry().getEntryType() == EntryType.TRANSACTIONEND) { + if (data.getEntryType() == EntryType.TRANSACTIONBEGIN || data.getEntryType() == EntryType.TRANSACTIONEND) { // System.out.println(MessageFormat.format(messgae, new Object[] // { Thread.currentThread().getName(), // header.getLogfilename(), header.getLogfileoffset(), // format.format(date), // data.getEntry().getEntryType(), "" })); - System.out.println(data.getEntry().getEntryType()); + System.out.println(data.getEntryType()); } else { System.out.println(MessageFormat.format(messgae, - new Object[] { Thread.currentThread().getName(), header.getLogfileName(), - String.valueOf(header.getLogfileOffset()), format.format(date), header.getEventType(), - header.getSchemaName(), header.getTableName() })); + new Object[] { Thread.currentThread().getName(), data.getJournalName(), + String.valueOf(data.getPosition()), format.format(date) })); } } diff --git a/pom.xml b/pom.xml index 8ab961ea..1e8123ef 100644 --- a/pom.xml +++ b/pom.xml @@ -256,7 +256,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_371 + 2.0.0_preview_520 com.alibaba diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index 984766e2..35adf15d 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -8,6 +8,7 @@ import org.apache.commons.lang.builder.ToStringBuilder; import com.alibaba.otter.canal.common.utils.CanalToStringStyle; import com.alibaba.otter.canal.protocol.CanalEntry.Entry; +import com.google.protobuf.ByteString; /** * @author zebin.xuzb @ 2012-6-19 @@ -18,11 +19,26 @@ public class Message implements Serializable { private static final long serialVersionUID = 1234034768477580009L; private long id; + @Deprecated private List entries = new ArrayList(); + // row data for performance, see: + // https://github.com/alibaba/canal/issues/726 + private boolean raw = true; + private List rawEntries = new ArrayList(); public Message(long id, List entries){ this.id = id; this.entries = entries == null ? new ArrayList() : entries; + this.raw = false; + } + + public Message(long id, boolean raw, List entries){ + this.id = id; + if (raw) { + this.rawEntries = entries == null ? new ArrayList() : entries; + } else { + this.entries = entries == null ? new ArrayList() : entries; + } } public Message(long id){ @@ -49,6 +65,26 @@ public class Message implements Serializable { this.entries.add(entry); } + public void setRawEntries(List rawEntries) { + this.rawEntries = rawEntries; + } + + public void addRawEntry(ByteString rawEntry) { + this.rawEntries.add(rawEntry); + } + + public List getRawEntries() { + return rawEntries; + } + + public boolean isRaw() { + return raw; + } + + public void setRaw(boolean raw) { + this.raw = raw; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index 8bfa8fe6..722bb2bc 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -14,7 +14,6 @@ import org.springframework.util.CollectionUtils; import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.instance.core.CanalInstanceGenerator; -import com.alibaba.otter.canal.protocol.CanalEntry.Entry; import com.alibaba.otter.canal.protocol.ClientIdentity; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.position.LogPosition; @@ -30,6 +29,7 @@ import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.MigrateMap; +import com.google.protobuf.ByteString; /** * 嵌入式版本实现 @@ -223,14 +223,14 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C logger.debug("get successfully, clientId:{} batchSize:{} but result is null", clientIdentity.getClientId(), batchSize); - return new Message(-1, new ArrayList()); // 返回空包,避免生成batchId,浪费性能 + return new Message(-1, true, new ArrayList()); // 返回空包,避免生成batchId,浪费性能 } else { // 记录到流式信息 Long batchId = canalInstance.getMetaManager().addBatch(clientIdentity, events.getPositionRange()); - List entrys = Lists.transform(events.getEvents(), new Function() { + List entrys = Lists.transform(events.getEvents(), new Function() { - public Entry apply(Event input) { - return input.getEntry(); + public ByteString apply(Event input) { + return input.getRawEntry(); } }); if (logger.isInfoEnabled()) { @@ -243,7 +243,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C } // 直接提交ack ack(clientIdentity, batchId); - return new Message(batchId, entrys); + return new Message(batchId, true, entrys); } } } @@ -302,14 +302,14 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C logger.debug("getWithoutAck successfully, clientId:{} batchSize:{} but result is null", clientIdentity.getClientId(), batchSize); - return new Message(-1, new ArrayList()); // 返回空包,避免生成batchId,浪费性能 + return new Message(-1, true, new ArrayList()); // 返回空包,避免生成batchId,浪费性能 } else { // 记录到流式信息 Long batchId = canalInstance.getMetaManager().addBatch(clientIdentity, events.getPositionRange()); - List entrys = Lists.transform(events.getEvents(), new Function() { + List entrys = Lists.transform(events.getEvents(), new Function() { - public Entry apply(Event input) { - return input.getEntry(); + public ByteString apply(Event input) { + return input.getRawEntry(); } }); if (logger.isInfoEnabled()) { @@ -320,7 +320,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C batchId, events.getPositionRange()); } - return new Message(batchId, entrys); + return new Message(batchId, true, entrys); } } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index 3eda5a0d..8f7b4278 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -135,9 +135,18 @@ public class SessionHandler extends SimpleChannelHandler { Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); messageBuilder.setBatchId(message.getId()); - if (message.getId() != -1 && !CollectionUtils.isEmpty(message.getEntries())) { - for (Entry entry : message.getEntries()) { - messageBuilder.addMessages(entry.toByteString()); + if (message.getId() != -1) { + if (message.isRaw()) { + // for performance + if (!CollectionUtils.isEmpty(message.getRawEntries())) { + messageBuilder.addAllMessages(message.getRawEntries()); + } + } else { + if (!CollectionUtils.isEmpty(message.getEntries())) { + for (Entry entry : message.getEntries()) { + messageBuilder.addMessages(entry.toByteString()); + } + } } } packetBuilder.setBody(messageBuilder.build().toByteString()); diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java index bfb4b771..6511e6ba 100644 --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java @@ -92,11 +92,11 @@ public class EntryEventSink extends AbstractCanalEventSink events = new ArrayList(); for (CanalEntry.Entry entry : entrys) { - Event event = new Event(new LogIdentity(remoteAddress, -1L), entry); - if (!doFilter(event)) { + if (!doFilter(entry)) { continue; } + Event event = new Event(new LogIdentity(remoteAddress, -1L), entry); events.add(event); hasRowData |= (entry.getEntryType() == EntryType.ROWDATA); hasHeartBeat |= (entry.getEntryType() == EntryType.HEARTBEAT); @@ -111,7 +111,7 @@ public class EntryEventSink extends AbstractCanalEventSink emptyTransactionInterval || lastEmptyTransactionCount.incrementAndGet() > emptyTransctionThresold) { @@ -126,15 +126,15 @@ public class EntryEventSink extends AbstractCanalEventSink before(List events) { boolean existHeartBeat = false; for (Event event : events) { - if (event.getEntry().getEntryType() == EntryType.HEARTBEAT) { + if (event.getEntryType() == EntryType.HEARTBEAT) { existHeartBeat = true; } } @@ -29,7 +29,7 @@ public class HeartBeatEntryEventHandler extends AbstractCanalEventDownStreamHand // 目前heartbeat和其他事件是分离的,保险一点还是做一下检查处理 List result = new ArrayList(); for (Event event : events) { - if (event.getEntry().getEntryType() != EntryType.HEARTBEAT) { + if (event.getEntryType() != EntryType.HEARTBEAT) { result.add(event); } } diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineBarrier.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineBarrier.java index be9edfa0..24f457b7 100644 --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineBarrier.java +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineBarrier.java @@ -139,7 +139,7 @@ public class TimelineBarrier implements GroupBarrier { } private Long getTimestamp(Event event) { - return event.getEntry().getHeader().getExecuteTime(); + return event.getExecuteTime(); } } diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineTransactionBarrier.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineTransactionBarrier.java index 774818f2..59bf8fd7 100644 --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineTransactionBarrier.java +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/group/TimelineTransactionBarrier.java @@ -113,11 +113,11 @@ public class TimelineTransactionBarrier extends TimelineBarrier { } private boolean isTransactionBegin(Event event) { - return event.getEntry().getEntryType() == EntryType.TRANSACTIONBEGIN; + return event.getEntryType() == EntryType.TRANSACTIONBEGIN; } private boolean isTransactionEnd(Event event) { - return event.getEntry().getEntryType() == EntryType.TRANSACTIONEND; + return event.getEntryType() == EntryType.TRANSACTIONEND; } } diff --git a/sink/src/test/java/com/alibaba/otter/canal/sink/stub/DummyEventStore.java b/sink/src/test/java/com/alibaba/otter/canal/sink/stub/DummyEventStore.java index 8a52bb05..7f786ef2 100644 --- a/sink/src/test/java/com/alibaba/otter/canal/sink/stub/DummyEventStore.java +++ b/sink/src/test/java/com/alibaba/otter/canal/sink/stub/DummyEventStore.java @@ -60,33 +60,33 @@ public class DummyEventStore implements CanalEventStore { } public void put(Event data) throws InterruptedException, CanalStoreException { - System.out.println("time:" + data.getEntry().getHeader().getExecuteTime()); + System.out.println("time:" + data.getExecuteTime()); } public boolean put(Event data, long timeout, TimeUnit unit) throws InterruptedException, CanalStoreException { - System.out.println("time:" + data.getEntry().getHeader().getExecuteTime()); + System.out.println("time:" + data.getExecuteTime()); return true; } public boolean tryPut(Event data) throws CanalStoreException { - System.out.println("time:" + data.getEntry().getHeader().getExecuteTime()); + System.out.println("time:" + data.getExecuteTime()); return true; } public void put(List datas) throws InterruptedException, CanalStoreException { Event data = datas.get(0); - System.out.println("time:" + data.getEntry().getHeader().getExecuteTime()); + System.out.println("time:" + data.getExecuteTime()); } public boolean put(List datas, long timeout, TimeUnit unit) throws InterruptedException, CanalStoreException { Event data = datas.get(0); - System.out.println("time:" + data.getEntry().getHeader().getExecuteTime()); + System.out.println("time:" + data.getExecuteTime()); return true; } public boolean tryPut(List datas) throws CanalStoreException { Event data = datas.get(0); - System.out.println("time:" + data.getEntry().getHeader().getExecuteTime()); + System.out.println("time:" + data.getExecuteTime()); return true; } diff --git a/store/src/main/java/com/alibaba/otter/canal/store/helper/CanalEventUtils.java b/store/src/main/java/com/alibaba/otter/canal/store/helper/CanalEventUtils.java index ac858446..4fdbfcba 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/helper/CanalEventUtils.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/helper/CanalEventUtils.java @@ -2,7 +2,6 @@ package com.alibaba.otter.canal.store.helper; import org.apache.commons.lang.StringUtils; -import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.protocol.position.LogPosition; import com.alibaba.otter.canal.store.model.Event; @@ -48,14 +47,13 @@ public class CanalEventUtils { */ public static LogPosition createPosition(Event event) { EntryPosition position = new EntryPosition(); - position.setJournalName(event.getEntry().getHeader().getLogfileName()); - position.setPosition(event.getEntry().getHeader().getLogfileOffset()); - position.setTimestamp(event.getEntry().getHeader().getExecuteTime()); + position.setJournalName(event.getJournalName()); + position.setPosition(event.getPosition()); + position.setTimestamp(event.getExecuteTime()); // add serverId at 2016-06-28 - position.setServerId(event.getEntry().getHeader().getServerId()); - + position.setServerId(event.getServerId()); // add gtid - position.setGtid(event.getEntry().getHeader().getGtid()); + position.setGtid(event.getGtid()); LogPosition logPosition = new LogPosition(); logPosition.setPostion(position); @@ -68,9 +66,9 @@ public class CanalEventUtils { */ public static LogPosition createPosition(Event event, boolean included) { EntryPosition position = new EntryPosition(); - position.setJournalName(event.getEntry().getHeader().getLogfileName()); - position.setPosition(event.getEntry().getHeader().getLogfileOffset()); - position.setTimestamp(event.getEntry().getHeader().getExecuteTime()); + position.setJournalName(event.getJournalName()); + position.setPosition(event.getPosition()); + position.setTimestamp(event.getExecuteTime()); position.setIncluded(included); LogPosition logPosition = new LogPosition(); @@ -84,13 +82,14 @@ public class CanalEventUtils { */ public static boolean checkPosition(Event event, LogPosition logPosition) { EntryPosition position = logPosition.getPostion(); - CanalEntry.Entry entry = event.getEntry(); - boolean result = position.getTimestamp().equals(entry.getHeader().getExecuteTime()); + boolean result = position.getTimestamp().equals(event.getExecuteTime()); boolean exactely = (StringUtils.isBlank(position.getJournalName()) && position.getPosition() == null); if (!exactely) {// 精确匹配 - result &= StringUtils.equals(entry.getHeader().getLogfileName(), position.getJournalName()); - result &= position.getPosition().equals(entry.getHeader().getLogfileOffset()); + result &= position.getPosition().equals(event.getPosition()); + if (result) {// short path + result &= StringUtils.equals(event.getJournalName(), position.getJournalName()); + } } return result; diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index 99995a8f..075d1524 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -278,7 +278,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple // 提取数据并返回 for (; next <= end; next++) { Event event = entries[getIndex(next)]; - if (ddlIsolation && isDdl(event.getEntry().getHeader().getEventType())) { + if (ddlIsolation && isDdl(event.getEventType())) { // 如果是ddl隔离,直接返回 if (entrys.size() == 0) { entrys.add(event);// 如果没有DML事件,加入当前的DDL事件 @@ -297,7 +297,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple for (; memsize <= maxMemSize && next <= maxAbleSequence; next++) { // 永远保证可以取出第一条的记录,避免死锁 Event event = entries[getIndex(next)]; - if (ddlIsolation && isDdl(event.getEntry().getHeader().getEventType())) { + if (ddlIsolation && isDdl(event.getEventType())) { // 如果是ddl隔离,直接返回 if (entrys.size() == 0) { entrys.add(event);// 如果没有DML事件,加入当前的DDL事件 @@ -325,9 +325,8 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple for (int i = entrys.size() - 1; i >= 0; i--) { Event event = entrys.get(i); - if (CanalEntry.EntryType.TRANSACTIONBEGIN == event.getEntry().getEntryType() - || CanalEntry.EntryType.TRANSACTIONEND == event.getEntry().getEntryType() - || isDdl(event.getEntry().getHeader().getEventType())) { + if (CanalEntry.EntryType.TRANSACTIONBEGIN == event.getEntryType() + || CanalEntry.EntryType.TRANSACTIONEND == event.getEntryType() || isDdl(event.getEventType())) { // 将事务头/尾设置可被为ack的点 range.setAck(CanalEventUtils.createPosition(event)); break; @@ -532,7 +531,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple private long calculateSize(Event event) { // 直接返回binlog中的事件大小 - return event.getEntry().getHeader().getEventLength(); + return event.getRawLength(); } private int getIndex(long sequcnce) { diff --git a/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java b/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java index c31d37f3..07e9fead 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java @@ -6,7 +6,10 @@ import org.apache.commons.lang.builder.ToStringBuilder; import com.alibaba.otter.canal.common.utils.CanalToStringStyle; import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.CanalEntry.EntryType; +import com.alibaba.otter.canal.protocol.CanalEntry.EventType; import com.alibaba.otter.canal.protocol.position.LogIdentity; +import com.google.protobuf.ByteString; /** * store存储数据对象 @@ -18,14 +21,32 @@ public class Event implements Serializable { private static final long serialVersionUID = 1333330351758762739L; private LogIdentity logIdentity; // 记录数据产生的来源 - private CanalEntry.Entry entry; + private ByteString rawEntry; + + private long executeTime; + private EntryType entryType; + private String journalName; + private long position; + private long serverId; + private EventType eventType; + private String gtid; + private long rawLength; public Event(){ } public Event(LogIdentity logIdentity, CanalEntry.Entry entry){ this.logIdentity = logIdentity; - this.entry = entry; + this.entryType = entry.getEntryType(); + this.executeTime = entry.getHeader().getExecuteTime(); + this.journalName = entry.getHeader().getLogfileName(); + this.position = entry.getHeader().getLogfileOffset(); + this.serverId = entry.getHeader().getServerId(); + this.gtid = entry.getHeader().getGtid(); + this.eventType = entry.getHeader().getEventType(); + // build raw + this.rawEntry = entry.toByteString(); + this.rawLength = rawEntry.size(); } public LogIdentity getLogIdentity() { @@ -36,12 +57,76 @@ public class Event implements Serializable { this.logIdentity = logIdentity; } - public CanalEntry.Entry getEntry() { - return entry; + public ByteString getRawEntry() { + return rawEntry; } - public void setEntry(CanalEntry.Entry entry) { - this.entry = entry; + public void setRawEntry(ByteString rawEntry) { + this.rawEntry = rawEntry; + } + + public long getExecuteTime() { + return executeTime; + } + + public void setExecuteTime(long executeTime) { + this.executeTime = executeTime; + } + + public EntryType getEntryType() { + return entryType; + } + + public void setEntryType(EntryType entryType) { + this.entryType = entryType; + } + + public String getJournalName() { + return journalName; + } + + public void setJournalName(String journalName) { + this.journalName = journalName; + } + + public long getPosition() { + return position; + } + + public void setPosition(long position) { + this.position = position; + } + + public long getServerId() { + return serverId; + } + + public void setServerId(long serverId) { + this.serverId = serverId; + } + + public String getGtid() { + return gtid; + } + + public void setGtid(String gtid) { + this.gtid = gtid; + } + + public long getRawLength() { + return rawLength; + } + + public void setRawLength(long rawLength) { + this.rawLength = rawLength; + } + + public EventType getEventType() { + return eventType; + } + + public void setEventType(EventType eventType) { + this.eventType = eventType; } public String toString() { diff --git a/store/src/test/java/com/alibaba/otter/cancel/store/memory/buffer/MemoryEventStoreMultiThreadTest.java b/store/src/test/java/com/alibaba/otter/cancel/store/memory/buffer/MemoryEventStoreMultiThreadTest.java index 4209e4f1..9aa216f6 100644 --- a/store/src/test/java/com/alibaba/otter/cancel/store/memory/buffer/MemoryEventStoreMultiThreadTest.java +++ b/store/src/test/java/com/alibaba/otter/cancel/store/memory/buffer/MemoryEventStoreMultiThreadTest.java @@ -161,13 +161,12 @@ public class MemoryEventStoreMultiThreadTest extends MemoryEventStoreBase { first = entrys.getPositionRange().getEnd(); for (Event event : entrys.getEvents()) { - this.result.add(event.getEntry().getHeader().getLogfileOffset()); + this.result.add(event.getPosition()); } emptyCount = 0; - System.out.println("offest : " - + entrys.getEvents().get(0).getEntry().getHeader().getLogfileOffset() - + " , count :" + entrys.getEvents().size()); + System.out.println("offest : " + entrys.getEvents().get(0).getPosition() + " , count :" + + entrys.getEvents().size()); ackCount++; if (ackCount == 1) { eventStore.cleanUntil(entrys.getPositionRange().getEnd()); From 5582a38930814d30b147285a4074fe93c72849a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 19 Jul 2018 13:35:38 +0800 Subject: [PATCH 011/360] =?UTF-8?q?fixed=20issue=20#726,=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96SessionHandler=E9=87=8C=E7=9B=B4=E6=8E=A5=E6=8B=BCbyte?= =?UTF-8?q?[],=E7=BB=95=E8=BF=87protobuf=E7=9A=84=E5=A4=9A=E6=AC=A1?= =?UTF-8?q?=E6=8B=B7=E8=B4=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/impl/SimpleCanalConnector.java | 3 +- .../parse/inbound/AbstractEventParser.java | 9 +- .../parse/inbound/MultiStageCoprocessor.java | 4 +- .../inbound/mysql/LocalBinLogConnection.java | 6 +- .../parse/inbound/mysql/MysqlConnection.java | 8 +- .../mysql/MysqlMultiStageCoprocessor.java | 16 ++-- .../otter/canal/server/netty/NettyUtils.java | 2 +- .../server/netty/handler/SessionHandler.java | 65 +++++++++---- .../otter/canal/server/ProtocolTest.java | 92 +++++++++++++++++++ 9 files changed, 169 insertions(+), 36 deletions(-) create mode 100644 server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java index 508c01dc..0f29794d 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java @@ -319,7 +319,8 @@ public class SimpleCanalConnector implements CanalConnector { } private Message receiveMessages() throws IOException { - Packet p = Packet.parseFrom(readNextPacket()); + byte[] data = readNextPacket(); + Packet p = Packet.parseFrom(data); switch (p.getType()) { case MESSAGES: { if (!p.getCompression().equals(Compression.NONE)) { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index 54bb14e7..c8cdb1bd 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -331,6 +331,11 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle stopHeartBeat(); // 先停止心跳 parseThread.interrupt(); // 尝试中断 eventSink.interrupt(); + + if (multiStageCoprocessor != null && multiStageCoprocessor.isStart()) { + multiStageCoprocessor.stop(); + } + try { parseThread.join();// 等待其结束 } catch (InterruptedException e) { @@ -343,10 +348,6 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle if (transactionBuffer.isStart()) { transactionBuffer.stop(); } - - if (multiStageCoprocessor != null && multiStageCoprocessor.isStart()) { - multiStageCoprocessor.stop(); - } } protected boolean consumeTheEventAndProfilingIfNecessary(List entrys) throws CanalSinkException, diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java index 1c5c495d..96e063e9 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java @@ -21,9 +21,9 @@ public interface MultiStageCoprocessor extends CanalLifeCycle { /** * 网络数据投递 */ - public void publish(LogBuffer buffer); + public boolean publish(LogBuffer buffer); - public void publish(LogBuffer buffer, String binlogFileName); + public boolean publish(LogBuffer buffer, String binlogFileName); public void reset(); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java index f97c4ccd..6c59465b 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java @@ -221,7 +221,11 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); - coprocessor.publish(buffer, binlogfilename); // set filename + // set filename + if (!coprocessor.publish(buffer, binlogfilename)) { + needContinue = false; + break; + } } if (needContinue) {// 读取下一个 diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index 46f7e300..d01a210e 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -206,7 +206,9 @@ public class MysqlConnection implements ErosaConnection { while (fetcher.fetch()) { LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); - coprocessor.publish(buffer); + if (!coprocessor.publish(buffer)) { + break; + } } } finally { fetcher.close(); @@ -230,7 +232,9 @@ public class MysqlConnection implements ErosaConnection { while (fetcher.fetch()) { LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); - coprocessor.publish(buffer); + if (!coprocessor.publish(buffer)) { + break; + } } } finally { fetcher.close(); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 76c70723..65904b34 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -135,20 +135,22 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement } catch (Throwable e) { // ignore } - disruptorMsgBuffer = null; super.stop(); } /** * 网络数据投递 */ - public void publish(LogBuffer buffer) { - publish(buffer, null); + public boolean publish(LogBuffer buffer) { + return publish(buffer, null); } - public void publish(LogBuffer buffer, String binlogFileName) { - if (!isStart() && exception != null) { - throw exception; + public boolean publish(LogBuffer buffer, String binlogFileName) { + if (!isStart()) { + if (exception != null) { + throw exception; + } + return false; } boolean interupted = false; @@ -172,6 +174,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement if (exception != null) { throw exception; } + + return isStart(); } @Override diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java index 531c817f..22f45617 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java @@ -19,7 +19,7 @@ import com.alibaba.otter.canal.protocol.CanalPacket.Packet; public class NettyUtils { private static final Logger logger = LoggerFactory.getLogger(NettyUtils.class); - private static int HEADER_LENGTH = 4; + public static int HEADER_LENGTH = 4; public static Timer hashedWheelTimer = new HashedWheelTimer(); public static void write(Channel channel, byte[] body, ChannelFutureListener channelFutureListner) { diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index 8f7b4278..8b94d452 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -33,13 +33,10 @@ import com.alibaba.otter.canal.protocol.ClientIdentity; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; import com.alibaba.otter.canal.server.netty.NettyUtils; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; -/** - * 处理具体的客户端请求 - * - * @author jianghang 2012-10-24 下午02:21:13 - * @version 1.0.0 - */ public class SessionHandler extends SimpleChannelHandler { private static final Logger logger = LoggerFactory.getLogger(SessionHandler.class); @@ -130,27 +127,57 @@ public class SessionHandler extends SimpleChannelHandler { } // } - Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); - packetBuilder.setType(PacketType.MESSAGES); + if (message.getId() != -1 && message.isRaw()) { + List rowEntries = message.getRawEntries(); + // message size + int messageSize = 0; + messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, message.getId()); - Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); - messageBuilder.setBatchId(message.getId()); - if (message.getId() != -1) { - if (message.isRaw()) { - // for performance - if (!CollectionUtils.isEmpty(message.getRawEntries())) { + int dataSize = 0; + for (int i = 0; i < rowEntries.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + } + messageSize += dataSize; + messageSize += 1 * rowEntries.size(); + // packet size + int size = 0; + size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, + PacketType.MESSAGES.getNumber()); + size += com.google.protobuf.CodedOutputStream.computeTagSize(5) + + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + + messageSize; + // TODO recyle bytes[] + byte[] body = new byte[size]; + CodedOutputStream output = CodedOutputStream.newInstance(body); + output.writeEnum(3, PacketType.MESSAGES.getNumber()); + + output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(messageSize); + // message + output.writeInt64(1, message.getId()); + for (int i = 0; i < rowEntries.size(); i++) { + output.writeBytes(2, rowEntries.get(i)); + } + output.checkNoSpaceLeft(); + NettyUtils.write(ctx.getChannel(), body, null); + } else { + Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); + packetBuilder.setType(PacketType.MESSAGES); + + Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); + messageBuilder.setBatchId(message.getId()); + if (message.getId() != -1) { + if (message.isRaw() && !CollectionUtils.isEmpty(message.getRawEntries())) { messageBuilder.addAllMessages(message.getRawEntries()); - } - } else { - if (!CollectionUtils.isEmpty(message.getEntries())) { + } else if (!CollectionUtils.isEmpty(message.getEntries())) { for (Entry entry : message.getEntries()) { messageBuilder.addMessages(entry.toByteString()); } } } + packetBuilder.setBody(messageBuilder.build().toByteString()); + NettyUtils.write(ctx.getChannel(), packetBuilder.build().toByteArray(), null);// 输出数据 } - packetBuilder.setBody(messageBuilder.build().toByteString()); - NettyUtils.write(ctx.getChannel(), packetBuilder.build().toByteArray(), null);// 输出数据 } else { NettyUtils.error(401, MessageFormatter.format("destination or clientId is null", get.toString()).getMessage(), diff --git a/server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java b/server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java new file mode 100644 index 00000000..cb078ddf --- /dev/null +++ b/server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java @@ -0,0 +1,92 @@ +package com.alibaba.otter.canal.server; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.alibaba.otter.canal.protocol.CanalEntry.Entry; +import com.alibaba.otter.canal.protocol.CanalEntry.EntryType; +import com.alibaba.otter.canal.protocol.CanalEntry.Header; +import com.alibaba.otter.canal.protocol.CanalPacket.Compression; +import com.alibaba.otter.canal.protocol.CanalPacket.Messages; +import com.alibaba.otter.canal.protocol.CanalPacket.Packet; +import com.alibaba.otter.canal.protocol.CanalPacket.PacketType; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; + +public class ProtocolTest { + + @Test + public void testSimple() throws IOException { + Header.Builder headerBuilder = Header.newBuilder(); + headerBuilder.setLogfileName("mysql-bin.000001"); + headerBuilder.setLogfileOffset(1024); + headerBuilder.setExecuteTime(1024); + Entry.Builder entryBuilder = Entry.newBuilder(); + entryBuilder.setHeader(headerBuilder.build()); + entryBuilder.setEntryType(EntryType.ROWDATA); + Entry entry = entryBuilder.build(); + Message message = new Message(3, true, Arrays.asList(entry.toByteString())); + + byte[] body = buildData(message); + Packet packet = Packet.parseFrom(body); + switch (packet.getType()) { + case MESSAGES: { + if (!packet.getCompression().equals(Compression.NONE)) { + throw new CanalClientException("compression is not supported in this connector"); + } + + Messages messages = Messages.parseFrom(packet.getBody()); + Message result = new Message(messages.getBatchId()); + for (ByteString byteString : messages.getMessagesList()) { + result.addEntry(Entry.parseFrom(byteString)); + } + + System.out.println(result); + break; + } + default: { + throw new CanalClientException("unexpected packet type: " + packet.getType()); + } + } + } + + private byte[] buildData(Message message) throws IOException { + List rowEntries = message.getRawEntries(); + // message size + int messageSize = 0; + messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, message.getId()); + + int dataSize = 0; + for (int i = 0; i < rowEntries.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + } + messageSize += dataSize; + messageSize += 1 * rowEntries.size(); + // packet size + int size = 0; + size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, PacketType.MESSAGES.getNumber()); + size += com.google.protobuf.CodedOutputStream.computeTagSize(5) + + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + messageSize; + // TODO recyle bytes[] + byte[] body = new byte[size]; + CodedOutputStream output = CodedOutputStream.newInstance(body); + output.writeEnum(3, PacketType.MESSAGES.getNumber()); + + output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(messageSize); + // message + output.writeInt64(1, message.getId()); + for (int i = 0; i < rowEntries.size(); i++) { + output.writeBytes(2, rowEntries.get(i)); + } + output.checkNoSpaceLeft(); + + return body; + } +} From b19eaeb89a90b980e550e98b38e89efdd5ead222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 19 Jul 2018 17:55:53 +0800 Subject: [PATCH 012/360] =?UTF-8?q?fixed=20issue=20#726=20,=20=E5=8D=95?= =?UTF-8?q?=E9=93=BE=E6=8E=A5=E5=A4=8D=E7=94=A8ByteBuffer=E6=95=B0?= =?UTF-8?q?=E7=BB=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/server/netty/NettyUtils.java | 18 ++++++++++++++++ .../server/netty/handler/SessionHandler.java | 21 +++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java index 22f45617..20532400 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java @@ -2,8 +2,12 @@ package com.alibaba.otter.canal.server.netty; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.buffer.CompositeChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.Channels; @@ -22,6 +26,20 @@ public class NettyUtils { public static int HEADER_LENGTH = 4; public static Timer hashedWheelTimer = new HashedWheelTimer(); + public static void write(Channel channel, ByteBuffer body, ChannelFutureListener channelFutureListner) { + byte[] header = ByteBuffer.allocate(HEADER_LENGTH).order(ByteOrder.BIG_ENDIAN).putInt(body.limit()).array(); + List components = new ArrayList(2); + components.add(ChannelBuffers.wrappedBuffer(ByteOrder.BIG_ENDIAN, header)); + components.add(ChannelBuffers.wrappedBuffer(body)); + + if (channelFutureListner == null) { + Channels.write(channel, new CompositeChannelBuffer(ByteOrder.BIG_ENDIAN, components)); + } else { + Channels.write(channel, new CompositeChannelBuffer(ByteOrder.BIG_ENDIAN, components)) + .addListener(channelFutureListner); + } + } + public static void write(Channel channel, byte[] body, ChannelFutureListener channelFutureListner) { byte[] header = ByteBuffer.allocate(HEADER_LENGTH).order(ByteOrder.BIG_ENDIAN).putInt(body.length).array(); if (channelFutureListner == null) { diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index 8b94d452..11bba043 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -73,7 +73,7 @@ public class SessionHandler extends SimpleChannelHandler { } embeddedServer.subscribe(clientIdentity); - ctx.setAttachment(clientIdentity);// 设置状态数据 + // ctx.setAttachment(clientIdentity);// 设置状态数据 NettyUtils.ack(ctx.getChannel(), null); } else { NettyUtils.error(401, @@ -146,7 +146,19 @@ public class SessionHandler extends SimpleChannelHandler { size += com.google.protobuf.CodedOutputStream.computeTagSize(5) + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + messageSize; - // TODO recyle bytes[] + // recyle bytes + // ByteBuffer byteBuffer = (ByteBuffer) + // ctx.getAttachment(); + // if (byteBuffer != null && size <= + // byteBuffer.capacity()) { + // byteBuffer.clear(); + // } else { + // byteBuffer = + // ByteBuffer.allocate(size).order(ByteOrder.BIG_ENDIAN); + // ctx.setAttachment(byteBuffer); + // } + // CodedOutputStream output = + // CodedOutputStream.newInstance(byteBuffer); byte[] body = new byte[size]; CodedOutputStream output = CodedOutputStream.newInstance(body); output.writeEnum(3, PacketType.MESSAGES.getNumber()); @@ -160,6 +172,11 @@ public class SessionHandler extends SimpleChannelHandler { } output.checkNoSpaceLeft(); NettyUtils.write(ctx.getChannel(), body, null); + + // output.flush(); + // byteBuffer.flip(); + // NettyUtils.write(ctx.getChannel(), byteBuffer, + // null); } else { Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); packetBuilder.setType(PacketType.MESSAGES); From 617f9bbc624b11ebbb179d6ddecc73c37ba219a6 Mon Sep 17 00:00:00 2001 From: CAI1987 <41417665+CAI1987@users.noreply.github.com> Date: Thu, 19 Jul 2018 19:24:37 +0800 Subject: [PATCH 013/360] =?UTF-8?q?=E6=B7=BB=E5=8A=A0Canal=E5=95=86?= =?UTF-8?q?=E4=B8=9A=E5=8C=96=E7=89=88=E6=9C=AC=E6=9B=B4=E6=96=B0=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 456bd2db..e90f7869 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@
  1. canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
  2. canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
  3. +
  4. Canal已在阿里云推出商业化版本 https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E

背景

From b9156936ae98085204e8eea0005931598c143161 Mon Sep 17 00:00:00 2001 From: CAI1987 <41417665+CAI1987@users.noreply.github.com> Date: Thu, 19 Jul 2018 20:12:03 +0800 Subject: [PATCH 014/360] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=95=86=E4=B8=9A?= =?UTF-8?q?=E7=89=88=E6=9C=AC=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index e90f7869..736e238e 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,15 @@ See the wiki page for : wiki文
  • 阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):http://github.com/alibaba/yugong
  • +

    相关产品

    +
      +
    1. 阿里云分布式数据库DRDS阿里云分布式数据库DRDS
    2. +
    3. 阿里云数据传输服务DTS阿里云数据传输服务DTS
    4. +
    +
  • 阿里云数据库备份服务DBS
  • +
  • 阿里云数据管理服务DMS
  • + +

    问题反馈

    1. qq交流群: 161559791
    2. From b880d96a862c38965ce26f8766f28806aea2c1fa Mon Sep 17 00:00:00 2001 From: CAI1987 <41417665+CAI1987@users.noreply.github.com> Date: Thu, 19 Jul 2018 20:13:01 +0800 Subject: [PATCH 015/360] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=95=86=E4=B8=9A?= =?UTF-8?q?=E5=8C=96=E7=89=88=E6=9C=AC=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 736e238e..1ddbd9c7 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@
      1. canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
      2. canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
      3. -
      4. Canal已在阿里云推出商业化版本 https://www.aliyun.com/product/dts?spm=a2c4g.11186623.cloudEssentials.80.srdwr7, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。https://help.aliyun.com/document_detail/26592.html?spm=a2c4g.11174283.6.539.t1Y91E
      5. +
      6. Canal已在阿里云推出商业化版本 数据传输服务DTS, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。DTS产品使用文档

      背景

      From b9d7f0b6d0bd9a2f6b6c7541e427bc0d6503ced5 Mon Sep 17 00:00:00 2001 From: CAI1987 <41417665+CAI1987@users.noreply.github.com> Date: Thu, 19 Jul 2018 20:14:19 +0800 Subject: [PATCH 016/360] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1ddbd9c7..58b4cda0 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ See the wiki page for : wiki文

      相关产品

        -
      1. 阿里云分布式数据库DRDS阿里云分布式数据库DRDS
      2. -
      3. 阿里云数据传输服务DTS阿里云数据传输服务DTS
      4. +
      5. 阿里云分布式数据库DRDS
      6. +
      7. 阿里云数据传输服务DTS
    3. 阿里云数据库备份服务DBS
    4. 阿里云数据管理服务DMS
    5. From 67f2e55d38c50686bb1ba7a68e7c6181617d5c46 Mon Sep 17 00:00:00 2001 From: CAI1987 <41417665+CAI1987@users.noreply.github.com> Date: Thu, 19 Jul 2018 20:14:49 +0800 Subject: [PATCH 017/360] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 58b4cda0..9e596f07 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ See the wiki page for : wiki文
      1. 阿里云分布式数据库DRDS
      2. 阿里云数据传输服务DTS
      3. -
    6. 阿里云数据库备份服务DBS
    7. 阿里云数据管理服务DMS
    From fe38bc16d2c95c4367e23fe06ceb628f20906b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 20 Jul 2018 14:32:13 +0800 Subject: [PATCH 018/360] =?UTF-8?q?fixed=20issue=20#726=20,=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E5=AE=8C=E6=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dbsync/binlog/DirectLogFetcherTest.java | 93 ++++++++----------- .../driver/mysql/socket/BioSocketChannel.java | 4 +- .../mysql/socket/BioSocketChannelPool.java | 2 + .../parse/inbound/mysql/MysqlDumpTest.java | 3 + 4 files changed, 48 insertions(+), 54 deletions(-) diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java index 23a10f01..78d5b586 100644 --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java @@ -8,15 +8,6 @@ import java.sql.Statement; import org.junit.Assert; import org.junit.Test; -import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.XidLogEvent; -import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; - public class DirectLogFetcherTest extends BaseLogFetcherTest { @Test @@ -24,56 +15,54 @@ public class DirectLogFetcherTest extends BaseLogFetcherTest { DirectLogFetcher fecther = new DirectLogFetcher(); try { Class.forName("com.mysql.jdbc.Driver"); - Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello"); + Connection connection = DriverManager.getConnection("jdbc:mysql://100.81.154.142:3306", "root", "hello"); Statement statement = connection.createStatement(); statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'"); statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'"); - fecther.open(connection, "mysql-bin.000001", 4L, 2); + fecther.open(connection, "mysql-bin.000006", 120L, 2); - LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.UNKNOWN_EVENT); LogContext context = new LogContext(); while (fecther.fetch()) { - LogEvent event = null; - event = decoder.decode(fecther, context); - - if (event == null) { - continue; - // throw new RuntimeException("parse failed"); - } - - int eventType = event.getHeader().getType(); - switch (eventType) { - case LogEvent.ROTATE_EVENT: - binlogFileName = ((RotateLogEvent) event).getFilename(); - break; - case LogEvent.WRITE_ROWS_EVENT_V1: - case LogEvent.WRITE_ROWS_EVENT: - parseRowsEvent((WriteRowsLogEvent) event); - break; - case LogEvent.UPDATE_ROWS_EVENT_V1: - case LogEvent.UPDATE_ROWS_EVENT: - parseRowsEvent((UpdateRowsLogEvent) event); - break; - case LogEvent.DELETE_ROWS_EVENT_V1: - case LogEvent.DELETE_ROWS_EVENT: - parseRowsEvent((DeleteRowsLogEvent) event); - break; - case LogEvent.QUERY_EVENT: - parseQueryEvent((QueryLogEvent) event); - break; - case LogEvent.ROWS_QUERY_LOG_EVENT: - parseRowsQueryEvent((RowsQueryLogEvent) event); - break; - case LogEvent.ANNOTATE_ROWS_EVENT: - parseAnnotateRowsEvent((AnnotateRowsEvent) event); - break; - case LogEvent.XID_EVENT: - parseXidEvent((XidLogEvent) event); - break; - default: - break; - } + decoder.decode(fecther, context); + continue; + // if (event == null) { + // continue; + // } + // + // int eventType = event.getHeader().getType(); + // switch (eventType) { + // case LogEvent.ROTATE_EVENT: + // binlogFileName = ((RotateLogEvent) event).getFilename(); + // break; + // case LogEvent.WRITE_ROWS_EVENT_V1: + // case LogEvent.WRITE_ROWS_EVENT: + // parseRowsEvent((WriteRowsLogEvent) event); + // break; + // case LogEvent.UPDATE_ROWS_EVENT_V1: + // case LogEvent.UPDATE_ROWS_EVENT: + // parseRowsEvent((UpdateRowsLogEvent) event); + // break; + // case LogEvent.DELETE_ROWS_EVENT_V1: + // case LogEvent.DELETE_ROWS_EVENT: + // parseRowsEvent((DeleteRowsLogEvent) event); + // break; + // case LogEvent.QUERY_EVENT: + // parseQueryEvent((QueryLogEvent) event); + // break; + // case LogEvent.ROWS_QUERY_LOG_EVENT: + // parseRowsQueryEvent((RowsQueryLogEvent) event); + // break; + // case LogEvent.ANNOTATE_ROWS_EVENT: + // parseAnnotateRowsEvent((AnnotateRowsEvent) event); + // break; + // case LogEvent.XID_EVENT: + // parseXidEvent((XidLogEvent) event); + // break; + // default: + // break; + // } } } catch (Exception e) { e.printStackTrace(); diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java index 2ed318e8..f746189e 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.parse.driver.mysql.socket; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -24,7 +25,7 @@ public class BioSocketChannel implements SocketChannel { BioSocketChannel(Socket socket) throws IOException{ this.socket = socket; - this.input = socket.getInputStream(); + this.input = new BufferedInputStream(socket.getInputStream(), 16384); this.output = socket.getOutputStream(); } @@ -164,5 +165,4 @@ public class BioSocketChannel implements SocketChannel { this.socket = null; } - } diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java index dcc17bad..a268ff2e 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java @@ -16,6 +16,8 @@ public abstract class BioSocketChannelPool { socket.setKeepAlive(true); socket.setReuseAddress(true); socket.connect(address, BioSocketChannel.DEFAULT_CONNECT_TIMEOUT); + System.out.println(socket.getReceiveBufferSize()); + System.out.println(socket.getSendBufferSize()); return new BioSocketChannel(socket); } diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java index 6849c5b3..e4d81823 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java @@ -41,6 +41,9 @@ public class MysqlDumpTest { controller.setTsdbSpringXml("classpath:tsdb/h2-tsdb.xml"); controller.setEventFilter(new AviaterRegexFilter("test\\..*")); controller.setEventBlackFilter(new AviaterRegexFilter("canal_tsdb\\..*")); + controller.setParallel(true); + controller.setParallelBufferSize(4096); + controller.setParallelThreadSize(16); controller.setIsGTIDMode(false); controller.setEventSink(new AbstractCanalEventSinkTest>() { From 77c1c65b83a13ee416009d028e18555ea4b4a032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 20 Jul 2018 14:32:51 +0800 Subject: [PATCH 019/360] =?UTF-8?q?fixed=20issue=20#726=20,=20=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tddl/dbsync/FetcherPerformanceTest.java | 57 ++++++ .../SimpleCanalClientPermanceTest.java | 68 +++++++ .../parse/MysqlBinlogDumpPerformanceTest.java | 89 ++++++++ .../MysqlBinlogEventPerformanceTest.java | 82 ++++++++ .../MysqlBinlogParsePerformanceTest.java | 192 ++++++++++++++++++ 5 files changed, 488 insertions(+) create mode 100644 dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java new file mode 100644 index 00000000..03fedccd --- /dev/null +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java @@ -0,0 +1,57 @@ +package com.taobao.tddl.dbsync; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +import java.util.concurrent.atomic.AtomicLong; + +import com.taobao.tddl.dbsync.binlog.DirectLogFetcher; +import com.taobao.tddl.dbsync.binlog.LogContext; +import com.taobao.tddl.dbsync.binlog.LogDecoder; +import com.taobao.tddl.dbsync.binlog.LogEvent; + +public class FetcherPerformanceTest { + + public static void main(String args[]) { + DirectLogFetcher fetcher = new DirectLogFetcher(); + try { + Class.forName("com.mysql.jdbc.Driver"); + Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", + "root", + "hello"); + Statement statement = connection.createStatement(); + statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'"); + statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'"); + + fetcher.open(connection, "mysql-bin.000006", 120L, 2); + + AtomicLong sum = new AtomicLong(0); + long start = System.currentTimeMillis(); + long last = 0; + long end = 0; + + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogContext context = new LogContext(); + while (fetcher.fetch()) { + decoder.decode(fetcher, context); + sum.incrementAndGet(); + long current = sum.get(); + if (current - last >= 100000) { + end = System.currentTimeMillis(); + long tps = ((current - last) * 1000) / (end - start); + System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps); + last = current; + start = end; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + fetcher.close(); + } catch (IOException e) { + } + } + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java b/example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java new file mode 100644 index 00000000..c58eb869 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java @@ -0,0 +1,68 @@ +package com.alibaba.otter.canal.example; +import java.net.InetSocketAddress; +import java.util.concurrent.ArrayBlockingQueue; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; +import com.alibaba.otter.canal.protocol.Message; + +public class SimpleCanalClientPermanceTest { + + public static void main(String args[]) { + String destination = "example"; + String ip = "127.0.0.1"; + int batchSize = 1024; + int count = 0; + int sum = 0; + int perSum = 0; + long start = System.currentTimeMillis(); + long end = 0; + final ArrayBlockingQueue queue = new ArrayBlockingQueue(100); + try { + final CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111), + destination, + "", + ""); + + Thread ackThread = new Thread(new Runnable() { + + @Override + public void run() { + while (true) { + try { + long batchId = queue.take(); + connector.ack(batchId); + } catch (InterruptedException e) { + } + } + } + }); + ackThread.start(); + + ((SimpleCanalConnector) connector).setLazyParseEntry(true); + connector.connect(); + connector.subscribe(); + while (true) { + Message message = connector.getWithoutAck(batchSize); + long batchId = message.getId(); + int size = message.getRawEntries().size(); + sum += size; + perSum += size; + count++; + queue.add(batchId); + if (count % 10 == 0) { + end = System.currentTimeMillis(); + long tps = (perSum * 1000) / (end - start); + System.out.println(" total : " + sum + " , current : " + perSum + " , cost : " + (end - start) + + " , tps : " + tps); + start = end; + perSum = 0; + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java new file mode 100644 index 00000000..8ca9b044 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java @@ -0,0 +1,89 @@ +package com.alibaba.otter.canal.parse; + +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; +import com.alibaba.otter.canal.parse.exception.CanalParseException; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; +import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager; +import com.alibaba.otter.canal.parse.support.AuthenticationInfo; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import com.alibaba.otter.canal.protocol.position.LogPosition; +import com.alibaba.otter.canal.sink.CanalEventSink; +import com.alibaba.otter.canal.sink.exception.CanalSinkException; + +public class MysqlBinlogDumpPerformanceTest { + + public static void main(String args[]) { + final MysqlEventParser controller = new MysqlEventParser(); + final EntryPosition startPosition = new EntryPosition("mysql-bin.001699", 120L, 100L); + controller.setConnectionCharset(Charset.forName("UTF-8")); + controller.setSlaveId(3344L); + controller.setDetectingEnable(false); + controller.setFilterQueryDml(true); + controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3328), "root", "hello")); + controller.setMasterPosition(startPosition); + controller.setEnableTsdb(false); + controller.setDestination("example"); + controller.setTsdbSpringXml("classpath:spring/tsdb/h2-tsdb.xml"); + // controller.setEventFilter(new AviaterRegexFilter("test\\..*")); + // controller.setEventBlackFilter(new + // AviaterRegexFilter("canal_tsdb\\..*")); + controller.setParallel(true); + controller.setParallelBufferSize(256); + controller.setParallelThreadSize(16); + controller.setIsGTIDMode(false); + final AtomicLong sum = new AtomicLong(0); + final AtomicLong last = new AtomicLong(0); + final AtomicLong start = new AtomicLong(System.currentTimeMillis()); + final AtomicLong end = new AtomicLong(0); + controller.setEventSink(new AbstractCanalEventSinkTest>() { + + public boolean sink(List entrys, InetSocketAddress remoteAddress, String destination) + throws CanalSinkException, + InterruptedException { + + sum.addAndGet(entrys.size()); + long current = sum.get(); + if (current - last.get() >= 100000) { + end.set(System.currentTimeMillis()); + long tps = ((current - last.get()) * 1000) / (end.get() - start.get()); + System.out.println(" total : " + sum + " , cost : " + (end.get() - start.get()) + " , tps : " + tps); + last.set(current); + start.set(end.get()); + } + return true; + } + + }); + controller.setLogPositionManager(new AbstractLogPositionManager() { + + @Override + public LogPosition getLatestIndexBy(String destination) { + return null; + } + + @Override + public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException { + } + }); + + controller.start(); + + try { + Thread.sleep(100 * 1000 * 1000L); + } catch (InterruptedException e) { + } + controller.stop(); + } + + public static abstract class AbstractCanalEventSinkTest extends AbstractCanalLifeCycle implements CanalEventSink { + + public void interrupt() { + } + } +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java new file mode 100644 index 00000000..e92fecb5 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java @@ -0,0 +1,82 @@ +package com.alibaba.otter.canal.parse; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicLong; + +import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector; +import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor; +import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket; +import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket; +import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager; +import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher; +import com.taobao.tddl.dbsync.binlog.LogContext; +import com.taobao.tddl.dbsync.binlog.LogDecoder; +import com.taobao.tddl.dbsync.binlog.LogEvent; + +public class MysqlBinlogEventPerformanceTest { + + protected static Charset charset = Charset.forName("utf-8"); + + public static void main(String args[]) { + DirectLogFetcher fetcher = new DirectLogFetcher(); + try { + MysqlConnector connector = new MysqlConnector(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + connector.connect(); + updateSettings(connector); + sendBinlogDump(connector, "mysql-bin.000006", 120L, 3); + fetcher.start(connector.getChannel()); + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogContext context = new LogContext(); + AtomicLong sum = new AtomicLong(0); + long start = System.currentTimeMillis(); + long last = 0; + long end = 0; + while (fetcher.fetch()) { + decoder.decode(fetcher, context); + sum.incrementAndGet(); + long current = sum.get(); + if (current - last >= 100000) { + end = System.currentTimeMillis(); + long tps = ((current - last) * 1000) / (end - start); + System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps); + last = current; + start = end; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + fetcher.close(); + } catch (IOException e) { + } + } + } + + private static void sendBinlogDump(MysqlConnector connector, String binlogfilename, Long binlogPosition, int slaveId) + throws IOException { + BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket(); + binlogDumpCmd.binlogFileName = binlogfilename; + binlogDumpCmd.binlogPosition = binlogPosition; + binlogDumpCmd.slaveServerId = slaveId; + byte[] cmdBody = binlogDumpCmd.toBytes(); + + HeaderPacket binlogDumpHeader = new HeaderPacket(); + binlogDumpHeader.setPacketBodyLength(cmdBody.length); + binlogDumpHeader.setPacketSequenceNumber((byte) 0x00); + PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody); + } + + private static void updateSettings(MysqlConnector connector) throws IOException { + update("set @master_binlog_checksum= '@@global.binlog_checksum'", connector); + update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'", connector); + } + + public static void update(String cmd, MysqlConnector connector) throws IOException { + MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector); + exector.update(cmd); + } + +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java new file mode 100644 index 00000000..79b88375 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java @@ -0,0 +1,192 @@ +package com.alibaba.otter.canal.parse; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.BitSet; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicLong; + +import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector; +import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor; +import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket; +import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket; +import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager; +import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher; +import com.taobao.tddl.dbsync.binlog.LogBuffer; +import com.taobao.tddl.dbsync.binlog.LogContext; +import com.taobao.tddl.dbsync.binlog.LogDecoder; +import com.taobao.tddl.dbsync.binlog.LogEvent; +import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer; +import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent.ColumnInfo; +import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; + +public class MysqlBinlogParsePerformanceTest { + + protected static Charset charset = Charset.forName("utf-8"); + + public static void main(String args[]) { + DirectLogFetcher fetcher = new DirectLogFetcher(); + try { + MysqlConnector connector = new MysqlConnector(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + connector.connect(); + updateSettings(connector); + sendBinlogDump(connector, "mysql-bin.000006", 120L, 3); + fetcher.start(connector.getChannel()); + final BlockingQueue buffer = new ArrayBlockingQueue(1024); + Thread thread = new Thread(new Runnable() { + + @Override + public void run() { + try { + consumer(buffer); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + thread.start(); + + while (fetcher.fetch()) { + buffer.put(fetcher.duplicate()); + fetcher.consume(fetcher.limit()); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + fetcher.close(); + } catch (IOException e) { + } + } + } + + public static void consumer(BlockingQueue buffer) throws IOException, InterruptedException { + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogContext context = new LogContext(); + + AtomicLong sum = new AtomicLong(0); + long start = System.currentTimeMillis(); + long last = 0; + long end = 0; + while (true) { + LogEvent event = null; + event = decoder.decode(buffer.take(), context); + int eventType = event.getHeader().getType(); + switch (eventType) { + case LogEvent.ROTATE_EVENT: + break; + case LogEvent.WRITE_ROWS_EVENT_V1: + case LogEvent.WRITE_ROWS_EVENT: + parseRowsEvent((WriteRowsLogEvent) event, sum); + break; + case LogEvent.UPDATE_ROWS_EVENT_V1: + case LogEvent.UPDATE_ROWS_EVENT: + parseRowsEvent((UpdateRowsLogEvent) event, sum); + break; + case LogEvent.DELETE_ROWS_EVENT_V1: + case LogEvent.DELETE_ROWS_EVENT: + parseRowsEvent((DeleteRowsLogEvent) event, sum); + break; + case LogEvent.XID_EVENT: + sum.incrementAndGet(); + break; + case LogEvent.QUERY_EVENT: + sum.incrementAndGet(); + break; + default: + break; + } + + long current = sum.get(); + if (current - last >= 100000) { + end = System.currentTimeMillis(); + long tps = ((current - last) * 1000) / (end - start); + System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps); + last = current; + start = end; + } + } + } + + private static void sendBinlogDump(MysqlConnector connector, String binlogfilename, Long binlogPosition, int slaveId) + throws IOException { + BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket(); + binlogDumpCmd.binlogFileName = binlogfilename; + binlogDumpCmd.binlogPosition = binlogPosition; + binlogDumpCmd.slaveServerId = slaveId; + byte[] cmdBody = binlogDumpCmd.toBytes(); + + HeaderPacket binlogDumpHeader = new HeaderPacket(); + binlogDumpHeader.setPacketBodyLength(cmdBody.length); + binlogDumpHeader.setPacketSequenceNumber((byte) 0x00); + PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody); + } + + private static void updateSettings(MysqlConnector connector) throws IOException { + update("set @master_binlog_checksum= '@@global.binlog_checksum'", connector); + update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'", connector); + } + + public static void update(String cmd, MysqlConnector connector) throws IOException { + MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector); + exector.update(cmd); + } + + public static void parseRowsEvent(RowsLogEvent event, AtomicLong sum) { + try { + RowsLogBuffer buffer = event.getRowsBuf(charset.name()); + BitSet columns = event.getColumns(); + BitSet changeColumns = event.getChangeColumns(); + while (buffer.nextOneRow(columns)) { + int type = event.getHeader().getType(); + if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) { + parseOneRow(event, buffer, columns, true); + } else if (LogEvent.DELETE_ROWS_EVENT_V1 == type || LogEvent.DELETE_ROWS_EVENT == type) { + parseOneRow(event, buffer, columns, false); + } else { + parseOneRow(event, buffer, columns, false); + if (!buffer.nextOneRow(changeColumns)) { + break; + } + parseOneRow(event, buffer, changeColumns, true); + } + + sum.incrementAndGet(); + } + } catch (Exception e) { + throw new RuntimeException("parse row data failed.", e); + } + } + + public static void parseOneRow(RowsLogEvent event, RowsLogBuffer buffer, BitSet cols, boolean isAfter) + throws UnsupportedEncodingException { + TableMapLogEvent map = event.getTable(); + if (map == null) { + throw new RuntimeException("not found TableMap with tid=" + event.getTableId()); + } + + final int columnCnt = map.getColumnCnt(); + final ColumnInfo[] columnInfo = map.getColumnInfo(); + for (int i = 0; i < columnCnt; i++) { + if (!cols.get(i)) { + continue; + } + + ColumnInfo info = columnInfo[i]; + buffer.nextValue(info.type, info.meta); + if (buffer.isNull()) { + } else { + buffer.getValue(); + } + } + } +} From d520260fe891396cc73d0ece48cc662e5c1ca6b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 20 Jul 2018 14:46:52 +0800 Subject: [PATCH 020/360] remove unused --- .../java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java index 03fedccd..f5af66e3 100644 --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java @@ -7,8 +7,6 @@ import java.sql.Statement; import java.util.concurrent.atomic.AtomicLong; import com.taobao.tddl.dbsync.binlog.DirectLogFetcher; -import com.taobao.tddl.dbsync.binlog.LogContext; -import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogEvent; public class FetcherPerformanceTest { @@ -31,10 +29,7 @@ public class FetcherPerformanceTest { long last = 0; long end = 0; - LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); - LogContext context = new LogContext(); while (fetcher.fetch()) { - decoder.decode(fetcher, context); sum.incrementAndGet(); long current = sum.get(); if (current - last >= 100000) { From 7b52568575b03b0791ef05aadbbf5dff9ee8c93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 20 Jul 2018 16:43:00 +0800 Subject: [PATCH 021/360] fixed unused code --- .../canal/parse/driver/mysql/socket/BioSocketChannelPool.java | 2 -- .../canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java | 3 --- 2 files changed, 5 deletions(-) diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java index a268ff2e..dcc17bad 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannelPool.java @@ -16,8 +16,6 @@ public abstract class BioSocketChannelPool { socket.setKeepAlive(true); socket.setReuseAddress(true); socket.connect(address, BioSocketChannel.DEFAULT_CONNECT_TIMEOUT); - System.out.println(socket.getReceiveBufferSize()); - System.out.println(socket.getSendBufferSize()); return new BioSocketChannel(socket); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java index a85962cc..222d762a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java @@ -64,9 +64,6 @@ public class DirectLogFetcher extends LogFetcher { if ("1".equals(dbsemi)) { issemi = true; } - // 和mysql driver一样,提供buffer机制,提升读取binlog速度 - // this.input = new - // BufferedInputStream(channel.socket().getInputStream(), 16384); } /** From 89eb3d60785b2b91e42f8984ec8d6a9db3cacbf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 20 Jul 2018 16:45:09 +0800 Subject: [PATCH 022/360] fixed test code --- .../otter/canal/parse/inbound/mysql/MysqlDumpTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java index e4d81823..6c11c637 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java @@ -42,8 +42,8 @@ public class MysqlDumpTest { controller.setEventFilter(new AviaterRegexFilter("test\\..*")); controller.setEventBlackFilter(new AviaterRegexFilter("canal_tsdb\\..*")); controller.setParallel(true); - controller.setParallelBufferSize(4096); - controller.setParallelThreadSize(16); + controller.setParallelBufferSize(256); + controller.setParallelThreadSize(2); controller.setIsGTIDMode(false); controller.setEventSink(new AbstractCanalEventSinkTest>() { From 20807e58c8f189946bdd26a6ea48ac4233f72c18 Mon Sep 17 00:00:00 2001 From: CAI1987 <41417665+CAI1987@users.noreply.github.com> Date: Mon, 23 Jul 2018 15:39:23 +0800 Subject: [PATCH 023/360] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9e596f07..86ef8755 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@
  • canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
  • canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
  • Canal已在阿里云推出商业化版本 数据传输服务DTS, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。DTS产品使用文档
  • +DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,立即体验>>>

    背景

    From aaf18ccd7e94150c89e49e2b16f875642818ca29 Mon Sep 17 00:00:00 2001 From: winger Date: Wed, 25 Jul 2018 10:18:15 +0800 Subject: [PATCH 024/360] fix issue #771 #756 --- .../driver/mysql/packets/client/BinlogDumpCommandPacket.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java index 70624549..f0fba11a 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java @@ -54,7 +54,7 @@ public class BinlogDumpCommandPacket extends CommandPacket { // 1. write 4 bytes bin-log position to start at ByteHelper.writeUnsignedIntLittleEndian(binlogPosition, out); // 2. write 2 bytes bin-log flags - int binlog_flags = BINLOG_DUMP_NON_BLOCK; + int binlog_flags = 0; binlog_flags |= BINLOG_SEND_ANNOTATE_ROWS_EVENT; out.write(binlog_flags); out.write(0x00); From ccd1e52411809a796dccc13fd5b91f3557d9a1e7 Mon Sep 17 00:00:00 2001 From: winger Date: Wed, 25 Jul 2018 16:38:38 +0800 Subject: [PATCH 025/360] fix issue #771 #776 756 --- .../otter/canal/parse/inbound/AbstractEventParser.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index c8cdb1bd..7f7d39ac 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -303,7 +303,12 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle transactionBuffer.reset();// 重置一下缓冲队列,重新记录数据 binlogParser.reset();// 重新置位 if (multiStageCoprocessor != null) { - multiStageCoprocessor.reset(); + // 处理 RejectedExecutionException + try { + multiStageCoprocessor.reset(); + } catch (Throwable t) { + logger.debug("multi processor rejected:", t); + } } if (running) { From e608e1d6012e2cf5bfecaaf068a730633dac8a70 Mon Sep 17 00:00:00 2001 From: winger Date: Thu, 26 Jul 2018 12:33:10 +0800 Subject: [PATCH 026/360] fix bug #776 --- .../com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java index b645e954..5c2678b5 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java @@ -44,13 +44,14 @@ public class GtidLogEvent extends LogEvent { gno = buffer.getLong64(); // support gtid lastCommitted and sequenceNumber - // 42 = 1+16+8+1+8+8 - if (buffer.capacity() > 42 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) { + // fix bug #776 + if (buffer.hasRemaining() && buffer.remaining() > 16 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) { lastCommitted = buffer.getLong64(); sequenceNumber = buffer.getLong64(); } + // ignore gtid info read // sid.copy_from((uchar *)ptr_buffer); // ptr_buffer+= ENCODED_SID_LENGTH; From 129051a56e950c84f1cf9673a5e1b6cc3c45538c Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Thu, 26 Jul 2018 14:30:42 +0800 Subject: [PATCH 027/360] Further improvement for issue #771. --- .../mysql/MysqlMultiStageCoprocessor.java | 48 ++++++++++--------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 65904b34..fb3539a6 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -4,6 +4,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.LockSupport; +import com.alibaba.otter.canal.common.CanalException; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; @@ -52,17 +53,17 @@ import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; */ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor { - private LogEventConvert logEventConvert; - private EventTransactionBuffer transactionBuffer; - private ErosaConnection connection; + private LogEventConvert logEventConvert; + private EventTransactionBuffer transactionBuffer; + private ErosaConnection connection; - private int parserThreadCount; - private int ringBufferSize; - private RingBuffer disruptorMsgBuffer; - private ExecutorService parserExecutor; - private ExecutorService stageExecutor; - private String destination; - private CanalParseException exception; + private int parserThreadCount; + private int ringBufferSize; + private RingBuffer disruptorMsgBuffer; + private ExecutorService parserExecutor; + private ExecutorService stageExecutor; + private String destination; + private volatile CanalParseException exception; public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, EventTransactionBuffer transactionBuffer, String destination){ @@ -147,11 +148,14 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement public boolean publish(LogBuffer buffer, String binlogFileName) { if (!isStart()) { - if (exception != null) { - throw exception; - } return false; } + /* 由于改为processor退出不stop,那么需要由exception标识coprocessor是否正常工作。 + * 让dump线程能够及时感知 + */ + if (exception != null) { + throw exception; + } boolean interupted = false; do { @@ -253,9 +257,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - if (isStart()) { - stop(); - } + } } @@ -291,9 +293,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - if (isStart()) { - stop(); - } + } } @@ -332,9 +332,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - if (isStart()) { - stop(); - } + } } @@ -401,7 +399,11 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void handleEventException(final Throwable ex, final long sequence, final Object event) { - throw new RuntimeException(ex); + /*如果仅仅抛出去,onShutdown会调用stop(),与instance主parse线程的reset()产生竞争,导致向terminated ExecutorService提交新task + * #771场景中的异常是InterruptedException,由shutdownNow产生。 + */ + exception = new CanalParseException(ex); + throw exception; } @Override From 72b907a75f78b3adae78dba42f16b5c1677cdab9 Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Thu, 26 Jul 2018 14:30:42 +0800 Subject: [PATCH 028/360] Further improvement for issue #771. --- .../mysql/MysqlMultiStageCoprocessor.java | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index fb3539a6..71dc7ca9 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -64,6 +64,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement private ExecutorService stageExecutor; private String destination; private volatile CanalParseException exception; + private volatile RuntimeException incident; public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, EventTransactionBuffer transactionBuffer, String destination){ @@ -78,6 +79,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement public void start() { super.start(); this.exception = null; + this.incident = null; this.disruptorMsgBuffer = RingBuffer.createSingleProducer(new MessageEventFactory(), ringBufferSize, new BlockingWaitStrategy()); @@ -148,13 +150,17 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement public boolean publish(LogBuffer buffer, String binlogFileName) { if (!isStart()) { + if (exception != null) { + throw exception; + } return false; } - /* 由于改为processor退出不stop,那么需要由exception标识coprocessor是否正常工作。 + + /* 由于改为processor仅终止自身stage而不是stop,那么需要由incident标识coprocessor是否正常工作。 * 让dump线程能够及时感知 */ - if (exception != null) { - throw exception; + if (incident != null) { + throw incident; } boolean interupted = false; @@ -399,11 +405,12 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void handleEventException(final Throwable ex, final long sequence, final Object event) { - /*如果仅仅抛出去,onShutdown会调用stop(),与instance主parse线程的reset()产生竞争,导致向terminated ExecutorService提交新task + /* + * 若异常不是onEvent body里产生的。如果仅仅抛出去,onShutdown会调用stop(),与dump线程的reset()产生竞争,导致向terminated ExecutorService提交新task * #771场景中的异常是InterruptedException,由shutdownNow产生。 */ - exception = new CanalParseException(ex); - throw exception; + incident = new RuntimeException(ex); + throw incident; } @Override From 8cfb70e0809ba12fa5bc9f0131e4412064d8b1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 26 Jul 2018 16:05:33 +0800 Subject: [PATCH 029/360] =?UTF-8?q?fixed=20issue=20#782=20,=20=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E4=BA=86exception?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mysql/MysqlMultiStageCoprocessor.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 71dc7ca9..281a05f2 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -4,7 +4,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.LockSupport; -import com.alibaba.otter.canal.common.CanalException; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; @@ -64,7 +63,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement private ExecutorService stageExecutor; private String destination; private volatile CanalParseException exception; - private volatile RuntimeException incident; public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, EventTransactionBuffer transactionBuffer, String destination){ @@ -79,7 +77,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement public void start() { super.start(); this.exception = null; - this.incident = null; this.disruptorMsgBuffer = RingBuffer.createSingleProducer(new MessageEventFactory(), ringBufferSize, new BlockingWaitStrategy()); @@ -156,13 +153,13 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement return false; } - /* 由于改为processor仅终止自身stage而不是stop,那么需要由incident标识coprocessor是否正常工作。 + /** + * 由于改为processor仅终止自身stage而不是stop,那么需要由incident标识coprocessor是否正常工作。 * 让dump线程能够及时感知 */ - if (incident != null) { - throw incident; + if (exception != null) { + throw exception; } - boolean interupted = false; do { try { @@ -180,11 +177,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement interupted = Thread.interrupted(); } } while (!interupted && isStart()); - - if (exception != null) { - throw exception; - } - return isStart(); } @@ -405,12 +397,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void handleEventException(final Throwable ex, final long sequence, final Object event) { - /* - * 若异常不是onEvent body里产生的。如果仅仅抛出去,onShutdown会调用stop(),与dump线程的reset()产生竞争,导致向terminated ExecutorService提交新task - * #771场景中的异常是InterruptedException,由shutdownNow产生。 - */ - incident = new RuntimeException(ex); - throw incident; } @Override From c0a40499b0e478398bf838da8b72fa6b2b073bd7 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sun, 29 Jul 2018 14:55:38 +0800 Subject: [PATCH 030/360] Canal monitoring(prometheus module) snapshot version. --- deployer/pom.xml | 8 ++ deployer/src/main/bin/metrics_env.sh | 15 +++ deployer/src/main/bin/startup.sh | 7 +- .../example/AbstractCanalClientTest.java | 3 +- pom.xml | 3 +- prometheus/pom.xml | 67 +++++++++++ .../prometheus/CanalInstanceExports.java | 98 ++++++++++++++++ .../canal/prometheus/CanalServerExports.java | 21 ++++ .../canal/prometheus/PrometheusProvider.java | 15 +++ .../canal/prometheus/PrometheusService.java | 110 ++++++++++++++++++ .../impl/InboundThroughputAspect.java | 79 +++++++++++++ .../impl/InstanceMetaCollector.java | 64 ++++++++++ .../prometheus/impl/MemoryStoreCollector.java | 75 ++++++++++++ .../impl/OutboundThroughputAspect.java | 80 +++++++++++++ ...PrometheusCanalEventDownStreamHandler.java | 73 ++++++++++++ .../src/main/resources/META-INF/aop.xml | 11 ++ ...ibaba.otter.canal.spi.CanalMetricsProvider | 1 + .../embedded/CanalServerWithEmbedded.java | 59 +++++++--- .../otter/canal/spi/CanalMetricsProvider.java | 24 ++++ .../otter/canal/spi/CanalMetricsService.java | 42 +++++++ .../canal/spi/NopCanalMetricsService.java | 38 ++++++ 21 files changed, 876 insertions(+), 17 deletions(-) create mode 100644 deployer/src/main/bin/metrics_env.sh create mode 100644 prometheus/pom.xml create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java create mode 100644 prometheus/src/main/resources/META-INF/aop.xml create mode 100644 prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider create mode 100644 server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java create mode 100644 server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java create mode 100644 server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java diff --git a/deployer/pom.xml b/deployer/pom.xml index 5fa2efc8..75f05805 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -16,6 +16,14 @@ canal.server ${project.version}
    + + + + + + + + diff --git a/deployer/src/main/bin/metrics_env.sh b/deployer/src/main/bin/metrics_env.sh new file mode 100644 index 00000000..1085eae2 --- /dev/null +++ b/deployer/src/main/bin/metrics_env.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Additional line arg for current prometheus solution +case "`uname`" in +Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + ;; +*) + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +base=${bin_abs_path}/.. +if [ $(ls $base/lib/aspectjweaver*.jar | wc -l) -eq 1 ]; then + WEAVER=$(ls $base/lib/aspectjweaver*.jar) + METRICS_OPTS=" -javaagent:"${WEAVER}" " +fi diff --git a/deployer/src/main/bin/startup.sh b/deployer/src/main/bin/startup.sh index 5655c713..b87652c0 100644 --- a/deployer/src/main/bin/startup.sh +++ b/deployer/src/main/bin/startup.sh @@ -94,7 +94,12 @@ then echo LOG CONFIGURATION : $logback_configurationFile echo canal conf : $canal_conf echo CLASSPATH :$CLASSPATH - $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 & +# metrics support options +# if [ -x $base/bin/metrics_env.sh ]; then +# . $base/bin/metrics_env.sh +# echo METRICS_OPTS $METRICS_OPTS +# fi + $JAVA $JAVA_OPTS $METRICS_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 & echo $! > $base/bin/canal.pid echo "cd to $current_path for continue" diff --git a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java index 1b84463b..36979177 100644 --- a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java +++ b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.example; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; @@ -115,7 +116,7 @@ public class AbstractCanalClientTest { connector.connect(); connector.subscribe(); while (running) { - Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据 + Message message = connector.getWithoutAck(batchSize, 1000L, TimeUnit.MILLISECONDS); // 获取指定数量的数据 long batchId = message.getId(); int size = message.getEntries().size(); if (batchId == -1 || size == 0) { diff --git a/pom.xml b/pom.xml index 1e8123ef..8b824ab5 100644 --- a/pom.xml +++ b/pom.xml @@ -129,6 +129,7 @@ example kafka kafka-client + prometheus @@ -256,7 +257,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_520 + 2.0.0_preview_186 com.alibaba diff --git a/prometheus/pom.xml b/prometheus/pom.xml new file mode 100644 index 00000000..16cf8acf --- /dev/null +++ b/prometheus/pom.xml @@ -0,0 +1,67 @@ + + + + canal + com.alibaba.otter + 1.0.26-SNAPSHOT + + 4.0.0 + + com.alibaba.otter + canal.prometheus + 1.0.26-SNAPSHOT + canal prometheus module for otter ${project.version} + + + + org.aspectj + aspectjrt + 1.8.9 + + + org.aspectj + aspectjweaver + 1.8.9 + + + org.jctools + jctools-core + 2.1.2 + + + io.prometheus + simpleclient + 0.4.0 + + + io.prometheus + simpleclient_hotspot + 0.4.0 + + + io.prometheus + simpleclient_httpserver + 0.4.0 + + + io.prometheus + simpleclient_pushgateway + 0.4.0 + + + com.alibaba.otter + canal.instance.core + ${project.version} + provided + + + com.alibaba.otter + canal.server + ${project.version} + provided + + + + diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java new file mode 100644 index 00000000..8acf40b8 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java @@ -0,0 +1,98 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.prometheus.impl.InstanceMetaCollector; +import com.alibaba.otter.canal.prometheus.impl.MemoryStoreCollector; +import com.alibaba.otter.canal.prometheus.impl.PrometheusCanalEventDownStreamHandler; +import com.alibaba.otter.canal.sink.CanalEventSink; +import com.alibaba.otter.canal.sink.entry.EntryEventSink; +import com.alibaba.otter.canal.store.CanalStoreException; +import io.prometheus.client.Collector; +import io.prometheus.client.CollectorRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; + +/** + * @author Chuanyi Li + */ +public class CanalInstanceExports { + + private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class); + + public static final String[] labels = {"destination"}; + + public static final List labelList = Collections.singletonList(labels[0]); + + private final String destination; + + private Collector storeCollector; + + private Collector delayCollector; + + private Collector metaCollector; + + private CanalInstanceExports(CanalInstance instance) { + this.destination = instance.getDestination(); + initDelayGauge(instance); + initStoreCollector(instance); + initMetaCollector(instance); + } + + + + static CanalInstanceExports forInstance(CanalInstance instance) { + return new CanalInstanceExports(instance); + } + + void register() { + if (delayCollector != null) { + delayCollector.register(); + } + if (storeCollector != null) { + storeCollector.register(); + } + if (metaCollector != null) { + metaCollector.register(); + } + } + + void unregister() { + if (delayCollector != null) { + CollectorRegistry.defaultRegistry.unregister(delayCollector); + } + if (storeCollector != null) { + CollectorRegistry.defaultRegistry.unregister(storeCollector); + } + if (metaCollector != null) { + CollectorRegistry.defaultRegistry.unregister(metaCollector); + } + } + + private void initDelayGauge(CanalInstance instance) { + CanalEventSink sink = instance.getEventSink(); + if (sink instanceof EntryEventSink) { + EntryEventSink entryEventSink = (EntryEventSink) sink; + // TODO ensure not to add handler again + PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(destination); + entryEventSink.addHandler(handler); + delayCollector = handler.getCollector(); + } else { + logger.warn("This impl register metrics for only EntryEventSink, skip."); + } + } + + private void initStoreCollector(CanalInstance instance) { + try { + storeCollector = new MemoryStoreCollector(instance.getEventStore(), destination); + } catch (CanalStoreException cse) { + logger.warn("Failed to register metrics for destination {}.", destination, cse); + } + } + + private void initMetaCollector(CanalInstance instance) { + metaCollector = new InstanceMetaCollector(instance); + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java new file mode 100644 index 00000000..8eef79ba --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java @@ -0,0 +1,21 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.prometheus.impl.InboundThroughputAspect; +import com.alibaba.otter.canal.prometheus.impl.OutboundThroughputAspect; + +/** + * @author Chuanyi Li + */ +public class CanalServerExports { + + private static boolean initialized = false; + + public static synchronized void initialize() { + if (!initialized) { + InboundThroughputAspect.getCollector().register(); + OutboundThroughputAspect.getCollector().register(); + initialized = true; + } + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java new file mode 100644 index 00000000..12b8675f --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java @@ -0,0 +1,15 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.spi.CanalMetricsProvider; +import com.alibaba.otter.canal.spi.CanalMetricsService; + +/** + * @author Chuanyi Li + */ +public class PrometheusProvider implements CanalMetricsProvider { + + @Override + public CanalMetricsService getService() { + return PrometheusService.getInstance(); + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java new file mode 100644 index 00000000..c7942235 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -0,0 +1,110 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.spi.CanalMetricsService; +import io.prometheus.client.exporter.HTTPServer; +import io.prometheus.client.hotspot.DefaultExports; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Chuanyi Li + */ +public class PrometheusService implements CanalMetricsService { + + private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); + + private final Map exports = new ConcurrentHashMap(); + + private volatile boolean running = false; + + private HTTPServer server; + + private PrometheusService() { + } + + private static class SingletonHolder { + private static final PrometheusService SINGLETON = new PrometheusService(); + } + + public static PrometheusService getInstance() { + return SingletonHolder.SINGLETON; + } + + @Override + public void initialize() { + try { + //TODO 1.Configurable port + //TODO 2.Https + server = new HTTPServer(11112); + } catch (IOException e) { + logger.warn("Unable to start prometheus HTTPServer.", e); + return; + } + try { + // JVM exports + DefaultExports.initialize(); + // Canal server level exports + CanalServerExports.initialize(); + } catch (Throwable t) { + logger.warn("Unable to initialize server exports.", t); + } + + running = true; + } + + @Override + public void terminate() { + running = false; + // Normally, service should be terminated at canal shutdown. + // No need to unregister instance exports explicitly. + // But for the sake of safety, unregister them. + for (CanalInstanceExports ie : exports.values()) { + ie.unregister(); + } + if (server != null) { + server.stop(); + } + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public void register(CanalInstance instance) { + if (instance.isStart()) { + logger.warn("Cannot register metrics for destination {} that is running.", instance.getDestination()); + return; + } + try { + CanalInstanceExports export = CanalInstanceExports.forInstance(instance); + export.register(); + exports.put(instance.getDestination(), export); + } catch (Throwable t) { + logger.warn("Unable to register instance exports for {}.", instance.getDestination(), t); + } + logger.info("Register metrics for destination {}.", instance.getDestination()); + } + + @Override + public void unregister(CanalInstance instance) { + if (instance.isStart()) { + logger.warn("Try unregister metrics after destination {} is stopped.", instance.getDestination()); + } + try { + CanalInstanceExports export = exports.remove(instance.getDestination()); + if (export != null) { + export.unregister(); + } + } catch (Throwable t) { + logger.warn("Unable to unregister instance exports for {}.", instance.getDestination(), t); + } + logger.info("Unregister metrics for destination {}.", instance.getDestination()); + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java new file mode 100644 index 00000000..eabc256c --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java @@ -0,0 +1,79 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.jctools.maps.ConcurrentAutoTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Chuanyi Li + */ +@Aspect +public class InboundThroughputAspect { + + private static final Logger logger = LoggerFactory.getLogger(InboundThroughputAspect.class); + + /** + * Support highly scalable counters + * @see ConcurrentAutoTable + */ + private static final ConcurrentAutoTable total = new ConcurrentAutoTable(); + + private static final Collector collector = new InboundThroughputCollector(); + + public static Collector getCollector() { + return collector; + } + + @Pointcut("call(byte[] com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))") + public void read() {} + + @Pointcut("call(void com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..)) ") + public void readBytes() {} + + //nested read, just eliminate them. + @Pointcut("withincode(* com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))") + public void nestedCall() {} + + @After("read() && !nestedCall() && args(len, ..)") + public void recordRead(int len) { + accumulateBytes(len); + } + + @After("readBytes() && !nestedCall() && args(.., len, timeout)") + public void recordReadBytes(int len, int timeout) { + accumulateBytes(len); + } + + private void accumulateBytes(int count) { + try { + total.add(count); + } catch (Throwable t) { + //Catch every Throwable, rather than break the business logic. + logger.warn("Error while accumulate inbound bytes.", t); + } + } + + public static class InboundThroughputCollector extends Collector { + + private InboundThroughputCollector() {} + + @Override + public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily bytes = new CounterMetricFamily("canal_net_inbound_bytes", + "Total socket inbound bytes of canal server.", + total.get()); + mfs.add(bytes); + return mfs; + } + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java new file mode 100644 index 00000000..5562b8fd --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java @@ -0,0 +1,64 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring; +import com.alibaba.otter.canal.meta.CanalMetaManager; +import com.alibaba.otter.canal.prometheus.CanalInstanceExports; +import com.alibaba.otter.canal.protocol.ClientIdentity; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Chuanyi Li + */ +public class InstanceMetaCollector extends Collector { + + private static final List InfoLabel = Arrays.asList("destination", "mode"); + + private CanalMetaManager metaManager; + + private final String destination; + + private final String mode; + + private final String subsHelp; + + public InstanceMetaCollector(CanalInstance instance) { + if (instance == null) { + throw new IllegalArgumentException("CanalInstance must not be null."); + } + if (instance instanceof CanalInstanceWithSpring) { + mode = "spring"; + } else { + mode = "manager"; + } + this.metaManager = instance.getMetaManager(); + this.destination = instance.getDestination(); + this.subsHelp = "Subscriptions of canal instance " + destination; + } + + @Override + public List collect() { + List mfs = new ArrayList(); + GaugeMetricFamily instanceInfo = new GaugeMetricFamily( + "canal_instance", + "Canal instance", + InfoLabel); + instanceInfo.addMetric(Arrays.asList(destination, mode), 1); + mfs.add(instanceInfo); + if (metaManager.isStart()) { + // client id = hardcode 1001, 目前没有意义 + List subs = metaManager.listAllSubscribeInfo(destination); + GaugeMetricFamily subscriptions = new GaugeMetricFamily( + "canal_instance_subscription", + subsHelp, CanalInstanceExports.labelList); + subscriptions.addMetric(Arrays.asList(destination), subs.size()); + mfs.add(subscriptions); + } + return mfs; + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java new file mode 100644 index 00000000..79fdca4a --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java @@ -0,0 +1,75 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.prometheus.CanalInstanceExports; +import com.alibaba.otter.canal.store.CanalEventStore; +import com.alibaba.otter.canal.store.CanalStoreException; +import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer; +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author Chuanyi Li + */ +public class MemoryStoreCollector extends Collector { + + private static final Class clazz = MemoryEventStoreWithBuffer.class; + + private final String destination; + + private final AtomicLong putSequence; + + private final AtomicLong ackSequence; + + private final String putHelp; + + private final String ackHelp; + + public MemoryStoreCollector(CanalEventStore store, String destination) { + this.destination = destination; + if (!(store instanceof MemoryEventStoreWithBuffer)) { + throw new IllegalArgumentException("EventStore must be MemoryEventStoreWithBuffer"); + } + MemoryEventStoreWithBuffer ms = (MemoryEventStoreWithBuffer) store; + putSequence = getDeclaredValue(ms, "putSequence"); + ackSequence = getDeclaredValue(ms, "ackSequence"); + putHelp = "Produced sequence of canal instance " + destination; + ackHelp = "Consumed sequence of canal instance " + destination; + } + + @Override + public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily put = new CounterMetricFamily("canal_instance_store_produce_seq", + putHelp, Arrays.asList(CanalInstanceExports.labels)); + put.addMetric(Collections.singletonList(destination), putSequence.doubleValue()); + mfs.add(put); + CounterMetricFamily ack = new CounterMetricFamily("canal_instance_store_consume_seq", + ackHelp, Arrays.asList(CanalInstanceExports.labels)); + ack.addMetric(Collections.singletonList(destination), ackSequence.doubleValue()); + mfs.add(ack); + return mfs; + } + + @SuppressWarnings("unchecked") + private static T getDeclaredValue(MemoryEventStoreWithBuffer store, String name) { + T value; + try { + Field putField = clazz.getDeclaredField(name); + putField.setAccessible(true); + value = (T) putField.get(store); + } catch (NoSuchFieldException e) { + throw new CanalStoreException(e); + } catch (IllegalAccessException e) { + throw new CanalStoreException(e); + } + return value; + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java new file mode 100644 index 00000000..60dd808d --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java @@ -0,0 +1,80 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.jboss.netty.channel.Channel; +import org.jctools.maps.ConcurrentAutoTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.otter.canal.server.netty.NettyUtils.HEADER_LENGTH; + +/** + * @author Chuanyi Li + */ +@Aspect +public class OutboundThroughputAspect { + private static final Logger logger = LoggerFactory.getLogger(OutboundThroughputAspect.class); + + /** + * Support highly scalable counters + * @see ConcurrentAutoTable + */ + private static final ConcurrentAutoTable total = new ConcurrentAutoTable(); + + private static final Collector collector = new OutboundThroughputCollector(); + + public static Collector getCollector() { + return collector; + } + + @Pointcut("call(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))") + public void write() {} + + //nested read, just eliminate them. + @Pointcut("withincode(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))") + public void nestedCall() {} + + @After("write() && !nestedCall() && args(ch, bytes, ..)") + public void recordWriteBytes(Channel ch, byte[] bytes) { + if (bytes != null) { + accumulateBytes(HEADER_LENGTH + bytes.length); + } + } + + @After("write() && !nestedCall() && args(ch, buf, ..)") + public void recordWriteBuffer(Channel ch, ByteBuffer buf) { + if (buf != null) { + total.add(HEADER_LENGTH + buf.limit()); + } + } + private void accumulateBytes(int count) { + try { + total.add(count); + } catch (Throwable t) { + //Catch every Throwable, rather than break the business logic. + logger.warn("Error while accumulate inbound bytes.", t); + } + } + + public static class OutboundThroughputCollector extends Collector { + + private OutboundThroughputCollector() {} + + @Override public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily bytes = new CounterMetricFamily("canal_net_outbound_bytes", + "Total socket outbound bytes of canal server.", + total.get()); + mfs.add(bytes); + return mfs; + } + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java new file mode 100644 index 00000000..c55a7eac --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -0,0 +1,73 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.prometheus.CanalInstanceExports; +import com.alibaba.otter.canal.sink.AbstractCanalEventDownStreamHandler; +import com.alibaba.otter.canal.store.model.Event; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Chuanyi Li + */ +public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler> { + + private final Collector collector; + + private long latestExecuteTime = 0L; + + private static final String DELAY_NAME = "canal_instance_traffic_delay"; + + private final String delayHelpName; + + private final List labelValues; + + public PrometheusCanalEventDownStreamHandler(final String destination) { + this.delayHelpName = "Traffic delay of canal instance " + destination + " in seconds."; + this.labelValues = Collections.singletonList(destination); + collector = new Collector() { + @Override + public List collect() { + List mfs = new ArrayList(); + long now = System.currentTimeMillis(); + GaugeMetricFamily delay = new GaugeMetricFamily( + DELAY_NAME, + delayHelpName, + CanalInstanceExports.labelList); + double d = 0.0; + if (latestExecuteTime > 0) { + d = now - latestExecuteTime; + } + d = d > 0.0 ? (d / 1000) : 0.0; + delay.addMetric(labelValues, d); + mfs.add(delay); + return mfs; + } + }; + } + + @Override + public List before(List events) { + // TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming + // see: https://dev.mysql.com/worklog/task/?id=342 + // heartbeats are sent by the master only if there is no + // more unsent events in the actual binlog file for a period longer that + // master_heartbeat_period. + if (events != null && !events.isEmpty()) { + Event last = events.get(events.size() - 1); + long ts = last.getExecuteTime(); + if (ts > latestExecuteTime) { + latestExecuteTime = ts; + } + } + return events; + } + + public Collector getCollector() { + return this.collector; + } + +} diff --git a/prometheus/src/main/resources/META-INF/aop.xml b/prometheus/src/main/resources/META-INF/aop.xml new file mode 100644 index 00000000..8bd31d0c --- /dev/null +++ b/prometheus/src/main/resources/META-INF/aop.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider b/prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider new file mode 100644 index 00000000..9adbf8d1 --- /dev/null +++ b/prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider @@ -0,0 +1 @@ +com.alibaba.otter.canal.prometheus.PrometheusProvider diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index 722bb2bc..ccf21020 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -1,11 +1,11 @@ package com.alibaba.otter.canal.server.embedded; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; +import com.alibaba.otter.canal.spi.CanalMetricsProvider; +import com.alibaba.otter.canal.spi.CanalMetricsService; +import com.alibaba.otter.canal.spi.NopCanalMetricsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -33,17 +33,18 @@ import com.google.protobuf.ByteString; /** * 嵌入式版本实现 - * + * * @author jianghang 2012-7-12 下午01:34:00 * @author zebin.xuzb * @version 1.0.0 */ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements CanalServer, CanalService { - private static final Logger logger = LoggerFactory.getLogger(CanalServerWithEmbedded.class); + private static final Logger logger = LoggerFactory.getLogger(CanalServerWithEmbedded.class); private Map canalInstances; // private Map lastRollbackPostions; private CanalInstanceGenerator canalInstanceGenerator; + private CanalMetricsService metrics = NopCanalMetricsService.NOP; private static class SingletonHolder { @@ -61,7 +62,9 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C public void start() { if (!isStart()) { super.start(); - + // 如果存在provider,则启动metrics service + loadCanalMetrics(); + metrics.initialize(); canalInstances = MigrateMap.makeComputingMap(new Function() { public CanalInstance apply(String destination) { @@ -92,6 +95,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C logger.error(String.format("stop CanalInstance[%s] has an error", entry.getKey()), e); } } + metrics.terminate(); } public void start(final String destination) { @@ -99,6 +103,9 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C if (!canalInstance.isStart()) { try { MDC.put("destination", destination); + if (metrics.isRunning()) { + metrics.register(canalInstance); + } canalInstance.start(); logger.info("start CanalInstances[{}] successfully", destination); } finally { @@ -114,6 +121,9 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C try { MDC.put("destination", destination); canalInstance.stop(); + if (metrics.isRunning()) { + metrics.unregister(canalInstance); + } logger.info("stop CanalInstances[{}] successfully", destination); } finally { MDC.remove("destination"); @@ -176,7 +186,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 获取数据 - * + * *
          * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
          * 
    @@ -188,14 +198,14 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 获取数据,可以指定超时时间. - * + * *
          * 几种case:
          * a. 如果timeout为null,则采用tryGet方式,即时获取
          * b. 如果timeout不为null
          *    1. timeout为0,则采用get阻塞方式,获取数据,不设置超时,直到有足够的batchSize数据才返回
          *    2. timeout不为0,则采用get+timeout方式,获取数据,超时还没有batchSize足够的数据,有多少返回多少
    -     * 
    +     *
          * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
          * 
    */ @@ -251,7 +261,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 不指定 position 获取事件。canal 会记住此 client 最新的 position。
    * 如果是第一次 fetch,则会从 canal 中保存的最老一条数据开始输出。 - * + * *
          * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
          * 
    @@ -264,14 +274,14 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 不指定 position 获取事件。canal 会记住此 client 最新的 position。
    * 如果是第一次 fetch,则会从 canal 中保存的最老一条数据开始输出。 - * + * *
          * 几种case:
          * a. 如果timeout为null,则采用tryGet方式,即时获取
          * b. 如果timeout不为null
          *    1. timeout为0,则采用get阻塞方式,获取数据,不设置超时,直到有足够的batchSize数据才返回
          *    2. timeout不为0,则采用get+timeout方式,获取数据,超时还没有batchSize足够的数据,有多少返回多少
    -     *    
    +     *
          * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
          * 
    */ @@ -342,7 +352,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。 - * + * *
          * 注意:进行反馈时必须按照batchId的顺序进行ack(需有客户端保证)
          * 
    @@ -492,6 +502,27 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C } } + private void loadCanalMetrics() { + ServiceLoader providers = ServiceLoader.load(CanalMetricsProvider.class); + List list = new ArrayList(); + for (CanalMetricsProvider provider : providers) { + list.add(provider); + } + if (!list.isEmpty()) { + // 发现provider, 进行初始化 + if (list.size() > 1) { + logger.warn("Found more than one CanalMetricsProvider, use the first one."); + //报告冲突 + for (CanalMetricsProvider p : list) { + logger.warn("Found CanalMetricsProvider: {}.", p.getClass().getName()); + } + } + //默认使用第一个 + CanalMetricsProvider provider = list.get(0); + this.metrics = provider.getService(); + } + } + // ========= setter ========== public void setCanalInstanceGenerator(CanalInstanceGenerator canalInstanceGenerator) { diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java new file mode 100644 index 00000000..ad0ceddf --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java @@ -0,0 +1,24 @@ +package com.alibaba.otter.canal.spi; + +/** + * Use java service provider mechanism to provide {@link CanalMetricsService}. + *
    + * Example:
    + * {@code
    + *     ServiceLoader providers = ServiceLoader.load(CanalMetricsProvider.class);
    + *     List list = new ArrayList();
    + *     for (CanalMetricsProvider provider : providers) {
    + *         list.add(provider);
    + *     }
    + * }
    + * 
    + * @author Chuanyi Li + */ +public interface CanalMetricsProvider { + + /** + * @return Impl of {@link CanalMetricsService} + */ + CanalMetricsService getService(); + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java new file mode 100644 index 00000000..6d823be8 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java @@ -0,0 +1,42 @@ +package com.alibaba.otter.canal.spi; + +import com.alibaba.otter.canal.instance.core.CanalInstance; + +/** + * Canal server/instance metrics for export. + * + * Designed to be created by service provider. + * + * @see CanalMetricsProvider + * @author Chuanyi Li + */ +public interface CanalMetricsService { + + /** + * Initialization on canal server startup. + */ + void initialize(); + + /** + * Clean-up at canal server stop phase. + */ + void terminate(); + + /** + * @return {@code true} if the metrics service is running, otherwise {@code false}. + */ + boolean isRunning(); + + /** + * Register instance level metrics for specified instance. + * @param instance {@link CanalInstance} + */ + void register(CanalInstance instance); + + /** + * Unregister instance level metrics for specified instance. + * @param instance {@link CanalInstance} + */ + void unregister(CanalInstance instance); + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java new file mode 100644 index 00000000..0d6ad088 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java @@ -0,0 +1,38 @@ +package com.alibaba.otter.canal.spi; + +import com.alibaba.otter.canal.instance.core.CanalInstance; + +/** + * @author Chuanyi Li + */ +public class NopCanalMetricsService implements CanalMetricsService { + + public static final NopCanalMetricsService NOP = new NopCanalMetricsService(); + + private NopCanalMetricsService() {} + + @Override + public void initialize() { + + } + + @Override + public void terminate() { + + } + + @Override + public boolean isRunning() { + return false; + } + + @Override + public void register(CanalInstance instance) { + + } + + @Override + public void unregister(CanalInstance instance) { + + } +} From 1eaf198acfc0ae4464446315aba773ca50a27eff Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sun, 29 Jul 2018 15:06:30 +0800 Subject: [PATCH 031/360] Revert test code. --- .../alibaba/otter/canal/example/AbstractCanalClientTest.java | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java index 36979177..d1ddd88e 100644 --- a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java +++ b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java @@ -116,7 +116,7 @@ public class AbstractCanalClientTest { connector.connect(); connector.subscribe(); while (running) { - Message message = connector.getWithoutAck(batchSize, 1000L, TimeUnit.MILLISECONDS); // 获取指定数量的数据 + Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据 long batchId = message.getId(); int size = message.getEntries().size(); if (batchId == -1 || size == 0) { diff --git a/pom.xml b/pom.xml index 8b824ab5..59c5bc01 100644 --- a/pom.xml +++ b/pom.xml @@ -257,7 +257,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_186 + 2.0.0_preview_520 com.alibaba From 3d5f13fec8b840f5d913103264db8f7f4c2c1f72 Mon Sep 17 00:00:00 2001 From: winger Date: Tue, 31 Jul 2018 10:34:19 +0800 Subject: [PATCH 032/360] =?UTF-8?q?kafka=20producer=20=E9=80=82=E9=85=8D?= =?UTF-8?q?=20row=20data=20for=20performance=20#726?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kafka/producer/CanalKafkaProducer.java | 27 ++++++++++++++----- .../kafka/producer/MessageSerializer.java | 25 +++++++++-------- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java index 1543abf9..3318a89e 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java @@ -1,7 +1,9 @@ package com.alibaba.otter.canal.kafka.producer; +import java.io.IOException; import java.util.Properties; +import com.google.protobuf.ByteString; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; @@ -49,14 +51,25 @@ public class CanalKafkaProducer { } } - public void send(Topic topic, Message message) { + public void send(Topic topic, Message message) throws IOException { boolean valid = false; - if (message != null && !message.getEntries().isEmpty()) { - for (CanalEntry.Entry entry : message.getEntries()) { - if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - valid = true; - break; + if (message != null) { + if (message.isRaw() && !message.getRawEntries().isEmpty()) { + for (ByteString byteString : message.getRawEntries()) { + CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString); + if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN + && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { + valid = true; + break; + } + } + } else if (!message.getEntries().isEmpty()){ + for (CanalEntry.Entry entry : message.getEntries()) { + if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN + && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { + valid = true; + break; + } } } } diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java index 339e4f8c..17fca117 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java @@ -1,14 +1,13 @@ package com.alibaba.otter.canal.kafka.producer; -import java.util.Map; - -import org.apache.kafka.common.errors.SerializationException; -import org.apache.kafka.common.serialization.Serializer; -import org.springframework.util.CollectionUtils; - import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalPacket; import com.alibaba.otter.canal.protocol.Message; +import org.apache.kafka.common.errors.SerializationException; +import org.apache.kafka.common.serialization.Serializer; +import org.springframework.util.CollectionUtils; + +import java.util.Map; /** * Kafka Message类的序列化 @@ -25,12 +24,15 @@ public class MessageSerializer implements Serializer { @Override public byte[] serialize(String topic, Message data) { try { - if (data == null) return null; - else { + if (data != null) { CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); - if (data.getId() != -1 && !CollectionUtils.isEmpty(data.getEntries())) { - for (CanalEntry.Entry entry : data.getEntries()) { - messageBuilder.addMessages(entry.toByteString()); + if (data.getId() != -1) { + if (data.isRaw() && !CollectionUtils.isEmpty(data.getRawEntries())) { + messageBuilder.addAllMessages(data.getRawEntries()); + } else if (!CollectionUtils.isEmpty(data.getEntries())) { + for (CanalEntry.Entry entry : data.getEntries()) { + messageBuilder.addMessages(entry.toByteString()); + } } } CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); @@ -41,6 +43,7 @@ public class MessageSerializer implements Serializer { } catch (Exception e) { throw new SerializationException("Error when serializing message to byte[] "); } + return null; } @Override From 93ebc7b973b282c38d9cfacd7d7846cac309403b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 1 Aug 2018 18:25:49 +0800 Subject: [PATCH 033/360] fixed unused repo --- pom.xml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pom.xml b/pom.xml index 59c5bc01..9a14868b 100644 --- a/pom.xml +++ b/pom.xml @@ -66,16 +66,6 @@ false - - alibaba - http://code.alibabatech.com/mvn/releases/ - - true - - - false - - sonatype sonatype From 26edbeaa280b8c3617fea51f16f9358d67f659cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 2 Aug 2018 13:58:41 +0800 Subject: [PATCH 034/360] fixed kafka performance --- deployer/src/main/resources/canal.properties | 1 + .../resources/spring/default-instance.xml | 1 + .../main/resources/spring/file-instance.xml | 1 + .../main/resources/spring/group-instance.xml | 1 + .../main/resources/spring/local-instance.xml | 1 + .../main/resources/spring/memory-instance.xml | 1 + .../kafka/client/MessageDeserializer.java | 4 +- .../client/running/ClientRunningData.java | 4 +- .../client/running/ClientRunningMonitor.java | 82 ++++++++++--------- .../otter/canal/kafka/CanalServerStarter.java | 6 +- .../kafka/producer/CanalKafkaProducer.java | 51 ++++++------ .../kafka/producer/CanalKafkaStarter.java | 5 +- .../canal/kafka/producer/KafkaProperties.java | 25 ++++-- .../kafka/producer/MessageSerializer.java | 59 ++++++++++--- kafka/src/main/resources/kafka.yml | 1 + 15 files changed, 154 insertions(+), 89 deletions(-) diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 23ec03a1..0a11a5c9 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -43,6 +43,7 @@ canal.instance.filter.query.dml = false canal.instance.filter.query.ddl = false canal.instance.filter.table.error = false canal.instance.filter.rows = false +canal.instance.filter.transaction.entry = false # binlog format/image check canal.instance.binlog.format = ROW,STATEMENT,MIXED diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index c4465df1..9bf28896 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -81,6 +81,7 @@ + diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 340a8158..6d263717 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -67,6 +67,7 @@ + diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index 60d069dc..60ceff01 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -64,6 +64,7 @@ + diff --git a/deployer/src/main/resources/spring/local-instance.xml b/deployer/src/main/resources/spring/local-instance.xml index 0b2f96aa..2020d0d6 100644 --- a/deployer/src/main/resources/spring/local-instance.xml +++ b/deployer/src/main/resources/spring/local-instance.xml @@ -67,6 +67,7 @@ + diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index 8fec8918..c08f3f12 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -64,6 +64,7 @@ + diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java index 1cb3daba..70f2fc06 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java +++ b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java @@ -29,7 +29,9 @@ public class MessageDeserializer implements Deserializer { @Override public Message deserialize(String topic, byte[] data) { try { - if (data == null) return null; + if (data == null) { + return null; + } else { CanalPacket.Packet p = CanalPacket.Packet.parseFrom(data); switch (p.getType()) { diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java index 3eab85ed..7bbbabd7 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java +++ b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java @@ -8,8 +8,8 @@ package com.alibaba.otter.canal.kafka.client.running; */ public class ClientRunningData { - private String groupId; - private String address; + private String groupId; + private String address; private boolean active = true; public String getGroupId() { diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java index 3bdbfe6e..0f61211e 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java +++ b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java @@ -1,12 +1,11 @@ package com.alibaba.otter.canal.kafka.client.running; -import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; -import com.alibaba.otter.canal.common.utils.AddressUtils; -import com.alibaba.otter.canal.common.utils.BooleanMutex; -import com.alibaba.otter.canal.common.utils.JsonUtils; -import com.alibaba.otter.canal.common.zookeeper.ZkClientx; -import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; -import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import java.text.MessageFormat; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.exception.ZkException; import org.I0Itec.zkclient.exception.ZkInterruptedException; @@ -17,11 +16,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import java.text.MessageFormat; -import java.util.Random; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; +import com.alibaba.otter.canal.common.utils.AddressUtils; +import com.alibaba.otter.canal.common.utils.BooleanMutex; +import com.alibaba.otter.canal.common.utils.JsonUtils; +import com.alibaba.otter.canal.common.zookeeper.ZkClientx; +import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; + /** * kafka client running状态信息 @@ -31,13 +33,18 @@ import java.util.concurrent.TimeUnit; */ public class ClientRunningMonitor extends AbstractCanalLifeCycle { - private static final String TOPIC_ROOT_NODE = ZookeeperPathUtils.CANAL_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "topics"; + private static final String TOPIC_ROOT_NODE = ZookeeperPathUtils.CANAL_ROOT_NODE + + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "topics"; - private static final String TOPIC_NODE = TOPIC_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "{0}"; + private static final String TOPIC_NODE = TOPIC_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + + "{0}"; - private static final String TOPIC_CLIENTID_NODE = TOPIC_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "{1}"; + private static final String TOPIC_CLIENTID_NODE = TOPIC_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + + "{1}"; - private static final String TOPIC_CLIENTID_RUNNING_NODE = TOPIC_CLIENTID_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + ZookeeperPathUtils.RUNNING_NODE; + private static final String TOPIC_CLIENTID_RUNNING_NODE = TOPIC_CLIENTID_NODE + + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + + ZookeeperPathUtils.RUNNING_NODE; private static String getTopicClientRunning(String topic, String groupId) { return MessageFormat.format(TOPIC_CLIENTID_RUNNING_NODE, topic, groupId); @@ -47,21 +54,21 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { return MessageFormat.format(TOPIC_CLIENTID_NODE, topic, groupId); } - private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class); - private ZkClientx zkClient; - private String topic; - private ClientRunningData clientData; - private IZkDataListener dataListener; - private BooleanMutex mutex = new BooleanMutex(false); - private volatile boolean release = false; + private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class); + private ZkClientx zkClient; + private String topic; + private ClientRunningData clientData; + private IZkDataListener dataListener; + private BooleanMutex mutex = new BooleanMutex(false); + private volatile boolean release = false; private volatile ClientRunningData activeData; - private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1); - private ClientRunningListener listener; - private int delayTime = 5; + private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1); + private ClientRunningListener listener; + private int delayTime = 5; - private static Integer virtualPort; + private static Integer virtualPort; - public ClientRunningMonitor() { + public ClientRunningMonitor(){ if (virtualPort == null) { Random rand = new Random(); virtualPort = rand.nextInt(9000) + 1000; @@ -108,7 +115,6 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { } - public void start() { super.start(); @@ -123,7 +129,7 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { String path = getTopicClientRunning(this.topic, clientData.getGroupId()); zkClient.unsubscribeDataChanges(path, dataListener); releaseRunning(); // 尝试一下release - //Fix issue #697 + // Fix issue #697 if (delayExector != null) { delayExector.shutdown(); } @@ -159,13 +165,12 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { } } } catch (ZkNoNodeException e) { - zkClient.createPersistent(getClientIdNodePath(this.topic, clientData.getGroupId()), - true); // 尝试创建父节点 + zkClient.createPersistent(getClientIdNodePath(this.topic, clientData.getGroupId()), true); // 尝试创建父节点 initRunning(); } catch (Throwable t) { logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].", - topic), - t); + topic), + t); // 出现任何异常尝试release releaseRunning(); throw new CanalClientException("something goes wrong in initRunning method. ", t); @@ -187,7 +192,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { */ public boolean check() { String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - //ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId()); + // ZookeeperPathUtils.getDestinationClientRunning(this.destination, + // clientData.getClientId()); try { byte[] bytes = zkClient.readData(path); ClientRunningData eventData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class); @@ -196,8 +202,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { boolean result = isMine(activeData.getAddress()); if (!result) { logger.warn("canal is running in [{}] , but not in [{}]", - activeData.getAddress(), - clientData.getAddress()); + activeData.getAddress(), + clientData.getAddress()); } return result; } catch (ZkNoNodeException e) { @@ -235,7 +241,7 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { if (listener != null) { // 触发回调 listener.processActiveEnter(); - this.clientData.setAddress(/*address*/AddressUtils.getHostIp() + ":" + virtualPort); + this.clientData.setAddress(/* address */AddressUtils.getHostIp() + ":" + virtualPort); String path = getTopicClientRunning(this.topic, clientData.getGroupId()); // 序列化 diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java index e0b2d1d8..573f0eac 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java @@ -17,9 +17,9 @@ import com.alibaba.otter.canal.deployer.CanalController; */ public class CanalServerStarter { - private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(CanalServerStarter.class); - private volatile static boolean running = false; + private static final String CLASSPATH_URL_PREFIX = "classpath:"; + private static final Logger logger = LoggerFactory.getLogger(CanalServerStarter.class); + private volatile static boolean running = false; public static void init() { try { diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java index 3318a89e..4543aac1 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java @@ -3,7 +3,6 @@ package com.alibaba.otter.canal.kafka.producer; import java.io.IOException; import java.util.Properties; -import com.google.protobuf.ByteString; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; @@ -12,7 +11,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.kafka.producer.KafkaProperties.Topic; -import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.Message; /** @@ -52,31 +50,32 @@ public class CanalKafkaProducer { } public void send(Topic topic, Message message) throws IOException { - boolean valid = false; - if (message != null) { - if (message.isRaw() && !message.getRawEntries().isEmpty()) { - for (ByteString byteString : message.getRawEntries()) { - CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString); - if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - valid = true; - break; - } - } - } else if (!message.getEntries().isEmpty()){ - for (CanalEntry.Entry entry : message.getEntries()) { - if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - valid = true; - break; - } - } - } - } - if (!valid) { - return; - } + // set canal.instance.filter.transaction.entry = true + // boolean valid = false; + // if (message != null) { + // if (message.isRaw() && !message.getRawEntries().isEmpty()) { + // for (ByteString byteString : message.getRawEntries()) { + // CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString); + // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN + // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { + // valid = true; + // break; + // } + // } + // } else if (!message.getEntries().isEmpty()){ + // for (CanalEntry.Entry entry : message.getEntries()) { + // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN + // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { + // valid = true; + // break; + // } + // } + // } + // } + // if (!valid) { + // return; + // } ProducerRecord record; if (topic.getPartition() != null) { record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java index 0ff018c2..e88f9354 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java @@ -52,7 +52,10 @@ public class CanalKafkaStarter { // 初始化 kafka producer canalKafkaProducer = new CanalKafkaProducer(); canalKafkaProducer.init(kafkaProperties); - + // set filterTransactionEntry + if (kafkaProperties.isFilterTransactionEntry()) { + System.setProperty("canal.instance.filter.transaction.entry", "true"); + } // 对应每个instance启动一个worker线程 List destinations = kafkaProperties.getCanalDestinations(); diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java index 0b346137..8e193aac 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java @@ -13,15 +13,15 @@ import java.util.Set; */ public class KafkaProperties { - private String servers = "localhost:6667"; - private int retries = 0; - private int batchSize = 16384; - private int lingerMs = 1; - private long bufferMemory = 33554432L; + private String servers = "localhost:6667"; + private int retries = 0; + private int batchSize = 16384; + private int lingerMs = 1; + private long bufferMemory = 33554432L; + private boolean filterTransactionEntry = true; + private int canalBatchSize = 5; - private int canalBatchSize = 5; - - private List canalDestinations = new ArrayList(); + private List canalDestinations = new ArrayList(); public static class CanalDestination { @@ -158,4 +158,13 @@ public class KafkaProperties { public void setCanalDestinations(List canalDestinations) { this.canalDestinations = canalDestinations; } + + public boolean isFilterTransactionEntry() { + return filterTransactionEntry; + } + + public void setFilterTransactionEntry(boolean filterTransactionEntry) { + this.filterTransactionEntry = filterTransactionEntry; + } + } diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java index 17fca117..02336004 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java @@ -1,13 +1,19 @@ package com.alibaba.otter.canal.kafka.producer; -import com.alibaba.otter.canal.protocol.CanalEntry; -import com.alibaba.otter.canal.protocol.CanalPacket; -import com.alibaba.otter.canal.protocol.Message; +import java.util.List; +import java.util.Map; + import org.apache.kafka.common.errors.SerializationException; import org.apache.kafka.common.serialization.Serializer; import org.springframework.util.CollectionUtils; -import java.util.Map; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.CanalPacket; +import com.alibaba.otter.canal.protocol.CanalPacket.PacketType; +import com.alibaba.otter.canal.protocol.Message; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; /** * Kafka Message类的序列化 @@ -25,20 +31,53 @@ public class MessageSerializer implements Serializer { public byte[] serialize(String topic, Message data) { try { if (data != null) { - CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); if (data.getId() != -1) { if (data.isRaw() && !CollectionUtils.isEmpty(data.getRawEntries())) { - messageBuilder.addAllMessages(data.getRawEntries()); + // for performance + List rowEntries = data.getRawEntries(); + // message size + int messageSize = 0; + messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, data.getId()); + + int dataSize = 0; + for (int i = 0; i < rowEntries.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + } + messageSize += dataSize; + messageSize += 1 * rowEntries.size(); + // packet size + int size = 0; + size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, + PacketType.MESSAGES.getNumber()); + size += com.google.protobuf.CodedOutputStream.computeTagSize(5) + + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + + messageSize; + // build data + byte[] body = new byte[size]; + CodedOutputStream output = CodedOutputStream.newInstance(body); + output.writeEnum(3, PacketType.MESSAGES.getNumber()); + + output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(messageSize); + // message + output.writeInt64(1, data.getId()); + for (int i = 0; i < rowEntries.size(); i++) { + output.writeBytes(2, rowEntries.get(i)); + } + output.checkNoSpaceLeft(); + return body; } else if (!CollectionUtils.isEmpty(data.getEntries())) { + CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); for (CanalEntry.Entry entry : data.getEntries()) { messageBuilder.addMessages(entry.toByteString()); } + + CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); + packetBuilder.setType(CanalPacket.PacketType.MESSAGES); + packetBuilder.setBody(messageBuilder.build().toByteString()); + return packetBuilder.build().toByteArray(); } } - CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); - packetBuilder.setType(CanalPacket.PacketType.MESSAGES); - packetBuilder.setBody(messageBuilder.build().toByteString()); - return packetBuilder.build().toByteArray(); } } catch (Exception e) { throw new SerializationException("Error when serializing message to byte[] "); diff --git a/kafka/src/main/resources/kafka.yml b/kafka/src/main/resources/kafka.yml index ab214319..d6ccc51a 100644 --- a/kafka/src/main/resources/kafka.yml +++ b/kafka/src/main/resources/kafka.yml @@ -5,6 +5,7 @@ lingerMs: 1 bufferMemory: 33554432 # canal的批次大小,单位 k canalBatchSize: 50 +filterTransactionEntry: true canalDestinations: - canalDestination: example From da1ce51a0ff52ee289ce96243bc4c5952b2dd7b6 Mon Sep 17 00:00:00 2001 From: winger Date: Fri, 3 Aug 2018 11:11:38 +0800 Subject: [PATCH 035/360] =?UTF-8?q?=E6=94=AF=E6=8C=81GTID=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=B8=8B=EF=BC=8C=E9=80=9A=E8=BF=87=E8=8E=B7=E5=8F=96?= =?UTF-8?q?=E5=BD=93=E5=89=8Dgtid=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/parse/inbound/mysql/MysqlEventParser.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index 6c2b9d4d..e640364f 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -643,6 +643,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE throw new CanalParseException("command : 'show master status' has an error! pls check. you need (at least one of) the SUPER,REPLICATION CLIENT privilege(s) for this operation"); } EntryPosition endPosition = new EntryPosition(fields.get(0), Long.valueOf(fields.get(1))); + if (isGTIDMode && fields.size() > 4) { + endPosition.setGtid(fields.get(4)); + } return endPosition; } catch (IOException e) { throw new CanalParseException("command : 'show master status' has an error!", e); From 7fd958a490d9a5bb1d42d43c84ddbc5e6bf4d6d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 3 Aug 2018 17:48:32 +0800 Subject: [PATCH 036/360] fixed issue #801 , support canal docker --- .gitignore | 2 + .../otter/canal/deployer/CanalController.java | 13 +- deployer/src/main/resources/canal.properties | 9 +- .../resources/example/instance.properties | 19 ++- .../resources/spring/default-instance.xml | 4 +- .../main/resources/spring/file-instance.xml | 4 +- .../main/resources/spring/group-instance.xml | 8 +- .../main/resources/spring/local-instance.xml | 2 +- .../main/resources/spring/memory-instance.xml | 4 +- docker/Dockerfile | 69 +++++++++++ docker/build.sh | 30 +++++ docker/image/admin/app.sh | 117 ++++++++++++++++++ docker/image/admin/bin/clean_log | 2 + docker/image/admin/bin/clean_log.sh | 45 +++++++ docker/image/admin/health.sh | 13 ++ docker/image/alidata/bin/exec_rc_local.sh | 11 ++ docker/image/alidata/bin/lark-wait | 6 + docker/image/alidata/bin/main.sh | 27 ++++ docker/image/alidata/init/02init-sshd.sh | 19 +++ docker/image/alidata/init/fix-hosts.py | 66 ++++++++++ docker/image/alidata/lib/proc.sh | 40 ++++++ docker/run.sh | 92 ++++++++++++++ .../parse/driver/mysql/MysqlConnector.java | 2 +- 23 files changed, 580 insertions(+), 24 deletions(-) create mode 100644 docker/Dockerfile create mode 100644 docker/build.sh create mode 100755 docker/image/admin/app.sh create mode 100644 docker/image/admin/bin/clean_log create mode 100755 docker/image/admin/bin/clean_log.sh create mode 100755 docker/image/admin/health.sh create mode 100755 docker/image/alidata/bin/exec_rc_local.sh create mode 100755 docker/image/alidata/bin/lark-wait create mode 100755 docker/image/alidata/bin/main.sh create mode 100755 docker/image/alidata/init/02init-sshd.sh create mode 100755 docker/image/alidata/init/fix-hosts.py create mode 100644 docker/image/alidata/lib/proc.sh create mode 100644 docker/run.sh diff --git a/.gitignore b/.gitignore index 3f1a5d6b..90bd720f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ jtester.properties .idea/ *.iml .DS_Store +*.tar.gz +*.rpm diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 88f6e9a3..0a2c91f8 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -379,7 +379,18 @@ public class CanalController { } private String getProperty(Properties properties, String key) { - return StringUtils.trim(properties.getProperty(StringUtils.trim(key))); + key = StringUtils.trim(key); + String value = System.getProperty(key); + + if (value == null) { + value = System.getenv(key); + } + + if (value == null) { + value = properties.getProperty(key); + } + + return StringUtils.trim(value); } public void start() throws Throwable { diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 0a11a5c9..2bff29ae 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -3,7 +3,7 @@ ################################################# canal.id= 1 canal.ip= -canal.port= 11111 +canal.port=11111 canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 @@ -59,6 +59,13 @@ canal.instance.parser.parallel = true ## disruptor ringbuffer size, must be power of 2 canal.instance.parser.parallelBufferSize = 256 +# table meta tsdb info +canal.instance.tsdb.enable=true +canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:} +canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; +canal.instance.tsdb.dbUsername=canal +canal.instance.tsdb.dbPassword=canal + ################################################# ######### destinations ############# ################################################# diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index a3703b86..e0cf0064 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -1,11 +1,12 @@ ################################################# -## mysql serverId -canal.instance.mysql.slaveId=0 +## mysql serverId , v1.0.26+ will autoGen +# canal.instance.mysql.slaveId=0 + +# enable gtid use true/false +canal.instance.gtidon=false # position info canal.instance.master.address=127.0.0.1:3306 -# enable gtid use true/false -canal.instance.gtidon=false canal.instance.master.journal.name= canal.instance.master.position= canal.instance.master.timestamp= @@ -13,23 +14,21 @@ canal.instance.master.gtid= # table meta tsdb info canal.instance.tsdb.enable=true -canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:} -canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; #canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb -canal.instance.tsdb.dbUsername=canal -canal.instance.tsdb.dbPassword=canal - +#canal.instance.tsdb.dbUsername=canal +#canal.instance.tsdb.dbPassword=canal #canal.instance.standby.address = #canal.instance.standby.journal.name = #canal.instance.standby.position = #canal.instance.standby.timestamp = #canal.instance.standby.gtid= + # username/password canal.instance.dbUsername=canal canal.instance.dbPassword=canal -canal.instance.defaultDatabaseName=test canal.instance.connectionCharset=UTF-8 + # table regex canal.instance.filter.regex=.*\\..* # table black regex diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index 9bf28896..6b455c83 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -149,7 +149,7 @@ - +
    @@ -157,7 +157,7 @@ - +
    diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 6d263717..377428a9 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -134,7 +134,7 @@ - +
    @@ -142,7 +142,7 @@ - + diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index 60ceff01..44f1ab3b 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -131,7 +131,7 @@ - + @@ -139,7 +139,7 @@ - + @@ -229,7 +229,7 @@ - + @@ -237,7 +237,7 @@ - + diff --git a/deployer/src/main/resources/spring/local-instance.xml b/deployer/src/main/resources/spring/local-instance.xml index 2020d0d6..930ecb7b 100644 --- a/deployer/src/main/resources/spring/local-instance.xml +++ b/deployer/src/main/resources/spring/local-instance.xml @@ -114,7 +114,7 @@ - + diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index c08f3f12..cd387b8f 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -122,7 +122,7 @@ - + @@ -130,7 +130,7 @@ - + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..7e10085b --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,69 @@ +FROM centos:centos6.7 + +MAINTAINER agapple (jianghang115@gmail.com) + +# install system +RUN \ + /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo 'root:Hello1234' | chpasswd && \ + groupadd -r admin && useradd -g admin admin && \ + yum install -y man && \ + yum install -y dstat && \ + yum install -y unzip && \ + yum install -y nc && \ + yum install -y openssh-server && \ + yum install -y tar && \ + yum install -y which && \ + yum install -y wget && \ + yum install -y perl && \ + yum install -y file && \ + ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \ + ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \ + sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \ + sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \ + mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \ + yum install -y cronie && \ + sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \ + yum clean all && \ + true + +# install canal +COPY image/ /tmp/docker/ +COPY canal.deployer-*.tar.gz /home/admin/ +COPY jdk-8-linux-x64.rpm /tmp/ + +RUN \ + cp -R /tmp/docker/alidata /alidata && \ + chmod +x /alidata/bin/* && \ + mkdir -p /home/admin && \ + cp -R /tmp/docker/admin/* /home/admin/ && \ + /bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \ + + touch /var/lib/rpm/* && \ + yum -y install /tmp/jdk-8-linux-x64.rpm && \ + /bin/rm -f /tmp/jdk-8-linux-x64.rpm && \ + + echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \ + echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \ + /bin/mv /home/admin/bin/canal-check /etc/cron.d && \ + /bin/mv /home/admin/bin/clean_log /etc/cron.d && \ + chmod +x /home/admin/bin/canal-process-check.py && \ + + mkdir -p /home/admin/canal-server && \ + tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \ + /bin/rm -f /home/admin/canal.deployer-*.tar.gz && \ + + mkdir -p home/admin/canal-server/logs && \ + chmod +x /home/admin/*.sh && \ + chmod +x /home/admin/bin/*.sh && \ + chown admin: -R /home/admin && \ + yum clean all && \ + true + +# 2222 sys , 8080 web , 8000 debug , 11111 canal +EXPOSE 2222 11111 8000 8080 + +WORKDIR /home/admin + +ENTRYPOINT [ "/alidata/bin/main.sh" ] +CMD [ "/home/admin/app.sh" ] \ No newline at end of file diff --git a/docker/build.sh b/docker/build.sh new file mode 100644 index 00000000..58424fb3 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +current_path=`pwd` +case "`uname`" in + Darwin) + bin_abs_path=`cd $(dirname $0); pwd` + ;; + Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + ;; + *) + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +BASE=${bin_abs_path} + +if [ ! -f $BASE/jdk*.rpm ] ; then + DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz" + wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm +fi + +cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; + +if [ "$1" == "kafka" ] ; then + cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ +else + cp $BASE/../target/canal.deployer-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ +fi diff --git a/docker/image/admin/app.sh b/docker/image/admin/app.sh new file mode 100755 index 00000000..254f7e48 --- /dev/null +++ b/docker/image/admin/app.sh @@ -0,0 +1,117 @@ +#!/bin/bash +set -e + +source /etc/profile +export JAVA_HOME=/usr/java/latest +export PATH=$JAVA_HOME/bin:$PATH +touch /tmp/start.log +chown admin: /tmp/start.log +chown -R admin: /home/admin/canal-server +host=`hostname -i` + +# waitterm +# wait TERM/INT signal. +# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html +waitterm() { + local PID + # any process to block + tail -f /dev/null & + PID="$!" + # setup trap, could do nothing, or just kill the blocker + trap "kill -TERM ${PID}" TERM INT + # wait for signal, ignore wait exit code + wait "${PID}" || true + # clear trap + trap - TERM INT + # wait blocker, ignore blocker exit code + wait "${PID}" 2>/dev/null || true +} + +# waittermpid "${PIDFILE}". +# monitor process by pidfile && wait TERM/INT signal. +# if the process disappeared, return 1, means exit with ERROR. +# if TERM or INT signal received, return 0, means OK to exit. +waittermpid() { + local PIDFILE PID do_run error + PIDFILE="${1?}" + do_run=true + error=0 + trap "do_run=false" TERM INT + while "${do_run}" ; do + PID="$(cat "${PIDFILE}")" + if ! ps -p "${PID}" >/dev/null 2>&1 ; then + do_run=false + error=1 + else + sleep 1 + fi + done + trap - TERM INT + return "${error}" +} + + +function checkStart() { + local name=$1 + local cmd=$2 + local timeout=$3 + cost=5 + while [ $timeout -gt 0 ]; do + ST=`eval $cmd` + if [ "$ST" == "0" ]; then + sleep 1 + let timeout=timeout-1 + let cost=cost+1 + elif [ "$ST" == "" ]; then + sleep 1 + let timeout=timeout-1 + let cost=cost+1 + else + break + fi + done + echo "start $name successful" +} + + +function start_canal() { + echo "start canal ..." + serverPort=`perl -le 'print $ENV{"canal.port"}'` + if [ -z "$serverPort" ] ; then + serverPort=11111 + fi + + destination=`perl -le 'print $ENV{"canal.destinations"}'` + if [[ "$destination" =~ ',' ]]; then + echo "multi destination:$destination is not support" + exit 1; + else + mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination + fi + su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1' + sleep 5 + #check start + checkStart "canal" "nc 127.0.0.1 $serverPort -w 1 -z | wc -l" 30 +} + +function stop_canal() { + echo "stop canal" + su admin -c 'cd /home/admin/canal-server/bin/ && sh stop.sh 1>>/tmp/start.log 2>&1' + echo "stop canal successful ..." +} + +echo "==> START ..." + +start_canal + +echo "==> START SUCCESSFUL ..." + +tail -f /dev/null & +# wait TERM signal +waitterm + +echo "==> STOP" + +stop_canal + +echo "==> STOP SUCCESSFUL ..." diff --git a/docker/image/admin/bin/clean_log b/docker/image/admin/bin/clean_log new file mode 100644 index 00000000..df183614 --- /dev/null +++ b/docker/image/admin/bin/clean_log @@ -0,0 +1,2 @@ +# cron clean log once per minute +*/2 * * * * admin /home/admin/bin/clean_log.sh >>/tmp/clean_log.log 2>&1 diff --git a/docker/image/admin/bin/clean_log.sh b/docker/image/admin/bin/clean_log.sh new file mode 100755 index 00000000..9d0a0bef --- /dev/null +++ b/docker/image/admin/bin/clean_log.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Global Settings +PATH="$HOME/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin" +export PATH + +CUTOFF="85" +#获取磁盘使用率最高的分区 +USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) +before=$USAGE + +baseClean(){ + #删除tmp目录15天前的文件。 + #更新文档时间戳 + if [ -d /tmp/hsperfdata_admin ] + then + touch /tmp/hsperfdata_admin + touch /tmp/hsperfdata_admin/* + fi + + find /tmp/ -type f -mtime +15 | xargs -t rm -rf >/dev/null 2>&1 + + + now=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + echo "before:$before; now:$now" +} + +CANAL_DIR="/home/admin/canal-server/logs" +if [[ -d $CANAL_DIR ]]; then + USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + if [[ $USAGE -ge 90 ]]; then + find $CANAL_DIR -type f -mtime +7 | xargs rm -rf {} + fi + USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + if [[ $USAGE -ge 80 ]]; then + find $CANAL_DIR -type f -mtime +3 | xargs rm -rf {} + fi + USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + if [[ $USAGE -ge 80 ]]; then + find $CANAL_DIR -type d -empty -mtime +3 | grep -v canal | xargs rm -rf {} + find $CANAL_DIR -type f -iname '*.tmp' | xargs rm -rf {} + fi + baseClean + exit 0 +fi \ No newline at end of file diff --git a/docker/image/admin/health.sh b/docker/image/admin/health.sh new file mode 100755 index 00000000..17a89740 --- /dev/null +++ b/docker/image/admin/health.sh @@ -0,0 +1,13 @@ +#!/bin/sh +CHECK_URL="http://127.0.0.1:8080/metrics" +CHECK_POINT="success" +CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT` +if [ $CHECK_COUNT -eq 0 ]; then + echo "[FAILED]" + status=0 + error=1 +else + echo "[ OK ]" + status=1 + error=0 +fi \ No newline at end of file diff --git a/docker/image/alidata/bin/exec_rc_local.sh b/docker/image/alidata/bin/exec_rc_local.sh new file mode 100755 index 00000000..7cc8aa3f --- /dev/null +++ b/docker/image/alidata/bin/exec_rc_local.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ "${SKIP_EXEC_RC_LOCAL}" = "YES" ] ; then + echo "skip /etc/rc.local: SKIP_EXEC_RC_LOCAL=${SKIP_EXEC_RC_LOCAL}" + exit +fi + +if [ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] ; then + echo "skip /etc/rc.local: DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}" + exit +fi \ No newline at end of file diff --git a/docker/image/alidata/bin/lark-wait b/docker/image/alidata/bin/lark-wait new file mode 100755 index 00000000..db7be708 --- /dev/null +++ b/docker/image/alidata/bin/lark-wait @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +chown admin: -R /home/admin/ +source /alidata/lib/proc.sh +waitterm diff --git a/docker/image/alidata/bin/main.sh b/docker/image/alidata/bin/main.sh new file mode 100755 index 00000000..a57551a7 --- /dev/null +++ b/docker/image/alidata/bin/main.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +[ -n "${DOCKER_DEPLOY_TYPE}" ] || DOCKER_DEPLOY_TYPE="VM" +echo "DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}" + +# run init scripts +for e in $(ls /alidata/init/*) ; do + [ -x "${e}" ] || continue + echo "==> INIT $e" + $e + echo "==> EXIT CODE: $?" +done + +echo "==> INIT DEFAULT" +service sshd start +service crond start + +#echo "check hostname -i: `hostname -i`" +#hti_num=`hostname -i|awk '{print NF}'` +#if [ $hti_num -gt 1 ];then +# echo "hostname -i result error:`hostname -i`" +# exit 120 +#fi + +echo "==> INIT DONE" +echo "==> RUN ${*}" +exec "${@}" \ No newline at end of file diff --git a/docker/image/alidata/init/02init-sshd.sh b/docker/image/alidata/init/02init-sshd.sh new file mode 100755 index 00000000..118ee553 --- /dev/null +++ b/docker/image/alidata/init/02init-sshd.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# set port +if [ -z "${SSHD_PORT}" ] ; then + SSHD_PORT=22 + [ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] && SSHD_PORT=2222 +fi + +sed -r -i '/^OPTIONS=/ d' /etc/sysconfig/sshd +echo 'OPTIONS="-p '"${SSHD_PORT}"'"' >> /etc/sysconfig/sshd + +# set admin ssh pulic key +if [ "${USE_ADMIN_PASSAGE}" = "YES" ] ; then + echo "set admin passage" + mkdir -p /home/admin/.ssh + chown admin:admin /home/admin/.ssh + chown admin:admin /home/admin/.ssh/authorized_keys + chmod 644 /home/admin/.ssh/authorized_keys +fi diff --git a/docker/image/alidata/init/fix-hosts.py b/docker/image/alidata/init/fix-hosts.py new file mode 100755 index 00000000..4ee8abb5 --- /dev/null +++ b/docker/image/alidata/init/fix-hosts.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +#****************************************************************# +# Create Date: 2017-01-06 17:58 +#***************************************************************# + +import socket +import shutil +from time import gmtime, strftime + +# get host_name +host_name = socket.gethostname() +tmp_file = "/tmp/.lark-fix-host.hosts" +host_file = "/etc/hosts" +bak_file_name = "/tmp/hosts-fix-bak.%s" % ( strftime("%Y-%m-%d_%H-%M-%S", gmtime()) ) + +# load /etc/hosts file context +FH = open(host_file,"r") +file_lines = [ i.rstrip() for i in FH.readlines()] +FH.close() +file_lines_reverse = file_lines[::-1] +new_lines = [] +bad_lines = [] +last_match_line = "" + +for line in file_lines_reverse: + if line.find(host_name) < 0: # 不匹配的行直接跳过 + new_lines.append(line + "\n") + continue + + cols = line.split() + new_cols = [] + if cols[0].startswith("#"): # 跳过已经注释掉的行 + new_lines.append(line + "\n") + continue + for col in cols: + if not col == host_name: # 跳过不匹配的列 + new_cols.append(col) + continue + + if cols[0] == "127.0.0.1": # 如果第一列是 127.0.0.1 就跳过匹配的列, 防止 hostname -i 返回 127.0.0.1 + continue + + # 如果已经发现过匹配的列, 就丢掉重复的列 + if not len(last_match_line) == 0: + continue + + new_cols.append(col) + last_match_line = line + + # 跳过 xx.xx.xx.xx hostname 这样的重复列 + if len(new_cols) == 1: + continue + + new_l = "%s\n" % " ".join(new_cols) + new_lines.append(new_l) + +# save tmp hosts + +FH2=file(tmp_file,"w+") +FH2.writelines( new_lines[::-1]) +FH2.close() + +# mv to /etc/hosts +shutil.copy(host_file, bak_file_name) +shutil.move(tmp_file, host_file) diff --git a/docker/image/alidata/lib/proc.sh b/docker/image/alidata/lib/proc.sh new file mode 100644 index 00000000..40ddff93 --- /dev/null +++ b/docker/image/alidata/lib/proc.sh @@ -0,0 +1,40 @@ +# waitterm +# wait TERM/INT signal. +# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html +waitterm() { + local PID + # any process to block + tail -f /dev/null & + PID="$!" + # setup trap, could do nothing, or just kill the blocker + trap "kill -TERM ${PID}" TERM INT + # wait for signal, ignore wait exit code + wait "${PID}" || true + # clear trap + trap - TERM INT + # wait blocker, ignore blocker exit code + wait "${PID}" 2>/dev/null || true +} + +# waittermpid "${PIDFILE}". +# monitor process by pidfile && wait TERM/INT signal. +# if the process disappeared, return 1, means exit with ERROR. +# if TERM or INT signal received, return 0, means OK to exit. +waittermpid() { + local PIDFILE PID do_run error + PIDFILE="${1?}" + do_run=true + error=0 + trap "do_run=false" TERM INT + while "${do_run}" ; do + PID="$(cat "${PIDFILE}")" + if ! ps -p "${PID}" >/dev/null 2>&1 ; then + do_run=false + error=1 + else + sleep 1 + fi + done + trap - TERM INT + return "${error}" +} diff --git a/docker/run.sh b/docker/run.sh new file mode 100644 index 00000000..7c56e2ba --- /dev/null +++ b/docker/run.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +function usage() { + echo "Usage:" + echo " run.sh [CONFIG]" + echo "example:" + echo " run.sh -e canal.instance.master.address=127.0.0.1:3306 \\" + echo " -e canal.instance.dbUsername=canal \\" + echo " -e canal.instance.dbPassword=canal \\" + echo " -e canal.instance.connectionCharset=UTF-8 \\" + echo " -e canal.instance.tsdb.enable=true \\" + echo " -e canal.instance.gtidon=false \\" + echo " -e canal.instance.filter.regex=.*\\..* " + exit +} + +function check_port() { + local port=$1 + local TL=$(which telnet) + if [ -f $TL ]; then + data=`echo quit | telnet 127.0.0.1 $port| grep -ic connected` + echo $data + return + fi + + local NC=$(which nc) + if [ -f $NC ]; then + data=`nc -z -w 1 127.0.0.1 $port | grep -ic succeeded` + echo $data + return + fi + echo "0" + return +} + +function getMyIp() { + case "`uname`" in + Darwin) + myip=`echo "show State:/Network/Global/IPv4" | scutil | grep PrimaryInterface | awk '{print $3}' | xargs ifconfig | grep inet | grep -v inet6 | awk '{print $2}'` + ;; + *) + myip=`ip route get 1 | awk '{print $NF;exit}'` + ;; + esac + echo $myip +} + +NET_MODE="" +case "`uname`" in + Darwin) + bin_abs_path=`cd $(dirname $0); pwd` + ;; + Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + NET_MODE="--net=host" + ;; + *) + NET_MODE="--net=host" + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +BASE=${bin_abs_path} +if [ $# -eq 0 ]; then + usage +elif [ "$1" == "-h" ] ; then + usage +elif [ "$1" == "help" ] ; then + usage +fi + +DATA="$BASE/data" +mkdir -p $DATA +CONFIG=${@:1} +#VOLUMNS="-v $DATA:/home/admin/canal-server/logs" +PORTLIST="8000 8080 2222 11111" +PORTS="" +for PORT in $PORTLIST ; do + #exist=`check_port $PORT` + exist="0" + if [ "$exist" == "0" ]; then + PORTS="$PORTS -p $PORT:$PORT" + else + echo "port $PORT is used , pls check" + exit 1 + fi +done + +MEMORY="-m 4096m" +LOCALHOST=`getMyIp` +cmd="docker run -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server" +echo $cmd +eval $cmd \ No newline at end of file diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java index 29b96852..0f01cbd5 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java @@ -32,7 +32,7 @@ public class MysqlConnector { private String password; private byte charsetNumber = 33; - private String defaultSchema = "retl"; + private String defaultSchema = "test"; private int soTimeout = 30 * 1000; private int connTimeout = 5 * 1000; private int receiveBufferSize = 16 * 1024; From 872dad8ce82e21e3acfdb5d3f596e81e52b44f2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Sat, 4 Aug 2018 10:37:55 +0800 Subject: [PATCH 037/360] fixed issue #801 , optimizer docker --- docker/Dockerfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 7e10085b..e44796cb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -45,9 +45,7 @@ RUN \ echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \ echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \ - /bin/mv /home/admin/bin/canal-check /etc/cron.d && \ /bin/mv /home/admin/bin/clean_log /etc/cron.d && \ - chmod +x /home/admin/bin/canal-process-check.py && \ mkdir -p /home/admin/canal-server && \ tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \ From 3c716e2a436f729f1394e9a92e86be2e9095da1e Mon Sep 17 00:00:00 2001 From: qmzhang <578967933@qq.com> Date: Sun, 5 Aug 2018 19:40:14 +0800 Subject: [PATCH 038/360] =?UTF-8?q?instance=E7=94=B1manager=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=97=B6=EF=BC=8CCanalParameter#MetaMode=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=9C=AC=E5=9C=B0=E6=96=87=E4=BB=B6=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E6=9C=AC=E5=9C=B0meta.dat=E5=AD=98=E5=82=A8=E4=BD=8D?= =?UTF-8?q?=E7=82=B9=E4=BF=A1=E6=81=AF=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manager/CanalInstanceWithManager.java | 21 +++----- .../manager/model/CanalParameter.java | 26 ++++++++- .../CanalServerWithEmbedded_FileModeTest.java | 53 +++++++++++++++++++ 3 files changed, 86 insertions(+), 14 deletions(-) create mode 100644 server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index bca5ba13..35e3235a 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import com.alibaba.otter.canal.meta.FileMixedMetaManager; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,13 +21,7 @@ import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.instance.core.AbstractCanalInstance; import com.alibaba.otter.canal.instance.manager.model.Canal; import com.alibaba.otter.canal.instance.manager.model.CanalParameter; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.DataSourcing; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.HAMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.IndexMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.MetaMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.SourcingType; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageScavengeMode; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.*; import com.alibaba.otter.canal.meta.MemoryMetaManager; import com.alibaba.otter.canal.meta.PeriodMixedMetaManager; import com.alibaba.otter.canal.meta.ZooKeeperMetaManager; @@ -37,12 +32,7 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; -import com.alibaba.otter.canal.parse.index.FailbackLogPositionManager; -import com.alibaba.otter.canal.parse.index.MemoryLogPositionManager; -import com.alibaba.otter.canal.parse.index.MetaLogPositionManager; -import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager; -import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager; +import com.alibaba.otter.canal.parse.index.*; import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.sink.entry.EntryEventSink; @@ -120,6 +110,11 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { ZooKeeperMetaManager zooKeeperMetaManager = new ZooKeeperMetaManager(); zooKeeperMetaManager.setZkClientx(getZkclientx()); ((PeriodMixedMetaManager) metaManager).setZooKeeperMetaManager(zooKeeperMetaManager); + } else if (mode.isLocalFile()){ + FileMixedMetaManager fileMixedMetaManager = new FileMixedMetaManager(); + fileMixedMetaManager.setDataDir(parameters.getDataDir()); + fileMixedMetaManager.setPeriod(parameters.getMetaFileFlushPeriod()); + metaManager = fileMixedMetaManager; } else { throw new CanalException("unsupport MetaMode for " + mode); } diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java index 16c929ca..befc8e2f 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java @@ -28,8 +28,10 @@ public class CanalParameter implements Serializable { private Long zkClusterId; // zk集群id,为管理方便 private List zkClusters; // zk集群地址 + private String dataDir = "../conf"; // 默认本地文件数据的目录默认是conf // meta相关参数 private MetaMode metaMode = MetaMode.MEMORY; // meta机制 + private Integer metaFileFlushPeriod = 1000; // meta刷新间隔 // storage存储 private Integer transactionSize = 1024; // 支持处理的transaction事务大小 @@ -243,7 +245,9 @@ public class CanalParameter implements Serializable { /** 文件存储模式 */ ZOOKEEPER, /** 混合模式,内存+文件 */ - MIXED; + MIXED, + /** 本地文件存储模式*/ + LOCAL_FILE; public boolean isMemory() { return this.equals(MetaMode.MEMORY); @@ -256,6 +260,10 @@ public class CanalParameter implements Serializable { public boolean isMixed() { return this.equals(MetaMode.MIXED); } + + public boolean isLocalFile(){ + return this.equals(MetaMode.LOCAL_FILE); + } } public static enum IndexMode { @@ -390,6 +398,22 @@ public class CanalParameter implements Serializable { return storageMode; } + public String getDataDir() { + return dataDir; + } + + public void setDataDir(String dataDir) { + this.dataDir = dataDir; + } + + public Integer getMetaFileFlushPeriod() { + return metaFileFlushPeriod; + } + + public void setMetaFileFlushPeriod(Integer metaFileFlushPeriod) { + this.metaFileFlushPeriod = metaFileFlushPeriod; + } + public void setStorageMode(StorageMode storageMode) { this.storageMode = storageMode; } diff --git a/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java b/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java new file mode 100644 index 00000000..e986b6ff --- /dev/null +++ b/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java @@ -0,0 +1,53 @@ +package com.alibaba.otter.canal.server; + +import java.net.InetSocketAddress; +import java.util.Arrays; + +import com.alibaba.otter.canal.instance.manager.model.Canal; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.*; + +public class CanalServerWithEmbedded_FileModeTest extends BaseCanalServerWithEmbededTest { + + protected Canal buildCanal() { + Canal canal = new Canal(); + canal.setId(1L); + canal.setName(DESTINATION); + canal.setDesc("my standalone server test "); + + CanalParameter parameter = new CanalParameter(); + + parameter.setMetaMode(MetaMode.LOCAL_FILE); + parameter.setDataDir("./conf"); + parameter.setMetaFileFlushPeriod(1000); + parameter.setHaMode(HAMode.HEARTBEAT); + parameter.setIndexMode(IndexMode.MEMORY_META_FAILBACK); + + parameter.setStorageMode(StorageMode.MEMORY); + parameter.setMemoryStorageBufferSize(32 * 1024); + + parameter.setSourcingType(SourcingType.MYSQL); + parameter.setDbAddresses(Arrays.asList(new InetSocketAddress(MYSQL_ADDRESS, 3306), + new InetSocketAddress(MYSQL_ADDRESS, 3306))); + parameter.setDbUsername(USERNAME); + parameter.setDbPassword(PASSWORD); + parameter.setPositions(Arrays.asList("{\"journalName\":\"mysql-bin.000001\",\"position\":332L,\"timestamp\":\"1505998863000\"}", + "{\"journalName\":\"mysql-bin.000001\",\"position\":332L,\"timestamp\":\"1505998863000\"}")); + + parameter.setSlaveId(1234L); + + parameter.setDefaultConnectionTimeoutInSeconds(30); + parameter.setConnectionCharset("UTF-8"); + parameter.setConnectionCharsetNumber((byte) 33); + parameter.setReceiveBufferSize(8 * 1024); + parameter.setSendBufferSize(8 * 1024); + + parameter.setDetectingEnable(false); + parameter.setDetectingIntervalInSeconds(10); + parameter.setDetectingRetryTimes(3); + parameter.setDetectingSQL(DETECTING_SQL); + + canal.setCanalParameter(parameter); + return canal; + } +} \ No newline at end of file From 8a952d3a5f41939b249bc85e7cb3b81ee41ff873 Mon Sep 17 00:00:00 2001 From: winger Date: Mon, 6 Aug 2018 11:11:57 +0800 Subject: [PATCH 039/360] fix bug: kafka get row data for performance --- .../alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java index e88f9354..0eedfa7a 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java @@ -121,7 +121,7 @@ public class CanalKafkaStarter { Message message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); // 获取指定数量的数据 long batchId = message.getId(); try { - int size = message.getEntries().size(); + int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); if (batchId != -1 && size != 0) { if (!StringUtils.isEmpty(destination.getTopic())) { Topic topic = new Topic(); From 7d9e2dc69b63df1053c5ee89541291e4f9b114ef Mon Sep 17 00:00:00 2001 From: winger Date: Tue, 7 Aug 2018 12:04:57 +0800 Subject: [PATCH 040/360] =?UTF-8?q?fix=20#802:=20=E4=BB=8Eotter=E4=B8=AD?= =?UTF-8?q?=E5=89=A5=E7=A6=BBMySQL=20=E7=9B=B8=E5=85=B3=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0=EF=BC=8C=E4=BD=9C=E4=B8=BAexample=E7=9A=84?= =?UTF-8?q?=E5=8F=82=E8=80=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/pom.xml | 81 +++ .../canal/example/db/AbstractDbClient.java | 144 ++++++ .../example/db/CanalConnectorClient.java | 488 ++++++++++++++++++ .../canal/example/db/MysqlLoadLauncher.java | 35 ++ .../canal/example/db/ServiceLocator.java | 44 ++ .../example/db/dialect/AbstractDbDialect.java | 121 +++++ .../db/dialect/AbstractSqlTemplate.java | 105 ++++ .../canal/example/db/dialect/DbDialect.java | 20 + .../canal/example/db/dialect/SqlTemplate.java | 40 ++ .../canal/example/db/dialect/TableType.java | 93 ++++ .../db/dialect/mysql/MysqlDialect.java | 32 ++ .../db/dialect/mysql/MysqlSqlTemplate.java | 84 +++ .../example/db/mysql/AbstractMysqlClient.java | 207 ++++++++ .../canal/example/db/mysql/MysqlClient.java | 23 + .../example/db/utils/ByteArrayConverter.java | 50 ++ .../canal/example/db/utils/DdlUtils.java | 326 ++++++++++++ .../db/utils/SqlTimestampConverter.java | 140 +++++ .../canal/example/db/utils/SqlUtils.java | 315 +++++++++++ example/src/main/resources/client-spring.xml | 53 ++ example/src/main/resources/client.properties | 16 + 20 files changed, 2417 insertions(+) create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java create mode 100644 example/src/main/resources/client-spring.xml create mode 100644 example/src/main/resources/client.properties diff --git a/example/pom.xml b/example/pom.xml index 1f60bc01..ba7a6dde 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -21,6 +21,79 @@ canal.protocol ${project.version} + + com.alibaba.otter + canal.instance.spring + ${project.version} + + + org.apache.ddlutils + ddlutils + 1.0 + + + commons-beanutils + commons-beanutils-core + + + commons-lang + commons-lang + + + commons-dbcp + commons-dbcp + + + commons-pool + commons-pool + + + commons-logging + commons-logging-api + + + dom4j + dom4j + + + stax + stax-api + + + commons-collections + commons-collections + + + commons-digester + commons-digester + + + commons-betwixt + commons-betwixt + + + + + org.apache.commons + commons-pool2 + 2.5.0 + + + commons-beanutils + commons-beanutils + 1.8.2 + + + org.apache.commons + commons-lang3 + 3.7 + + + commons-collections + commons-collections + 3.2 + + junit @@ -62,6 +135,14 @@ false + + org.apache.maven.plugins + maven-compiler-plugin + + 7 + 7 + + diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java new file mode 100644 index 00000000..ec998e55 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java @@ -0,0 +1,144 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.Message; +import org.slf4j.MDC; + +import java.util.Date; +import java.util.List; + +public abstract class AbstractDbClient extends CanalConnectorClient { + + + public abstract void insert(CanalEntry.Header header, List afterColumns); + + public abstract void update(CanalEntry.Header header, List afterColumns); + + public abstract void delete(CanalEntry.Header header, List beforeColumns); + + + @Override + public synchronized void start() { + if (running) { + return; + } + super.start(); + } + + @Override + public synchronized void stop() { + if (!running) { + return; + } + super.stop(); + MDC.remove("destination"); + } + + @Override + protected void processMessage(Message message) { + long batchId = message.getId(); + //遍历每条消息 + for (CanalEntry.Entry entry : message.getEntries()) { + session(entry);//no exception + } + //ack all the time。 + connector.ack(batchId); + } + + private void session(CanalEntry.Entry entry) { + CanalEntry.EntryType entryType = entry.getEntryType(); + int times = 0; + boolean success = false; + while (!success) { + if (times > 0) { + /** + * 1:retry,重试,重试默认为3次,由retryTimes参数决定,如果重试次数达到阈值,则跳过,并且记录日志。 + * 2:ignore,直接忽略,不重试,记录日志。 + */ + if (exceptionStrategy == ExceptionStrategy.RETRY.code) { + if (times >= retryTimes) { + break; + } + } else { + break; + } + } + try { + switch (entryType) { + case TRANSACTIONBEGIN: + transactionBegin(entry); + break; + case TRANSACTIONEND: + transactionEnd(entry); + break; + case ROWDATA: + rowData(entry); + break; + default: + break; + } + success = true; + } catch (Exception e) { + times++; + logger.error("parse event has an error ,times: + " + times + ", data:" + entry.toString(), e); + } + + } + } + + private void rowData(CanalEntry.Entry entry) throws Exception { + CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + CanalEntry.EventType eventType = rowChange.getEventType(); + CanalEntry.Header header = entry.getHeader(); + long executeTime = header.getExecuteTime(); + long delayTime = new Date().getTime() - executeTime; + String sql = rowChange.getSql(); + + try { + if (!isDML(eventType) || rowChange.getIsDdl()) { + processDDL(header, eventType, sql); + return; + } + //处理DML数据 + processDML(header, eventType, rowChange, sql); + } catch (Exception e) { + logger.error("process event error ,", e); + logger.error(rowFormat, + new Object[]{header.getLogfileName(), String.valueOf(header.getLogfileOffset()), + header.getSchemaName(), header.getTableName(), eventType, + String.valueOf(executeTime), String.valueOf(delayTime)}); + throw e;//重新抛出 + } + } + + /** + * 处理 dml 数据 + * + * @param header + * @param eventType + * @param rowChange + * @param sql + */ + protected void processDML(CanalEntry.Header header, CanalEntry.EventType eventType, CanalEntry.RowChange rowChange, String sql) { + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + switch (eventType) { + case DELETE: + delete(header, rowData.getBeforeColumnsList()); + break; + case INSERT: + insert(header, rowData.getAfterColumnsList()); + break; + case UPDATE: + update(header, rowData.getAfterColumnsList()); + break; + default: + whenOthers(header, sql); + } + } + } + +} + + + + diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java new file mode 100644 index 00000000..844f60db --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java @@ -0,0 +1,488 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.Message; +import org.apache.commons.lang.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.CollectionUtils; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public abstract class CanalConnectorClient extends AbstractCanalLifeCycle implements InitializingBean { + + protected static final Logger logger = LoggerFactory.getLogger(CanalConnectorClient.class); + protected static final String SEP = SystemUtils.LINE_SEPARATOR; + protected static String contextFormat; + protected static String rowFormat; + protected static String transactionFormat; + protected static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + static { + StringBuilder sb = new StringBuilder(); + sb.append(SEP) + .append("-------------Batch-------------") + .append(SEP) + .append("* Batch Id: [{}] ,count : [{}] , Mem size : [{}] , Time : {}") + .append(SEP) + .append("* Start : [{}] ") + .append(SEP) + .append("* End : [{}] ") + .append(SEP) + .append("-------------------------------") + .append(SEP); + contextFormat = sb.toString(); + + sb = new StringBuilder(); + sb.append(SEP) + .append("+++++++++++++Row+++++++++++++>>>") + .append("binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms") + .append(SEP); + rowFormat = sb.toString(); + + sb = new StringBuilder(); + sb.append(SEP) + .append("===========Transaction {} : {}=======>>>") + .append("binlog[{}:{}] , executeTime : {} , delay : {}ms") + .append(SEP); + transactionFormat = sb.toString(); + } + + private String zkServers;//cluster + private String address;//single,ip:port + private String destination; + private String username; + private String password; + private int batchSize = 5 * 1024; + private String filter = "";//同canal filter,用于过滤database或者table的相关数据。 + protected boolean debug = false;//开启debug,会把每条消息的详情打印 + + //1:retry,重试,重试默认为3次,由retryTimes参数决定,如果重试次数达到阈值,则跳过,并且记录日志。 + //2:ignore,直接忽略,不重试,记录日志。 + protected int exceptionStrategy = 1; + protected int retryTimes = 3; + protected int waitingTime = 100;//当binlog没有数据时,主线程等待的时间,单位ms,大于0 + + + protected CanalConnector connector; + protected Thread thread; + + protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { + + public void uncaughtException(Thread t, Throwable e) { + logger.error("process message has an error", e); + } + }; + + @Override + public void afterPropertiesSet() { + if (waitingTime <= 0) { + throw new IllegalArgumentException("waitingTime must be greater than 0"); + } + if (ExceptionStrategy.codeOf(exceptionStrategy) == null) { + throw new IllegalArgumentException("exceptionStrategy is not valid,1 or 2"); + } + start(); + } + + @Override + public void start() { + if (running) { + return; + } + super.start(); + initConnector(); + + thread = new Thread(new Runnable() { + + public void run() { + process(); + } + }); + + thread.setUncaughtExceptionHandler(handler); + thread.start(); + } + + @Override + public void stop() { + if (!running) { + return; + } + super.stop(); + quietlyStop(thread); + } + + protected void quietlyStop(Thread task) { + if (task != null) { + task.interrupt(); + try { + task.join(); + } catch (InterruptedException e) { + // ignore + } + } + } + + public void process() { + int times = 0; + while (running) { + try { + sleepWhenFailed(times); + //after block, should check the status of thread. + if (!running) { + break; + } + MDC.put("destination", destination); + connector.connect(); + connector.subscribe(filter); + connector.rollback(); + times = 0;//reset; + + while (running) { + // 获取指定数量的数据,不确认 + Message message = connector.getWithoutAck(batchSize); + + long batchId = message.getId(); + int size = message.getEntries().size(); + + if (batchId == -1 || size == 0) { + try { + Thread.sleep(waitingTime); + } catch (InterruptedException e) { + // + } + continue; + } + //logger + printBatch(message, batchId); + + processMessage(message); + + } + } catch (Exception e) { + logger.error("process error!", e); + if (times > 20) { + times = 0; + } + times++; + } finally { + connector.disconnect(); + MDC.remove("destination"); + } + } + } + + protected abstract void processMessage(Message message); + + + private void initConnector() { + if (zkServers != null && zkServers.length() > 0) { + connector = CanalConnectors.newClusterConnector(zkServers, destination, username, password); + } else if (address != null) { + String[] segments = address.split(":"); + SocketAddress socketAddress = new InetSocketAddress(segments[0], Integer.valueOf(segments[1])); + connector = CanalConnectors.newSingleConnector(socketAddress, destination, username, password); + } else { + throw new IllegalArgumentException("zkServers or address cant be null at same time,you should specify one of them!"); + } + + } + + /** + * 用于控制当连接异常时,重试的策略,我们不应该每次都是立即重试,否则将可能导致大量的错误,在空转时导致CPU过高的问题 + * sleep策略基于简单的累加 + * + * @param times + */ + private void sleepWhenFailed(int times) { + if (times <= 0) { + return; + } + try { + int sleepTime = 1000 + times * 100;//最大sleep 3s。 + Thread.sleep(sleepTime); + } catch (Exception ex) { + // + } + } + + /** + * 打印当前batch的摘要信息 + * + * @param message + * @param batchId + */ + protected void printBatch(Message message, long batchId) { + if (!debug) { + return; + } + List entries = message.getEntries(); + if (CollectionUtils.isEmpty(entries)) { + return; + } + + long memSize = 0; + for (CanalEntry.Entry entry : entries) { + memSize += entry.getHeader().getEventLength(); + } + int size = entries.size(); + String startPosition = buildPosition(entries.get(0)); + String endPosition = buildPosition(message.getEntries().get(size - 1)); + + SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); + logger.info(contextFormat, new Object[]{batchId, size, memSize, format.format(new Date()), startPosition, endPosition}); + } + + protected String buildPosition(CanalEntry.Entry entry) { + CanalEntry.Header header = entry.getHeader(); + long time = header.getExecuteTime(); + Date date = new Date(time); + SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); + StringBuilder sb = new StringBuilder(); + sb.append(header.getLogfileName()) + .append(":") + .append(header.getLogfileOffset()) + .append(":") + .append(header.getExecuteTime()) + .append("(") + .append(format.format(date)) + .append(")"); + return sb.toString(); + } + + /** + * default,only logging information + * + * @param entry + */ + protected void transactionBegin(CanalEntry.Entry entry) { + if (!debug) { + return; + } + try { + CanalEntry.TransactionBegin begin = CanalEntry.TransactionBegin.parseFrom(entry.getStoreValue()); + // 打印事务头信息,执行的线程id,事务耗时 + CanalEntry.Header header = entry.getHeader(); + long executeTime = header.getExecuteTime(); + long delayTime = new Date().getTime() - executeTime; + logger.info(transactionFormat, + new Object[]{"begin", begin.getTransactionId(), header.getLogfileName(), + String.valueOf(header.getLogfileOffset()), + String.valueOf(header.getExecuteTime()), String.valueOf(delayTime)}); + } catch (Exception e) { + logger.error("parse event has an error , data:" + entry.toString(), e); + } + } + + protected void transactionEnd(CanalEntry.Entry entry) { + if (!debug) { + return; + } + try { + CanalEntry.TransactionEnd end = CanalEntry.TransactionEnd.parseFrom(entry.getStoreValue()); + // 打印事务提交信息,事务id + CanalEntry.Header header = entry.getHeader(); + long executeTime = header.getExecuteTime(); + long delayTime = new Date().getTime() - executeTime; + + logger.info(transactionFormat, + new Object[]{"end", end.getTransactionId(), header.getLogfileName(), + String.valueOf(header.getLogfileOffset()), + String.valueOf(header.getExecuteTime()), String.valueOf(delayTime)}); + } catch (Exception e) { + logger.error("parse event has an error , data:" + entry.toString(), e); + } + } + + /** + * 判断事件类型为DML 数据 + * + * @param eventType + * @return + */ + protected boolean isDML(CanalEntry.EventType eventType) { + switch (eventType) { + case INSERT: + case UPDATE: + case DELETE: + return true; + default: + return false; + } + } + + /** + * 处理 DDL数据 + * + * @param header + * @param eventType + * @param sql + */ + + protected void processDDL(CanalEntry.Header header, CanalEntry.EventType eventType, String sql) { + if (!debug) { + return; + } + String table = header.getSchemaName() + "." + header.getTableName(); + //对于DDL,直接执行,因为没有行变更数据 + switch (eventType) { + case CREATE: + logger.warn("parse create table event, table: {}, sql: {}", table, sql); + return; + case ALTER: + logger.warn("parse alter table event, table: {}, sql: {}", table, sql); + return; + case TRUNCATE: + logger.warn("parse truncate table event, table: {}, sql: {}", table, sql); + return; + case ERASE: + case QUERY: + logger.warn("parse event : {}, sql: {} . ignored!", eventType.name(), sql); + return; + case RENAME: + logger.warn("parse rename table event, table: {}, sql: {}", table, sql); + return; + case CINDEX: + logger.warn("parse create index event, table: {}, sql: {}", table, sql); + return; + case DINDEX: + logger.warn("parse delete index event, table: {}, sql: {}", table, sql); + return; + default: + logger.warn("parse unknown event: {}, table: {}, sql: {}", new String[]{eventType.name(), table, sql}); + break; + } + } + + /** + * 强烈建议捕获异常,非上述已列出的其他操作,非核心 + * 除了“insert”、“update”、“delete”操作之外的,其他类型的操作. + * 默认实现为“无操作” + * + * @param header 可以从header中获得schema、table的名称 + * @param sql + */ + public void whenOthers(CanalEntry.Header header, String sql) { + String schema = header.getSchemaName(); + String table = header.getTableName(); + logger.error("ignore event,schema: {},table: {},SQL: {}", new String[]{schema, table, sql}); + } + + public enum ExceptionStrategy { + RETRY(1), IGNORE(2); + public int code; + + ExceptionStrategy(int code) { + this.code = code; + } + + public static ExceptionStrategy codeOf(Integer code) { + if (code != null) { + for (ExceptionStrategy e : ExceptionStrategy.values()) { + if (e.code == code) { + return e; + } + } + } + return null; + } + } + + public String getZkServers() { + return zkServers; + } + + public void setZkServers(String zkServers) { + this.zkServers = zkServers; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getBatchSize() { + return batchSize; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter; + } + + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + + public int getExceptionStrategy() { + return exceptionStrategy; + } + + public void setExceptionStrategy(int exceptionStrategy) { + this.exceptionStrategy = exceptionStrategy; + } + + public int getRetryTimes() { + return retryTimes; + } + + public void setRetryTimes(int retryTimes) { + this.retryTimes = retryTimes; + } + + public int getWaitingTime() { + return waitingTime; + } + + public void setWaitingTime(int waitingTime) { + this.waitingTime = waitingTime; + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java b/example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java new file mode 100644 index 00000000..b9936106 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java @@ -0,0 +1,35 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.example.db.mysql.MysqlClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MysqlLoadLauncher { + private static final Logger logger = LoggerFactory.getLogger(MysqlLoadLauncher.class); + + public static void main(String[] args) { + try { + logger.info("## start the canal mysql client."); + final MysqlClient client = ServiceLocator.getMysqlClient(); + logger.info("## the canal consumer is running now ......"); + client.start(); + Runtime.getRuntime().addShutdownHook(new Thread() { + + public void run() { + try { + logger.info("## stop the canal consumer"); + client.stop(); + } catch (Throwable e) { + logger.warn("##something goes wrong when stopping canal consumer:\n{}", e); + } finally { + logger.info("## canal consumer is down."); + } + } + + }); + } catch (Throwable e) { + logger.error("## Something goes wrong when starting up the canal consumer:\n{}", e); + System.exit(0); + } + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java b/example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java new file mode 100644 index 00000000..39ad4c62 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java @@ -0,0 +1,44 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.example.db.mysql.MysqlClient; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.util.Assert; + +public class ServiceLocator implements DisposableBean { + + private static ApplicationContext applicationContext = null; + + static { + try { + applicationContext = new ClassPathXmlApplicationContext("classpath:client-spring.xml"); + } catch (RuntimeException e) { + throw e; + } + } + + private static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + + private static void clearHolder() { + ServiceLocator.applicationContext = null; + } + + @Override + public void destroy() throws Exception { + ServiceLocator.clearHolder(); + } + + private static void assertContextInjected() { + Assert.state(applicationContext != null, "ApplicationContext not set"); + } + + + public static MysqlClient getMysqlClient() { + return getBean("mysqlClient"); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java new file mode 100644 index 00000000..ec99b316 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect; + +import com.alibaba.otter.canal.example.db.utils.DdlUtils; +import com.google.common.base.Function; +import com.google.common.collect.MigrateMap; +import org.apache.commons.lang.exception.NestableRuntimeException; +import org.apache.ddlutils.model.Table; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.Assert; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public abstract class AbstractDbDialect implements DbDialect { + + protected int databaseMajorVersion; + protected int databaseMinorVersion; + protected String databaseName; + protected JdbcTemplate jdbcTemplate; + protected TransactionTemplate transactionTemplate; + protected LobHandler lobHandler; + protected Map, Table> tables; + + public AbstractDbDialect(final JdbcTemplate jdbcTemplate, LobHandler lobHandler) { + this.jdbcTemplate = jdbcTemplate; + this.lobHandler = lobHandler; + // 初始化transction + this.transactionTemplate = new TransactionTemplate(); + transactionTemplate.setTransactionManager(new DataSourceTransactionManager(jdbcTemplate.getDataSource())); + transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + // 初始化一些数据 + jdbcTemplate.execute(new ConnectionCallback() { + + public Object doInConnection(Connection c) throws SQLException, DataAccessException { + DatabaseMetaData meta = c.getMetaData(); + databaseName = meta.getDatabaseProductName(); + databaseMajorVersion = meta.getDatabaseMajorVersion(); + databaseMinorVersion = meta.getDatabaseMinorVersion(); + + return null; + } + }); + + initTables(jdbcTemplate); + } + + public Table findTable(String schema, String table, boolean useCache) { + List key = Arrays.asList(schema, table); + if (useCache == false) { + tables.remove(key); + } + + return tables.get(key); + } + + public Table findTable(String schema, String table) { + return findTable(schema, table, true); + } + + public LobHandler getLobHandler() { + return lobHandler; + } + + public JdbcTemplate getJdbcTemplate() { + return jdbcTemplate; + } + + public TransactionTemplate getTransactionTemplate() { + return transactionTemplate; + } + + private void initTables(final JdbcTemplate jdbcTemplate) { + this.tables = MigrateMap.makeComputingMap(new Function, Table>() { + + public Table apply(List names) { + Assert.isTrue(names.size() == 2); + try { + Table table = DdlUtils.findTable(jdbcTemplate, names.get(0), names.get(0), names.get(1)); + if (table == null) { + throw new NestableRuntimeException("no found table [" + names.get(0) + "." + names.get(1) + + "] , pls check"); + } else { + return table; + } + } catch (Exception e) { + throw new NestableRuntimeException("find table [" + names.get(0) + "." + names.get(1) + "] error", + e); + } + } + }); + } + + +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java new file mode 100644 index 00000000..fbbecabe --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect; + +/** + * 默认的基于标准SQL实现的CRUD sql封装 + * + * @author jianghang 2011-10-27 下午01:37:00 + * @version 4.0.0 + */ +public abstract class AbstractSqlTemplate implements SqlTemplate { + + private static final String DOT = "."; + + public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { + StringBuilder sql = new StringBuilder("select "); + int size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(columnNames[i])).append((i + 1 < size) ? " , " : ""); + } + + sql.append(" from ").append(getFullName(schemaName, tableName)).append(" where ( "); + appendColumnEquals(sql, pkNames, "and"); + sql.append(" ) "); + return sql.toString().intern();// 不使用intern,避免方法区内存消耗过多 + } + + public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { + StringBuilder sql = new StringBuilder("update " + getFullName(schemaName, tableName) + " set "); + appendColumnEquals(sql, columnNames, ","); + sql.append(" where ("); + appendColumnEquals(sql, pkNames, "and"); + sql.append(")"); + return sql.toString().intern(); // 不使用intern,避免方法区内存消耗过多 + } + + public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { + StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "("); + String[] allColumns = new String[pkNames.length + columnNames.length]; + System.arraycopy(columnNames, 0, allColumns, 0, columnNames.length); + System.arraycopy(pkNames, 0, allColumns, columnNames.length, pkNames.length); + + int size = allColumns.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(allColumns[i])).append((i + 1 < size) ? "," : ""); + } + + sql.append(") values ("); + appendColumnQuestions(sql, allColumns); + sql.append(")"); + return sql.toString().intern();// intern优化,避免出现大量相同的字符串 + } + + public String getDeleteSql(String schemaName, String tableName, String[] pkNames) { + StringBuilder sql = new StringBuilder("delete from " + getFullName(schemaName, tableName) + " where "); + appendColumnEquals(sql, pkNames, "and"); + return sql.toString().intern();// intern优化,避免出现大量相同的字符串 + } + + protected String getFullName(String schemaName, String tableName) { + StringBuilder sb = new StringBuilder(); + if (schemaName != null) { + sb.append(appendEscape(schemaName)).append(DOT); + } + sb.append(appendEscape(tableName)); + return sb.toString().intern(); + } + + // ================ helper method ============ + + protected String appendEscape(String columnName) { + return columnName; + } + + protected void appendColumnQuestions(StringBuilder sql, String[] columns) { + int size = columns.length; + for (int i = 0; i < size; i++) { + sql.append("?").append((i + 1 < size) ? " , " : ""); + } + } + + protected void appendColumnEquals(StringBuilder sql, String[] columns, String separator) { + int size = columns.length; + for (int i = 0; i < size; i++) { + sql.append(" ").append(appendEscape(columns[i])).append(" = ").append("? "); + if (i != size - 1) { + sql.append(separator); + } + } + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java new file mode 100644 index 00000000..060f476d --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java @@ -0,0 +1,20 @@ +package com.alibaba.otter.canal.example.db.dialect; + +import org.apache.ddlutils.model.Table; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.transaction.support.TransactionTemplate; + +public interface DbDialect { + + LobHandler getLobHandler(); + + JdbcTemplate getJdbcTemplate(); + + TransactionTemplate getTransactionTemplate(); + + Table findTable(String schema, String table); + + Table findTable(String schema, String table, boolean useCache); + +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java new file mode 100644 index 00000000..a90bd5ce --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect; + +/** + * sql构造模板操作 + * + * @author jianghang 2011-10-27 下午01:31:15 + * @version 4.0.0 + */ +public interface SqlTemplate { + + public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); + + public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); + + public String getDeleteSql(String schemaName, String tableName, String[] pkNames); + + public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); + + /** + * 获取对应的mergeSql + */ + public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames, + String[] viewColumnNames, boolean updatePks); +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java new file mode 100644 index 00000000..58cf662a --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java @@ -0,0 +1,93 @@ +package com.alibaba.otter.canal.example.db.dialect; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * An enumeration wrapper around JDBC table types. + */ +public enum TableType { + + /** + * Unknown + */ + unknown, + + /** + * System table + */ + system_table, + + /** + * Global temporary + */ + global_temporary, + + /** + * Local temporary + */ + local_temporary, + + /** + * Table + */ + table, + + /** + * View + */ + view, + + /** + * Alias + */ + alias, + + /** + * Synonym + */ + synonym,; + + /** + * Converts an array of table types to an array of their corresponding string values. + * + * @param tableTypes Array of table types + * @return Array of string table types + */ + public static String[] toStrings(final TableType[] tableTypes) { + if ((tableTypes == null) || (tableTypes.length == 0)) { + return new String[0]; + } + + final List tableTypeStrings = new ArrayList(tableTypes.length); + + for (final TableType tableType : tableTypes) { + if (tableType != null) { + tableTypeStrings.add(tableType.toString().toUpperCase(Locale.ENGLISH)); + } + } + + return tableTypeStrings.toArray(new String[tableTypeStrings.size()]); + } + + /** + * Converts an array of string table types to an array of their corresponding enumeration values. + * + * @param tableTypeStrings Array of string table types + * @return Array of table types + */ + public static TableType[] valueOf(final String[] tableTypeStrings) { + if ((tableTypeStrings == null) || (tableTypeStrings.length == 0)) { + return new TableType[0]; + } + + final List tableTypes = new ArrayList(tableTypeStrings.length); + + for (final String tableTypeString : tableTypeStrings) { + tableTypes.add(valueOf(tableTypeString.toLowerCase(Locale.ENGLISH))); + } + + return tableTypes.toArray(new TableType[tableTypes.size()]); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java new file mode 100644 index 00000000..7bdfe1df --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect.mysql; + +import com.alibaba.otter.canal.example.db.dialect.AbstractDbDialect; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.lob.LobHandler; + +public class MysqlDialect extends AbstractDbDialect { + + public MysqlDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler) { + super(jdbcTemplate, lobHandler); + } + + public boolean isEmptyStringNulled() { + return false; + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java new file mode 100644 index 00000000..0cf987ff --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect.mysql; + +import com.alibaba.otter.canal.example.db.dialect.AbstractSqlTemplate; + +/** + * mysql sql生成模板 + * + * @author jianghang 2011-10-27 下午01:41:20 + * @version 4.0.0 + */ +public class MysqlSqlTemplate extends AbstractSqlTemplate { + + private static final String ESCAPE = "`"; + + public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames, + String[] viewColumnNames, boolean includePks) { + StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "("); + int size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(columnNames[i])).append(" , "); + } + size = pkNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(pkNames[i])).append((i + 1 < size) ? " , " : ""); + } + + sql.append(") values ("); + size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append("?").append(" , "); + } + size = pkNames.length; + for (int i = 0; i < size; i++) { + sql.append("?").append((i + 1 < size) ? " , " : ""); + } + sql.append(")"); + sql.append(" on duplicate key update "); + + size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(columnNames[i])) + .append("=values(") + .append(appendEscape(columnNames[i])) + .append(")"); + if (includePks) { + sql.append(" , "); + } else { + sql.append((i + 1 < size) ? " , " : ""); + } + } + + if (includePks) { + // mysql merge sql匹配了uniqe / primary key时都会执行update,所以需要更新pk信息 + size = pkNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(pkNames[i])).append("=values(").append(appendEscape(pkNames[i])).append(")"); + sql.append((i + 1 < size) ? " , " : ""); + } + } + + return sql.toString().intern();// intern优化,避免出现大量相同的字符串 + } + + protected String appendEscape(String columnName) { + return ESCAPE + columnName + ESCAPE; + } + +} \ No newline at end of file diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java new file mode 100644 index 00000000..608b81e6 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java @@ -0,0 +1,207 @@ +package com.alibaba.otter.canal.example.db.mysql; + +import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.example.db.AbstractDbClient; +import com.alibaba.otter.canal.example.db.dialect.DbDialect; +import com.alibaba.otter.canal.example.db.dialect.mysql.MysqlDialect; +import com.alibaba.otter.canal.example.db.dialect.mysql.MysqlSqlTemplate; +import com.alibaba.otter.canal.example.db.dialect.SqlTemplate; +import com.alibaba.otter.canal.example.db.utils.SqlUtils; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import org.apache.commons.lang.StringUtils; +import org.apache.ddlutils.model.Column; +import org.apache.ddlutils.model.Table; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.StatementCreatorUtils; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; + +import javax.sql.DataSource; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public abstract class AbstractMysqlClient extends AbstractDbClient { + + private DataSource dataSource; + + private DbDialect dbDialect; + private SqlTemplate sqlTemplate; + + protected Integer execute(final CanalEntry.Header header, final List columns) { + final String sql = getSql(header, columns); + final LobCreator lobCreator = dbDialect.getLobHandler().getLobCreator(); + dbDialect.getTransactionTemplate().execute(new TransactionCallback() { + + public Object doInTransaction(TransactionStatus status) { + try { + JdbcTemplate template = dbDialect.getJdbcTemplate(); + int affect = template.update(sql, new PreparedStatementSetter() { + + public void setValues(PreparedStatement ps) throws SQLException { + doPreparedStatement(ps, dbDialect, lobCreator, header, columns); + } + }); + return affect; + } finally { + lobCreator.close(); + } + } + }); + return 0; + } + + private String getSql(CanalEntry.Header header, List columns) { + List pkNames = new ArrayList<>(); + List colNames = new ArrayList<>(); + for (CanalEntry.Column column : columns) { + if (column.getIsKey()) { + pkNames.add(column.getName()); + } else { + colNames.add(column.getName()); + } + } + String sql = ""; + CanalEntry.EventType eventType = header.getEventType(); + switch (eventType) { + case INSERT: + sql = sqlTemplate.getInsertSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{}), colNames.toArray(new String[]{})); + break; + case UPDATE: + sql = sqlTemplate.getUpdateSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{}), colNames.toArray(new String[]{})); + break; + case DELETE: + sql = sqlTemplate.getDeleteSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{})); + } + logger.info("Execute sql: {}", sql); + return sql; + } + + private void doPreparedStatement(PreparedStatement ps, DbDialect dbDialect, LobCreator lobCreator, + CanalEntry.Header header, List columns) throws SQLException { + + List rebuildColumns = new ArrayList<>(columns.size()); + + List keyColumns = new ArrayList<>(columns.size()); + List notKeyColumns = new ArrayList<>(columns.size()); + for (CanalEntry.Column column : columns) { + if (column.getIsKey()) { + keyColumns.add(column); + } else { + notKeyColumns.add(column); + } + } + CanalEntry.EventType eventType = header.getEventType(); + switch (eventType) { + case INSERT: + case UPDATE: + // insert/update语句对应的字段数序都是将主键排在后面 + rebuildColumns.addAll(notKeyColumns); + rebuildColumns.addAll(keyColumns); + break; + case DELETE: + rebuildColumns.addAll(keyColumns); + } + + // 获取一下当前字段名的数据是否必填 + Table table = dbDialect.findTable(header.getSchemaName(), header.getTableName()); + Map isRequiredMap = new HashMap(); + for (Column tableColumn : table.getColumns()) { + isRequiredMap.put(StringUtils.lowerCase(tableColumn.getName()), tableColumn.isRequired()); + } + + List values = new ArrayList<>(rebuildColumns.size()); + for (int i = 0; i < rebuildColumns.size(); i++) { + int paramIndex = i + 1; + CanalEntry.Column column = rebuildColumns.get(i); + int sqlType = column.getSqlType(); + + Boolean isRequired = isRequiredMap.get(StringUtils.lowerCase(column.getName())); + if (isRequired == null) { + // 清理一下目标库的表结构,二次检查一下 + table = dbDialect.findTable(header.getSchemaName(), header.getTableName()); + + isRequiredMap = new HashMap<>(); + for (Column tableColumn : table.getColumns()) { + isRequiredMap.put(StringUtils.lowerCase(tableColumn.getName()), tableColumn.isRequired()); + } + + isRequired = isRequiredMap.get(StringUtils.lowerCase(column.getName())); + if (isRequired == null) { + throw new CanalClientException(String.format("column name %s is not found in Table[%s]", + column.getName(), + table.toString())); + } + } + + Object param; + if (sqlType == Types.TIME || sqlType == Types.TIMESTAMP || sqlType == Types.DATE) { + // 解决mysql的0000-00-00 00:00:00问题,直接依赖mysql + // driver进行处理,如果转化为Timestamp会出错 + param = column.getValue(); + if (param instanceof String && StringUtils.isEmpty(String.valueOf(param))) { + param = null; + } + } else { + param = SqlUtils.stringToSqlValue(column.getValue(), + sqlType, + isRequired, + column.getIsNull()); + } + + try { + switch (sqlType) { + case Types.CLOB: + lobCreator.setClobAsString(ps, paramIndex, (String) param); + break; + case Types.BLOB: + lobCreator.setBlobAsBytes(ps, paramIndex, (byte[]) param); + break; + case Types.TIME: + case Types.TIMESTAMP: + case Types.DATE: + ps.setObject(paramIndex, param); + break; + case Types.BIT: + StatementCreatorUtils.setParameterValue(ps, paramIndex, Types.DECIMAL, null, param); + break; + default: + StatementCreatorUtils.setParameterValue(ps, paramIndex, sqlType, null, param); + break; + } + values.add(param); + } catch (SQLException ex) { + logger.error("## SetParam error , [sqltype={}, value={}]", + new Object[]{sqlType, param}); + throw ex; + } + } + logger.info("## sql values: {}", JSON.toJSONString(values)); + } + + @Override + public void afterPropertiesSet() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + DefaultLobHandler lobHandler = new DefaultLobHandler(); + lobHandler.setStreamAsLob(true); + dbDialect = new MysqlDialect(jdbcTemplate, lobHandler); + sqlTemplate = new MysqlSqlTemplate(); + } + + public DataSource getDataSource() { + return dataSource; + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java new file mode 100644 index 00000000..f16b6146 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java @@ -0,0 +1,23 @@ +package com.alibaba.otter.canal.example.db.mysql; + +import com.alibaba.otter.canal.protocol.CanalEntry; + +import java.util.List; + +public class MysqlClient extends AbstractMysqlClient { + + @Override + public void insert(CanalEntry.Header header, List afterColumns) { + execute(header, afterColumns); + } + + @Override + public void update(CanalEntry.Header header, List afterColumns) { + execute(header, afterColumns); + } + + @Override + public void delete(CanalEntry.Header header, List beforeColumns) { + execute(header, beforeColumns); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java new file mode 100644 index 00000000..6bd33686 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java @@ -0,0 +1,50 @@ +package com.alibaba.otter.canal.example.db.utils; + +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.Converter; +import org.apache.commons.beanutils.converters.ArrayConverter; +import org.apache.commons.beanutils.converters.ByteConverter; + +public class ByteArrayConverter implements Converter { + + public static final Converter SQL_BYTES = new ByteArrayConverter(null); + private static final Converter converter = new ArrayConverter(byte[].class, new ByteConverter()); + + protected final Object defaultValue; + protected final boolean useDefault; + + public ByteArrayConverter() { + this.defaultValue = null; + this.useDefault = false; + } + + public ByteArrayConverter(Object defaultValue) { + this.defaultValue = defaultValue; + this.useDefault = true; + } + + public Object convert(Class type, Object value) { + if (value == null) { + if (useDefault) { + return (defaultValue); + } else { + throw new ConversionException("No value specified"); + } + } + + if (value instanceof byte[]) { + return (value); + } + + // BLOB类型,canal直接存储为String("ISO-8859-1") + if (value instanceof String) { + try { + return ((String) value).getBytes("ISO-8859-1"); + } catch (Exception e) { + throw new ConversionException(e); + } + } + + return converter.convert(type, value); // byteConvertor进行转化 + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java new file mode 100644 index 00000000..7964b18d --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java @@ -0,0 +1,326 @@ +package com.alibaba.otter.canal.example.db.utils; + +import com.alibaba.otter.canal.example.db.dialect.TableType; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.apache.commons.lang.math.NumberUtils; +import org.apache.ddlutils.model.Column; +import org.apache.ddlutils.model.Table; +import org.apache.ddlutils.platform.DatabaseMetaDataWrapper; +import org.apache.ddlutils.platform.MetaDataColumnDescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.JdbcUtils; + +import java.sql.*; +import java.util.*; + + +public class DdlUtils { + + private static final Logger logger = LoggerFactory.getLogger(DdlUtils.class); + private static TableType[] SUPPORTED_TABLE_TYPES = new TableType[]{TableType.view, TableType.table}; + private final static Map _defaultSizes = new HashMap(); + + static { + _defaultSizes.put(new Integer(1), "254"); + _defaultSizes.put(new Integer(12), "254"); + _defaultSizes.put(new Integer(-1), "254"); + _defaultSizes.put(new Integer(-2), "254"); + _defaultSizes.put(new Integer(-3), "254"); + _defaultSizes.put(new Integer(-4), "254"); + _defaultSizes.put(new Integer(4), "32"); + _defaultSizes.put(new Integer(-5), "64"); + _defaultSizes.put(new Integer(7), "7,0"); + _defaultSizes.put(new Integer(6), "15,0"); + _defaultSizes.put(new Integer(8), "15,0"); + _defaultSizes.put(new Integer(3), "15,15"); + _defaultSizes.put(new Integer(2), "15,15"); + } + + + public static Table findTable(final JdbcTemplate jdbcTemplate, final String catalogName, final String schemaName, + final String tableName) { + return (Table) jdbcTemplate.execute(new ConnectionCallback() { + + public Object doInConnection(Connection con) throws SQLException, DataAccessException { + Table table = null; + DatabaseMetaDataWrapper metaData = new DatabaseMetaDataWrapper(); + try { + + DatabaseMetaData databaseMetaData = con.getMetaData(); + + metaData.setMetaData(databaseMetaData); + metaData.setTableTypes(TableType.toStrings(SUPPORTED_TABLE_TYPES)); + metaData.setCatalog(catalogName); + metaData.setSchemaPattern(schemaName); + + String convertTableName = tableName; + if (databaseMetaData.storesUpperCaseIdentifiers()) { + metaData.setCatalog(catalogName.toUpperCase()); + metaData.setSchemaPattern(schemaName.toUpperCase()); + convertTableName = tableName.toUpperCase(); + } + if (databaseMetaData.storesLowerCaseIdentifiers()) { + metaData.setCatalog(catalogName.toLowerCase()); + metaData.setSchemaPattern(schemaName.toLowerCase()); + convertTableName = tableName.toLowerCase(); + } + + ResultSet tableData = null; + try { + tableData = metaData.getTables(convertTableName); + + while ((tableData != null) && tableData.next()) { + Map values = readColumns(tableData, initColumnsForTable()); + + table = readTable(metaData, values); + if (table.getName().equalsIgnoreCase(tableName)) { + break; + } + } + } finally { + JdbcUtils.closeResultSet(tableData); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + + makeAllColumnsPrimaryKeysIfNoPrimaryKeysFound(table); + return table; + } + }); + } + + /** + * Treat tables with no primary keys as a table with all primary keys. + */ + private static void makeAllColumnsPrimaryKeysIfNoPrimaryKeysFound(Table table) { + if ((table != null) && (table.getPrimaryKeyColumns() != null) && (table.getPrimaryKeyColumns().length == 0)) { + Column[] allCoumns = table.getColumns(); + + for (Column column : allCoumns) { + column.setPrimaryKey(true); + } + } + } + + private static Table readTable(DatabaseMetaDataWrapper metaData, Map values) throws SQLException { + String tableName = (String) values.get("TABLE_NAME"); + Table table = null; + + if ((tableName != null) && (tableName.length() > 0)) { + table = new Table(); + table.setName(tableName); + table.setType((String) values.get("TABLE_TYPE")); + table.setCatalog((String) values.get("TABLE_CAT")); + table.setSchema((String) values.get("TABLE_SCHEM")); + table.setDescription((String) values.get("REMARKS")); + table.addColumns(readColumns(metaData, tableName)); + + Collection primaryKeys = readPrimaryKeyNames(metaData, tableName); + + for (Object key : primaryKeys) { + Column col = table.findColumn((String) key, true); + + if (col != null) { + col.setPrimaryKey(true); + } else { + throw new NullPointerException(String.format("%s pk %s is null - %s %s", + tableName, + key, + ToStringBuilder.reflectionToString(metaData, ToStringStyle.SIMPLE_STYLE), + ToStringBuilder.reflectionToString(values, ToStringStyle.SIMPLE_STYLE))); + } + } + } + + return table; + } + + private static List initColumnsForTable() { + List result = new ArrayList(); + + result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("TABLE_TYPE", Types.VARCHAR, "UNKNOWN")); + result.add(new MetaDataColumnDescriptor("TABLE_CAT", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("TABLE_SCHEM", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR)); + + return result; + } + + private static List initColumnsForColumn() { + List result = new ArrayList(); + + // As suggested by Alexandre Borgoltz, we're reading the COLUMN_DEF + // first because Oracle + // has problems otherwise (it seemingly requires a LONG column to be the + // first to be read) + // See also DDLUTILS-29 + result.add(new MetaDataColumnDescriptor("COLUMN_DEF", Types.VARCHAR)); + + // we're also reading the table name so that a model reader impl can + // filter manually + result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("TYPE_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("DATA_TYPE", Types.INTEGER, new Integer(Types.OTHER))); + result.add(new MetaDataColumnDescriptor("NUM_PREC_RADIX", Types.INTEGER, new Integer(10))); + result.add(new MetaDataColumnDescriptor("DECIMAL_DIGITS", Types.INTEGER, new Integer(0))); + result.add(new MetaDataColumnDescriptor("COLUMN_SIZE", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("IS_NULLABLE", Types.VARCHAR, "YES")); + result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR)); + + return result; + } + + private static List initColumnsForPK() { + List result = new ArrayList(); + + result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR)); + + // we're also reading the table name so that a model reader impl can + // filter manually + result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); + + // the name of the primary key is currently only interesting to the pk + // index name resolution + result.add(new MetaDataColumnDescriptor("PK_NAME", Types.VARCHAR)); + + return result; + } + + private static List readColumns(DatabaseMetaDataWrapper metaData, String tableName) throws SQLException { + ResultSet columnData = null; + + try { + columnData = metaData.getColumns(tableName, null); + + List columns = new ArrayList(); + Map values; + + for (; columnData.next(); columns.add(readColumn(metaData, values))) { + Map tmp = readColumns(columnData, initColumnsForColumn()); + if (tableName.equalsIgnoreCase((String) tmp.get("TABLE_NAME"))) { + values = tmp; + } else { + break; + } + } + + return columns; + } finally { + JdbcUtils.closeResultSet(columnData); + } + } + + private static Column readColumn(DatabaseMetaDataWrapper metaData, Map values) throws SQLException { + Column column = new Column(); + + column.setName((String) values.get("COLUMN_NAME")); + column.setDefaultValue((String) values.get("COLUMN_DEF")); + column.setTypeCode(((Integer) values.get("DATA_TYPE")).intValue()); + + String typeName = (String) values.get("TYPE_NAME"); + // column.setType(typeName); + + if ((typeName != null) && typeName.startsWith("TIMESTAMP")) { + column.setTypeCode(Types.TIMESTAMP); + } + // modify 2013-09-25,处理下unsigned + if ((typeName != null) && StringUtils.containsIgnoreCase(typeName, "UNSIGNED")) { + // 如果为unsigned,往上调大一个量级,避免数据溢出 + switch (column.getTypeCode()) { + case Types.TINYINT: + column.setTypeCode(Types.SMALLINT); + break; + case Types.SMALLINT: + column.setTypeCode(Types.INTEGER); + break; + case Types.INTEGER: + column.setTypeCode(Types.BIGINT); + break; + case Types.BIGINT: + column.setTypeCode(Types.DECIMAL); + break; + default: + break; + } + } + + Integer precision = (Integer) values.get("NUM_PREC_RADIX"); + + if (precision != null) { + column.setPrecisionRadix(precision.intValue()); + } + + String size = (String) values.get("COLUMN_SIZE"); + + if (size == null) { + size = (String) _defaultSizes.get(new Integer(column.getTypeCode())); + } + + // we're setting the size after the precision and radix in case + // the database prefers to return them in the size value + column.setSize(size); + + int scale = 0; + Object dec_digits = values.get("DECIMAL_DIGITS"); + + if (dec_digits instanceof String) { + scale = (dec_digits == null) ? 0 : NumberUtils.toInt(dec_digits.toString()); + } else if (dec_digits instanceof Integer) { + scale = (dec_digits == null) ? 0 : (Integer) dec_digits; + } + + if (scale != 0) { + column.setScale(scale); + } + + column.setRequired("NO".equalsIgnoreCase(((String) values.get("IS_NULLABLE")).trim())); + column.setDescription((String) values.get("REMARKS")); + return column; + } + + private static Map readColumns(ResultSet resultSet, List columnDescriptors) + throws SQLException { + Map values = new HashMap(); + MetaDataColumnDescriptor descriptor; + + for (Iterator it = columnDescriptors.iterator(); it.hasNext(); values.put(descriptor.getName(), + descriptor.readColumn(resultSet))) { + descriptor = (MetaDataColumnDescriptor) it.next(); + } + + return values; + } + + private static Collection readPrimaryKeyNames(DatabaseMetaDataWrapper metaData, String tableName) + throws SQLException { + ResultSet pkData = null; + + try { + List pks = new ArrayList(); + Map values; + + for (pkData = metaData.getPrimaryKeys(tableName); pkData.next(); pks.add(readPrimaryKeyName(metaData, + values))) { + values = readColumns(pkData, initColumnsForPK()); + } + + return pks; + } finally { + JdbcUtils.closeResultSet(pkData); + } + } + + private static String readPrimaryKeyName(DatabaseMetaDataWrapper metaData, Map values) + throws SQLException { + return (String) values.get("COLUMN_NAME"); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java new file mode 100644 index 00000000..5743106d --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java @@ -0,0 +1,140 @@ +package com.alibaba.otter.canal.example.db.utils; + +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.Converter; +import org.apache.commons.lang.time.DateFormatUtils; + +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class SqlTimestampConverter implements Converter { + + /** + * Field description + */ + public static final String[] DATE_FORMATS = new String[]{"yyyy-MM-dd", "HH:mm:ss", "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd hh:mm:ss.fffffffff", "EEE MMM dd HH:mm:ss zzz yyyy", + DateFormatUtils.ISO_DATETIME_FORMAT.getPattern(), + DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), + DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(),}; + + public static final Converter SQL_TIMESTAMP = new SqlTimestampConverter(null); + + /** + * The default value specified to our Constructor, if any. + */ + private final Object defaultValue; + + /** + * Should we return the default value on conversion errors? + */ + private final boolean useDefault; + + /** + * Create a {@link Converter} that will throw a {@link ConversionException} if a conversion error occurs. + */ + public SqlTimestampConverter() { + this.defaultValue = null; + this.useDefault = false; + } + + /** + * Create a {@link Converter} that will return the specified default value if a conversion error occurs. + * + * @param defaultValue The default value to be returned + */ + public SqlTimestampConverter(Object defaultValue) { + this.defaultValue = defaultValue; + this.useDefault = true; + } + + /** + * Convert the specified input object into an output object of the specified type. + * + * @param type Data type to which this value should be converted + * @param value The input value to be converted + * @throws ConversionException if conversion cannot be performed successfully + */ + public Object convert(Class type, Object value) { + if (value == null) { + if (useDefault) { + return (defaultValue); + } else { + throw new ConversionException("No value specified"); + } + } + + if (value instanceof java.sql.Date && java.sql.Date.class.equals(type)) { + return value; + } else if (value instanceof java.sql.Time && java.sql.Time.class.equals(type)) { + return value; + } else if (value instanceof Timestamp && Timestamp.class.equals(type)) { + return value; + } else { + try { + if (java.sql.Date.class.equals(type)) { + return new java.sql.Date(convertTimestamp2TimeMillis(value.toString())); + } else if (java.sql.Time.class.equals(type)) { + return new java.sql.Time(convertTimestamp2TimeMillis(value.toString())); + } else if (Timestamp.class.equals(type)) { + return new Timestamp(convertTimestamp2TimeMillis(value.toString())); + } else { + return new Timestamp(convertTimestamp2TimeMillis(value.toString())); + } + } catch (Exception e) { + throw new ConversionException("Value format invalid: " + e.getMessage(), e); + } + } + + } + + private Long convertTimestamp2TimeMillis(String input) { + if (input == null) { + return null; + } + + try { + // 先处理Timestamp类型 + return Timestamp.valueOf(input).getTime(); + } catch (Exception nfe) { + try { + try { + return parseDate(input, DATE_FORMATS, Locale.ENGLISH).getTime(); + } catch (Exception err) { + return parseDate(input, DATE_FORMATS, Locale.getDefault()).getTime(); + } + } catch (Exception err) { + // 最后处理long time的情况 + return Long.parseLong(input); + } + } + } + + private Date parseDate(String str, String[] parsePatterns, Locale locale) throws ParseException { + if ((str == null) || (parsePatterns == null)) { + throw new IllegalArgumentException("Date and Patterns must not be null"); + } + + SimpleDateFormat parser = null; + ParsePosition pos = new ParsePosition(0); + + for (int i = 0; i < parsePatterns.length; i++) { + if (i == 0) { + parser = new SimpleDateFormat(parsePatterns[0], locale); + } else { + parser.applyPattern(parsePatterns[i]); + } + pos.setIndex(0); + Date date = parser.parse(str, pos); + if ((date != null) && (pos.getIndex() == str.length())) { + return date; + } + } + + throw new ParseException("Unable to parse the date: " + str, -1); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java new file mode 100644 index 00000000..565d5730 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java @@ -0,0 +1,315 @@ +package com.alibaba.otter.canal.example.db.utils; + +import org.apache.commons.beanutils.ConvertUtilsBean; +import org.apache.commons.lang.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.*; +import java.util.HashMap; +import java.util.Map; + +public class SqlUtils { + + public static final String REQUIRED_FIELD_NULL_SUBSTITUTE = " "; + public static final String SQLDATE_FORMAT = "yyyy-MM-dd"; + public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss"; + private static final Map> sqlTypeToJavaTypeMap = new HashMap>(); + private static final ConvertUtilsBean convertUtilsBean = new ConvertUtilsBean(); + + static { + // regist Converter + convertUtilsBean.register(SqlTimestampConverter.SQL_TIMESTAMP, Date.class); + convertUtilsBean.register(SqlTimestampConverter.SQL_TIMESTAMP, Time.class); + convertUtilsBean.register(SqlTimestampConverter.SQL_TIMESTAMP, Timestamp.class); + convertUtilsBean.register(ByteArrayConverter.SQL_BYTES, byte[].class); + + // bool + sqlTypeToJavaTypeMap.put(Types.BOOLEAN, Boolean.class); + + // int + sqlTypeToJavaTypeMap.put(Types.TINYINT, Integer.class); + sqlTypeToJavaTypeMap.put(Types.SMALLINT, Integer.class); + sqlTypeToJavaTypeMap.put(Types.INTEGER, Integer.class); + + // long + sqlTypeToJavaTypeMap.put(Types.BIGINT, Long.class); + // mysql bit最多64位,无符号 + sqlTypeToJavaTypeMap.put(Types.BIT, BigInteger.class); + + // decimal + sqlTypeToJavaTypeMap.put(Types.REAL, Float.class); + sqlTypeToJavaTypeMap.put(Types.FLOAT, Float.class); + sqlTypeToJavaTypeMap.put(Types.DOUBLE, Double.class); + sqlTypeToJavaTypeMap.put(Types.NUMERIC, BigDecimal.class); + sqlTypeToJavaTypeMap.put(Types.DECIMAL, BigDecimal.class); + + // date + sqlTypeToJavaTypeMap.put(Types.DATE, Date.class); + sqlTypeToJavaTypeMap.put(Types.TIME, Time.class); + sqlTypeToJavaTypeMap.put(Types.TIMESTAMP, Timestamp.class); + + // blob + sqlTypeToJavaTypeMap.put(Types.BLOB, byte[].class); + + // byte[] + sqlTypeToJavaTypeMap.put(Types.REF, byte[].class); + sqlTypeToJavaTypeMap.put(Types.OTHER, byte[].class); + sqlTypeToJavaTypeMap.put(Types.ARRAY, byte[].class); + sqlTypeToJavaTypeMap.put(Types.STRUCT, byte[].class); + sqlTypeToJavaTypeMap.put(Types.SQLXML, byte[].class); + sqlTypeToJavaTypeMap.put(Types.BINARY, byte[].class); + sqlTypeToJavaTypeMap.put(Types.DATALINK, byte[].class); + sqlTypeToJavaTypeMap.put(Types.DISTINCT, byte[].class); + sqlTypeToJavaTypeMap.put(Types.VARBINARY, byte[].class); + sqlTypeToJavaTypeMap.put(Types.JAVA_OBJECT, byte[].class); + sqlTypeToJavaTypeMap.put(Types.LONGVARBINARY, byte[].class); + + // String + sqlTypeToJavaTypeMap.put(Types.CHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.VARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.LONGVARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.LONGNVARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.NCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.NVARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.NCLOB, String.class); + sqlTypeToJavaTypeMap.put(Types.CLOB, String.class); + } + + /** + * 将指定java.sql.Types的ResultSet value转换成相应的String + * + * @param rs + * @param index + * @param sqlType + * @return + * @throws SQLException + */ + public static String sqlValueToString(ResultSet rs, int index, int sqlType) throws SQLException { + Class requiredType = sqlTypeToJavaTypeMap.get(sqlType); + if (requiredType == null) { + throw new IllegalArgumentException("unknow java.sql.Types - " + sqlType); + } + + return getResultSetValue(rs, index, requiredType); + } + + /** + * sqlValueToString方法的逆向过程 + * + * @param value + * @param sqlType + * @param isTextRequired + * @param isEmptyStringNulled + * @return + */ + public static Object stringToSqlValue(String value, int sqlType, boolean isRequired, boolean isEmptyStringNulled) { + // 设置变量 + String sourceValue = value; + if (SqlUtils.isTextType(sqlType)) { + if ((sourceValue == null) || (StringUtils.isEmpty(sourceValue) && isEmptyStringNulled)) { + return isRequired ? REQUIRED_FIELD_NULL_SUBSTITUTE : null; + } else { + return sourceValue; + } + } else { + if (StringUtils.isEmpty(sourceValue)) { + return null; + } else { + Class requiredType = sqlTypeToJavaTypeMap.get(sqlType); + if (requiredType == null) { + throw new IllegalArgumentException("unknow java.sql.Types - " + sqlType); + } else if (requiredType.equals(String.class)) { + return sourceValue; + } else if (isNumeric(sqlType)) { + return convertUtilsBean.convert(sourceValue.trim(), requiredType); + } else { + return convertUtilsBean.convert(sourceValue, requiredType); + } + } + } + } + + public static String encoding(String source, int sqlType, String sourceEncoding, String targetEncoding) { + switch (sqlType) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.NCHAR: + case Types.NVARCHAR: + case Types.LONGNVARCHAR: + case Types.CLOB: + case Types.NCLOB: + if (false == StringUtils.isEmpty(source)) { + String fromEncoding = StringUtils.isBlank(sourceEncoding) ? "UTF-8" : sourceEncoding; + String toEncoding = StringUtils.isBlank(targetEncoding) ? "UTF-8" : targetEncoding; + + // if (false == StringUtils.equalsIgnoreCase(fromEncoding, + // toEncoding)) { + try { + return new String(source.getBytes(fromEncoding), toEncoding); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + // } + } + } + + return source; + } + + /** + * Retrieve a JDBC column value from a ResultSet, using the specified value + * type. + *

    + * Uses the specifically typed ResultSet accessor methods, falling back to + * {@link #getResultSetValue(ResultSet, int)} for unknown types. + *

    + * Note that the returned value may not be assignable to the specified + * required type, in case of an unknown type. Calling code needs to deal + * with this case appropriately, e.g. throwing a corresponding exception. + * + * @param rs is the ResultSet holding the data + * @param index is the column index + * @param requiredType the required value type (may be null) + * @return the value object + * @throws SQLException if thrown by the JDBC API + */ + private static String getResultSetValue(ResultSet rs, int index, Class requiredType) throws SQLException { + if (requiredType == null) { + return getResultSetValue(rs, index); + } + + Object value = null; + boolean wasNullCheck = false; + + // Explicitly extract typed value, as far as possible. + if (String.class.equals(requiredType)) { + value = rs.getString(index); + } else if (boolean.class.equals(requiredType) || Boolean.class.equals(requiredType)) { + value = Boolean.valueOf(rs.getBoolean(index)); + wasNullCheck = true; + } else if (byte.class.equals(requiredType) || Byte.class.equals(requiredType)) { + value = new Byte(rs.getByte(index)); + wasNullCheck = true; + } else if (short.class.equals(requiredType) || Short.class.equals(requiredType)) { + value = new Short(rs.getShort(index)); + wasNullCheck = true; + } else if (int.class.equals(requiredType) || Integer.class.equals(requiredType)) { + value = new Long(rs.getLong(index)); + wasNullCheck = true; + } else if (long.class.equals(requiredType) || Long.class.equals(requiredType)) { + value = rs.getBigDecimal(index); + wasNullCheck = true; + } else if (float.class.equals(requiredType) || Float.class.equals(requiredType)) { + value = new Float(rs.getFloat(index)); + wasNullCheck = true; + } else if (double.class.equals(requiredType) || Double.class.equals(requiredType) + || Number.class.equals(requiredType)) { + value = new Double(rs.getDouble(index)); + wasNullCheck = true; + } else if (Time.class.equals(requiredType)) { + // try { + // value = rs.getTime(index); + // } catch (SQLException e) { + value = rs.getString(index);// 尝试拿为string对象,0000无法用Time表示 + // if (value == null && !rs.wasNull()) { + // value = "00:00:00"; // + // mysql设置了zeroDateTimeBehavior=convertToNull,出现0值时返回为null + // } + // } + } else if (Timestamp.class.equals(requiredType) || Date.class.equals(requiredType)) { + // try { + // value = convertTimestamp(rs.getTimestamp(index)); + // } catch (SQLException e) { + // 尝试拿为string对象,0000-00-00 00:00:00无法用Timestamp 表示 + value = rs.getString(index); + // if (value == null && !rs.wasNull()) { + // value = "0000:00:00 00:00:00"; // + // mysql设置了zeroDateTimeBehavior=convertToNull,出现0值时返回为null + // } + // } + } else if (BigDecimal.class.equals(requiredType)) { + value = rs.getBigDecimal(index); + } else if (BigInteger.class.equals(requiredType)) { + value = rs.getBigDecimal(index); + } else if (Blob.class.equals(requiredType)) { + value = rs.getBlob(index); + } else if (Clob.class.equals(requiredType)) { + value = rs.getClob(index); + } else if (byte[].class.equals(requiredType)) { + try { + byte[] bytes = rs.getBytes(index); + if (bytes == null) { + value = null; + } else { + value = new String(bytes, "ISO-8859-1");// 将binary转化为iso-8859-1的字符串 + } + } catch (UnsupportedEncodingException e) { + throw new SQLException(e); + } + } else { + // Some unknown type desired -> rely on getObject. + value = getResultSetValue(rs, index); + } + + // Perform was-null check if demanded (for results that the + // JDBC driver returns as primitives). + if (wasNullCheck && (value != null) && rs.wasNull()) { + value = null; + } + + return (value == null) ? null : convertUtilsBean.convert(value); + } + + /** + * Retrieve a JDBC column value from a ResultSet, using the most appropriate + * value type. The returned value should be a detached value object, not + * having any ties to the active ResultSet: in particular, it should not be + * a Blob or Clob object but rather a byte array respectively String + * representation. + *

    + * Uses the getObject(index) method, but includes additional + * "hacks" to get around Oracle 10g returning a non-standard object for its + * TIMESTAMP datatype and a java.sql.Date for DATE columns + * leaving out the time portion: These columns will explicitly be extracted + * as standard java.sql.Timestamp object. + * + * @param rs is the ResultSet holding the data + * @param index is the column index + * @return the value object + * @throws SQLException if thrown by the JDBC API + * @see Blob + * @see Clob + * @see Timestamp + */ + private static String getResultSetValue(ResultSet rs, int index) throws SQLException { + Object obj = rs.getObject(index); + return (obj == null) ? null : convertUtilsBean.convert(obj); + } + + // private static Object convertTimestamp(Timestamp timestamp) { + // return (timestamp == null) ? null : timestamp.getTime(); + // } + + /** + * Check whether the given SQL type is numeric. + */ + public static boolean isNumeric(int sqlType) { + return (Types.BIT == sqlType) || (Types.BIGINT == sqlType) || (Types.DECIMAL == sqlType) + || (Types.DOUBLE == sqlType) || (Types.FLOAT == sqlType) || (Types.INTEGER == sqlType) + || (Types.NUMERIC == sqlType) || (Types.REAL == sqlType) || (Types.SMALLINT == sqlType) + || (Types.TINYINT == sqlType); + } + + public static boolean isTextType(int sqlType) { + if (sqlType == Types.CHAR || sqlType == Types.VARCHAR || sqlType == Types.CLOB || sqlType == Types.LONGVARCHAR + || sqlType == Types.NCHAR || sqlType == Types.NVARCHAR || sqlType == Types.NCLOB + || sqlType == Types.LONGNVARCHAR) { + return true; + } else { + return false; + } + } +} diff --git a/example/src/main/resources/client-spring.xml b/example/src/main/resources/client-spring.xml new file mode 100644 index 00000000..d9319007 --- /dev/null +++ b/example/src/main/resources/client-spring.xml @@ -0,0 +1,53 @@ + + + + + + + + + classpath:client.properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/src/main/resources/client.properties b/example/src/main/resources/client.properties new file mode 100644 index 00000000..c5142912 --- /dev/null +++ b/example/src/main/resources/client.properties @@ -0,0 +1,16 @@ +# client 配置 +zk.servers=127.0.0.1:2181 +# 5 * 1024 +client.batch.size=5120 +client.debug=false +client.destination=example +client.username=canal +client.password=canal +client.exceptionstrategy=1 +client.retrytimes=3 +client.filter=.*\\..* + +# 同步目标: mysql 配置 +target.mysql.url=jdbc:mysql://127.0.0.1:4306 +target.mysql.username=root +target.mysql.password=123456 From 02868d8fecb50e445d8b624ca70b25da36cda788 Mon Sep 17 00:00:00 2001 From: yfpeng Date: Tue, 31 Jul 2018 20:30:32 +0800 Subject: [PATCH 041/360] =?UTF-8?q?=E4=B8=BA=E4=BA=86=E8=A7=A3=E5=86=B3?= =?UTF-8?q?=E5=9B=9E=E8=BF=BD=E6=95=B0=E6=8D=AE=E6=97=B6Rowdata=E4=B8=8E?= =?UTF-8?q?=E5=BD=93=E5=89=8Dschema=E4=B8=8D=E5=8C=B9=E9=85=8D=20=E5=AF=B9?= =?UTF-8?q?TableMeta=E6=8F=90=E4=BE=9B=E6=8C=81=E4=B9=85=E5=8C=96=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=8C=E5=8F=AF=E4=BB=A5=E6=A0=B9=E6=8D=AEtimestamp?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=8E=86=E5=8F=B2schema?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manager/CanalInstanceWithManager.java | 8 + .../manager/model/CanalParameter.java | 12 ++ .../parse/inbound/mysql/MysqlEventParser.java | 27 ++- .../inbound/mysql/dbsync/LogEventConvert.java | 11 +- .../inbound/mysql/dbsync/TableMetaCache.java | 7 +- .../tablemeta/HistoryTableMetaCache.java | 191 ++++++++++++++++++ .../tablemeta/TableMetaCacheInterface.java | 20 ++ .../tablemeta/TableMetaCacheWithStorage.java | 105 ++++++++++ .../mysql/tablemeta/TableMetaEntry.java | 55 +++++ .../mysql/tablemeta/TableMetaStorage.java | 18 ++ .../tablemeta/TableMetaStorageFactory.java | 9 + .../exception/CacheConnectionNull.java | 9 + .../exception/NoHistoryException.java | 21 ++ .../impl/mysql/MySqlTableMetaCallback.java | 14 ++ .../impl/mysql/MySqlTableMetaStorage.java | 48 +++++ .../mysql/MySqlTableMetaStorageFactory.java | 26 +++ .../mysql/tablemeta/NoStorageTest.java | 38 ++++ .../inbound/mysql/tablemeta/StorageTest.java | 77 +++++++ pom.xml | 2 +- 19 files changed, 687 insertions(+), 11 deletions(-) create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index bca5ba13..f6cec13d 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.HistoryTableMetaCache; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -242,6 +243,13 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { mysqlEventParser.setDetectingIntervalInSeconds(parameters.getDetectingIntervalInSeconds()); // 数据库信息参数 mysqlEventParser.setSlaveId(parameters.getSlaveId()); + mysqlEventParser.setTableMetaStorageFactory(parameters.getTableMetaStorageFactory()); + // Ctrip callback +// mysqlEventParser.setCallback(parameters.getCallback()); +// HistoryTableMetaCache cache = new HistoryTableMetaCache(); +// cache.init(parameters.getEntries()); +// mysqlEventParser.setHistoryTableMetaCache(cache); + if (!CollectionUtils.isEmpty(dbAddresses)) { mysqlEventParser.setMasterInfo(new AuthenticationInfo(dbAddresses.get(0), parameters.getDbUsername(), diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java index 16c929ca..94558590 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java @@ -5,6 +5,7 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; @@ -106,6 +107,9 @@ public class CanalParameter implements Serializable { private Long standbyLogfileOffest = null; private Long standbyTimestamp = null; + // Ctrip Table Meta + TableMetaStorageFactory tableMetaStorageFactory; + public static enum RunMode { /** 嵌入式 */ @@ -859,6 +863,14 @@ public class CanalParameter implements Serializable { this.blackFilter = blackFilter; } + public TableMetaStorageFactory getTableMetaStorageFactory() { + return tableMetaStorageFactory; + } + + public void setTableMetaStorageFactory(TableMetaStorageFactory tableMetaStorageFactory) { + this.tableMetaStorageFactory = tableMetaStorageFactory; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index 2efab0ee..a30af259 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -10,6 +10,11 @@ import java.util.Map; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicLong; +import com.alibaba.otter.canal.parse.inbound.*; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheWithStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; @@ -20,9 +25,6 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.ha.CanalHAController; -import com.alibaba.otter.canal.parse.inbound.ErosaConnection; -import com.alibaba.otter.canal.parse.inbound.HeartBeatCallback; -import com.alibaba.otter.canal.parse.inbound.SinkFunction; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogFormat; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert; @@ -62,7 +64,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE // 心跳检查信息 private String detectingSQL; // 心跳sql private MysqlConnection metaConnection; // 查询meta信息的链接 - private TableMetaCache tableMetaCache; // 对应meta + private TableMetaCacheInterface tableMetaCache; // 对应meta // cache private int fallbackIntervalInSeconds = 60; // 切换回退时间 private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 @@ -72,6 +74,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private int dumpErrorCount = 0; // binlogDump失败异常计数 private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 + private TableMetaStorageFactory tableMetaStorageFactory; + protected ErosaConnection buildErosaConnection() { return buildMysqlConnection(this.runningInfo); } @@ -125,7 +129,13 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter); } - tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB); + + TableMetaStorage storage = null; + if (tableMetaStorageFactory != null) { + storage = tableMetaStorageFactory.getTableMetaStorage(); + } + + tableMetaCache = new TableMetaCacheWithStorage(metaConnection, storage); ((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache); } } @@ -908,4 +918,11 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE this.dumpErrorCountThreshold = dumpErrorCountThreshold; } + public TableMetaStorageFactory getTableMetaStorageFactory() { + return tableMetaStorageFactory; + } + + public void setTableMetaStorageFactory(TableMetaStorageFactory tableMetaStorageFactory) { + this.tableMetaStorageFactory = tableMetaStorageFactory; + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index fe34e199..67bb07b7 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -10,6 +10,9 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; @@ -88,7 +91,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private volatile AviaterRegexFilter nameFilter; // 运行时引用可能会有变化,比如规则发生变化时 private volatile AviaterRegexFilter nameBlackFilter; - private TableMetaCache tableMetaCache; + private TableMetaCacheInterface tableMetaCache; private String binlogFileName = "mysql-bin.000001"; private Charset charset = Charset.defaultCharset(); private boolean filterQueryDcl = false; @@ -262,7 +265,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar if (!isSeek) { // 使用新的表结构元数据管理方式 EntryPosition position = createPosition(event.getHeader()); - tableMetaCache.apply(position, event.getDbName(), queryString, null); + String fulltbName = schemaName+"."+tableName; + tableMetaCache.apply(position, fulltbName, queryString, null); } Header header = createHeader(binlogFileName, event.getHeader(), schemaName, tableName, type); @@ -901,7 +905,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar this.nameBlackFilter = nameBlackFilter; } - public void setTableMetaCache(TableMetaCache tableMetaCache) { + public void setTableMetaCache(TableMetaCacheInterface tableMetaCache) { this.tableMetaCache = tableMetaCache; } @@ -928,5 +932,4 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar public void setGtidSet(GTIDSet gtidSet) { this.gtidSet = gtidSet; } - } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java index 4c1cef3c..5c782aa1 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; @@ -29,7 +30,7 @@ import com.google.common.cache.LoadingCache; * @author jianghang 2013-1-17 下午10:15:16 * @version 1.0.0 */ -public class TableMetaCache { +public class TableMetaCache implements TableMetaCacheInterface { public static final String COLUMN_NAME = "COLUMN_NAME"; public static final String COLUMN_TYPE = "COLUMN_TYPE"; @@ -99,6 +100,10 @@ public class TableMetaCache { String createDDL = packet.getFieldValues().get(1); MemoryTableMeta memoryTableMeta = new MemoryTableMeta(); memoryTableMeta.apply(DatabaseTableMeta.INIT_POSITION, schema, createDDL, null); + String[] strings = table.split("\\."); + if (strings.length > 1) { + table = strings[1]; + } TableMeta tableMeta = memoryTableMeta.find(schema, table); return tableMeta.getFields(); } else { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java new file mode 100644 index 00000000..0964627d --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java @@ -0,0 +1,191 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.CacheConnectionNull; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import java.io.IOException; +import java.util.*; + +public class HistoryTableMetaCache { + private TableMetaStorage tableMetaStorage; + private MysqlConnection metaConnection; + private LoadingCache> cache; // 第一层:数据库名.表名,第二层时间戳,TableMeta + + public HistoryTableMetaCache() { + cache = CacheBuilder.newBuilder().build(new CacheLoader>() { + @Override + public Map load(String tableName) throws Exception { + Long timestamp = new Date().getTime(); + String[] strs = tableName.split("\\."); + String schema = strs[0]; + if (tableMetaStorage != null) { + init(tableMetaStorage.fetchByTableName(tableName)); // 从存储中读取表的历史ddl + } + ResultSetPacket resultSetPacket = connectionQuery("show create table " + tableName); // 获取当前ddl + String currentDdl = resultSetPacket.getFieldValues().get(1); + if (cache.asMap().containsKey(tableName)) { + Map tableMetaMap = cache.getUnchecked(tableName); + if (tableMetaMap.isEmpty()) { + put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema,取时间为当前时间-1s + } else { // 如果table存在历史 + Iterator iterator = tableMetaMap.keySet().iterator(); + Long firstTimestamp = iterator.next(); + TableMeta first = tableMetaMap.get(firstTimestamp); // 拿第一条ddl + if (!first.getDdl().equalsIgnoreCase(currentDdl)) { // 当前ddl与历史第一条不一致,放入当前ddl + put(schema, tableName, currentDdl, calculateNewTimestamp(firstTimestamp)); // 计算放入的timestamp,设为第一条时间+1s + } + } + } else { + put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema + } + return cache.get(tableName); + } + }); + } + + public void init(List entries) throws IOException { + if (entries == null) { + return; + } + for (TableMetaEntry entry : entries) { + try { + put(entry.getSchema(), entry.getTable(), entry.getDdl(), entry.getTimestamp()); + } catch (CacheConnectionNull cacheConnectionNull) { + cacheConnectionNull.printStackTrace(); + } + } + } + + public TableMeta put(String schema, String table, String ddl, Long timestamp) throws CacheConnectionNull, IOException { + ResultSetPacket resultSetPacket; + if (!(ddl.contains("CREATE TABLE") || ddl.contains("create table"))) { // 尝试直接从数据库拉取CREATE TABLE的DDL + resultSetPacket = connectionQuery("show create table " + table); + ddl = resultSetPacket.getFieldValues().get(1); + } else { // CREATE TABLE 的 DDL + resultSetPacket = new ResultSetPacket(); + List fields = new ArrayList(); + String[] strings = table.split("\\."); + String shortTable = table; + if (strings.length > 1) { + shortTable = strings[1]; + } + fields.add(0, shortTable); + fields.add(1, ddl); + resultSetPacket.setFieldValues(fields); + if (metaConnection != null) { + resultSetPacket.setSourceAddress(metaConnection.getAddress()); + } + } + Map tableMetaMap; + if (!cache.asMap().containsKey(table)) { + tableMetaMap = new TreeMap(new Comparator() { + @Override + public int compare(Long o1, Long o2) { + return o2.compareTo(o1); + } + }); + cache.put(table, tableMetaMap); + } else { + tableMetaMap = cache.getUnchecked(table); + } + eliminate(tableMetaMap); // 淘汰旧的TableMeta + TableMeta tableMeta = new TableMeta(schema, table, TableMetaCache.parseTableMeta(schema, table, resultSetPacket)); + if (tableMeta.getDdl() == null) { // 生成的TableMeta有时DDL为null + tableMeta.setDdl(ddl); + } + tableMetaMap.put(timestamp, tableMeta); + return tableMeta; + } + + public TableMeta get(String schema, String table, Long timestamp) throws NoHistoryException, CacheConnectionNull { + Map tableMetaMap = cache.getUnchecked(table); + Iterator iterator = tableMetaMap.keySet().iterator(); + Long selected = null; + while(iterator.hasNext()) { + Long temp = iterator.next(); + if (timestamp > temp) { + selected = temp; + break; + } + } + + if (selected == null) { + iterator = tableMetaMap.keySet().iterator(); + if (iterator.hasNext()) { + selected = iterator.next(); + } else { + throw new NoHistoryException(schema, table); + } + } + + return tableMetaMap.get(selected); + } + + public void clearTableMeta() { + cache.invalidateAll(); + } + + public void clearTableMetaWithSchemaName(String schema) { + for (String tableName : cache.asMap().keySet()) { + String[] strs = tableName.split("\\."); + if (schema.equalsIgnoreCase(strs[0])) { + cache.invalidate(tableName); + } + } + } + + public void clearTableMeta(String schema, String table) { + if (!table.contains(".")) { + table = schema+"."+table; + } + cache.invalidate(table); + } + + // eliminate older table meta in cache + private void eliminate(Map tableMetaMap) { + int MAX_CAPABILITY = 20; + if (tableMetaMap.keySet().size() < MAX_CAPABILITY) { + return; + } + Iterator iterator = tableMetaMap.keySet().iterator(); + while(iterator.hasNext()) { + iterator.next(); + } + iterator.remove(); + } + + private Long calculateNewTimestamp(Long oldTimestamp) { + return oldTimestamp + 1000; + } + + private ResultSetPacket connectionQuery(String query) throws CacheConnectionNull, IOException { + if (metaConnection == null) { + throw new CacheConnectionNull(); + } + try { + return metaConnection.query(query); + } catch (IOException e) { + try { + metaConnection.reconnect(); + return metaConnection.query(query); + } catch (IOException e1) { + throw e1; + } + } + } + + public void setMetaConnection(MysqlConnection metaConnection) { + this.metaConnection = metaConnection; + } + + public void setTableMetaStorage(TableMetaStorage tableMetaStorage) { + this.tableMetaStorage = tableMetaStorage; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java new file mode 100644 index 00000000..5196ee08 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java @@ -0,0 +1,20 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.protocol.position.EntryPosition; + +public interface TableMetaCacheInterface { + + TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position); + + void clearTableMeta(); + + void clearTableMetaWithSchemaName(String schema); + + void clearTableMeta(String schema, String table); + + boolean apply(EntryPosition position, String schema, String ddl, String extra); + + boolean isOnRDS(); + +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java new file mode 100644 index 00000000..eb43158b --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java @@ -0,0 +1,105 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; + +public class TableMetaCacheWithStorage implements TableMetaCacheInterface { + + private static Logger logger = LoggerFactory.getLogger(TableMetaCacheWithStorage.class); + private TableMetaStorage tableMetaStorage; // TableMeta存储 + private HistoryTableMetaCache cache = new HistoryTableMetaCache(); // cache + + public TableMetaCacheWithStorage(MysqlConnection con, TableMetaStorage tableMetaStorage) { + this.tableMetaStorage = tableMetaStorage; + InetSocketAddress address = con.getAddress(); + this.tableMetaStorage.setDbAddress(address.getHostName()+":"+address.getPort()); + cache.setMetaConnection(con); + cache.setTableMetaStorage(tableMetaStorage); + if (tableMetaStorage != null) { + try { + cache.init(tableMetaStorage.fetch()); // 初始化,从存储拉取TableMeta + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + } + + @Override + public boolean apply(EntryPosition position, String fullTableName, String ddl, String extra) { + String[] strs = fullTableName.split("\\."); + String schema = strs[0]; + if (schema.equalsIgnoreCase("null")) { // ddl schema为null,放弃处理 + return false; + } + try { + TableMeta tableMeta = cache.get(schema, fullTableName, position.getTimestamp()); + if (!compare(tableMeta, ddl)) { // 获取最近的TableMeta,进行比对 + TableMeta result = cache.put(schema, fullTableName, ddl, calTimestamp(position.getTimestamp())); + if (tableMetaStorage != null && result != null) { // 储存 + tableMetaStorage.store(schema, fullTableName, result.getDdl(), calTimestamp(position.getTimestamp())); + } + } + return true; + } catch (Exception e) { + logger.error(e.toString()); + } + + return false; + } + + @Override + public boolean isOnRDS() { + return false; + } + + /*** + * + * @param schema dbname + * @param table tablename + * @param useCache unused + * @param position timestamp + * @return + */ + @Override + public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) { + String fulltbName = schema + "." + table; + try { + return cache.get(schema, fulltbName, position.getTimestamp()); + } catch (Exception e) { + logger.error(e.toString()); + } + return null; + } + + @Override + public void clearTableMeta() { + cache.clearTableMeta(); + } + + @Override + public void clearTableMetaWithSchemaName(String schema) { + cache.clearTableMetaWithSchemaName(schema); + } + + @Override + public void clearTableMeta(String schema, String table) { + cache.clearTableMeta(schema, table); + } + + private boolean compare(TableMeta tableMeta, String ddl) { + if (tableMeta == null) { + return false; + } + return tableMeta.getDdl().equalsIgnoreCase(ddl); + } + + private Long calTimestamp(Long timestamp) { + return timestamp; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java new file mode 100644 index 00000000..03e63ed7 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java @@ -0,0 +1,55 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import java.io.Serializable; + +public class TableMetaEntry implements Serializable { + + private static final long serialVersionUID = -1350200637109107904L; + + private String dbAddress; + private String schema; + private String table; + private String ddl; + private Long timestamp; + + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public String getDdl() { + return ddl; + } + + public void setDdl(String ddl) { + this.ddl = ddl; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + public String getDbAddress() { + return dbAddress; + } + + public void setDbAddress(String dbAddress) { + this.dbAddress = dbAddress; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java new file mode 100644 index 00000000..ac9d7a0a --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java @@ -0,0 +1,18 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import java.util.List; + +public interface TableMetaStorage { + + void store(String schema, String table, String ddl, Long timestamp); + + List fetch(); + + List fetchByTableName(String tableName); + + String getDbName(); + + String getDbAddress(); + + void setDbAddress(String address); +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java new file mode 100644 index 00000000..0e532ebb --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java @@ -0,0 +1,9 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +public interface TableMetaStorageFactory { + + TableMetaStorage getTableMetaStorage(); + + String getDbName(); + +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java new file mode 100644 index 00000000..1dadd78a --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java @@ -0,0 +1,9 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; + +public class CacheConnectionNull extends Exception{ + + @Override + public String toString() { + return "CacheConnectionNull"; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java new file mode 100644 index 00000000..d8f1d16b --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java @@ -0,0 +1,21 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; + +public class NoHistoryException extends Exception{ + + private String dbName; + private String tbName; + + public NoHistoryException(String dbName, String tbName) { + this.dbName = dbName; + this.tbName = tbName; + } + + public void printTableName() { + System.out.println(dbName+"."+tbName); + } + + @Override + public String toString() { + return "NioHistoryException: " + dbName + " " + tbName; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java new file mode 100644 index 00000000..661986a1 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java @@ -0,0 +1,14 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; + +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; + +import java.util.List; + +public interface MySqlTableMetaCallback { + + void save(String dbAddress, String schema, String table,String ddl, Long timestamp); + + List fetch(String dbAddress, String dbName); + + List fetch(String dbAddress, String dbName, String tableName); +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java new file mode 100644 index 00000000..f120ef80 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java @@ -0,0 +1,48 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; + +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; + +import java.util.List; + +public class MySqlTableMetaStorage implements TableMetaStorage { + private MySqlTableMetaCallback mySqlTableMetaCallback; + private String dbName; + private String dbAddress; + + MySqlTableMetaStorage(MySqlTableMetaCallback callback, String dbName) { + mySqlTableMetaCallback = callback; + this.dbName = dbName; + } + + + @Override + public void store(String schema, String table, String ddl, Long timestamp) { + mySqlTableMetaCallback.save(dbAddress, schema, table, ddl, timestamp); + } + + @Override + public List fetch() { + return mySqlTableMetaCallback.fetch(dbAddress, dbName); + } + + @Override + public List fetchByTableName(String tableName) { + return mySqlTableMetaCallback.fetch(dbAddress, dbName, tableName); + } + + @Override + public String getDbName() { + return dbName; + } + + @Override + public String getDbAddress() { + return dbAddress; + } + + @Override + public void setDbAddress(String address) { + this.dbAddress = address; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java new file mode 100644 index 00000000..0eee235d --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java @@ -0,0 +1,26 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; + +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; + +public class MySqlTableMetaStorageFactory implements TableMetaStorageFactory { + + private MySqlTableMetaCallback mySQLTableMetaCallback; + private String dbName; + + public MySqlTableMetaStorageFactory(MySqlTableMetaCallback callback, String dbName) { + mySQLTableMetaCallback = callback; + this.dbName = dbName; + } + + @Override + public TableMetaStorage getTableMetaStorage() { + return new MySqlTableMetaStorage(mySQLTableMetaCallback, dbName); + } + + @Override + public String getDbName() { + return dbName; + } + +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java new file mode 100644 index 00000000..04fd95ea --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java @@ -0,0 +1,38 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.util.Date; + +public class NoStorageTest { + final String DBNAME = "testdb"; + final String TBNAME = "testtb"; + final String DDL = "CREATE TABLE `testtb` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `name` varchar(2048) DEFAULT NULL,\n" + + " `datachange_lasttime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最晚更新时间',\n" + + " `otter_testcol` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol1` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol2` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol3` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol4` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol5` varchar(45) DEFAULT NULL,\n" + + " PRIMARY KEY (`id`)\n" + + " ) ENGINE=InnoDB AUTO_INCREMENT=58333898 DEFAULT CHARSET=utf8mb4"; + @Test + public void nostorage() { + MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + TableMetaCacheWithStorage tableMetaCacheWithStorage = new TableMetaCacheWithStorage(connection, null); + EntryPosition entryPosition = new EntryPosition(); + entryPosition.setTimestamp(new Date().getTime()); + String fullTableName = DBNAME + "." + TBNAME; + tableMetaCacheWithStorage.apply(entryPosition, fullTableName, DDL, null); + entryPosition.setTimestamp(new Date().getTime() + 1000L); + TableMeta result = tableMetaCacheWithStorage.getTableMeta(DBNAME, TBNAME, false, entryPosition); + assert result.getDdl().equalsIgnoreCase(DDL); + } +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java new file mode 100644 index 00000000..0ec16607 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java @@ -0,0 +1,77 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaCallback; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaStorageFactory; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import com.alibaba.otter.canal.protocol.position.Position; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class StorageTest { + + final String DBNAME = "testdb"; + final String TBNAME = "testtb"; + final String DDL = "CREATE TABLE `testtb` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `name` varchar(2048) DEFAULT NULL,\n" + + " `datachange_lasttime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最晚更新时间',\n" + + " `otter_testcol` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol1` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol2` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol3` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol4` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol5` varchar(45) DEFAULT NULL,\n" + + " PRIMARY KEY (`id`)\n" + + " ) ENGINE=InnoDB AUTO_INCREMENT=58333898 DEFAULT CHARSET=utf8mb4"; + + @Test + public void storage() { + + MySqlTableMetaStorageFactory factory = new MySqlTableMetaStorageFactory(new MySqlTableMetaCallback() { + @Override + public void save(String dbAddress, String schema, String table, String ddl, Long timestamp) { + + } + + @Override + public List fetch(String dbAddress, String dbName) { + TableMetaEntry tableMeta = new TableMetaEntry(); + tableMeta.setSchema(DBNAME); + tableMeta.setTable(TBNAME); + tableMeta.setDdl(DDL); + tableMeta.setTimestamp(new Date().getTime()); + List entries = new ArrayList(); + entries.add(tableMeta); + return entries; + } + + @Override + public List fetch(String dbAddress, String dbName, String tableName) { + TableMetaEntry tableMeta = new TableMetaEntry(); + tableMeta.setSchema(DBNAME); + tableMeta.setTable(TBNAME); + tableMeta.setDdl(DDL); + tableMeta.setTimestamp(new Date().getTime()); + List entries = new ArrayList(); + entries.add(tableMeta); + return entries; + } + }, DBNAME); + MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + TableMetaCacheWithStorage tableMetaCacheWithStorage = new TableMetaCacheWithStorage(connection, factory.getTableMetaStorage()); + EntryPosition entryPosition = new EntryPosition(); + entryPosition.setTimestamp(new Date().getTime()); + String fullTableName = DBNAME + "." + TBNAME; + tableMetaCacheWithStorage.apply(entryPosition, fullTableName, DDL, null); + + entryPosition.setTimestamp(new Date().getTime() + 1000L); + TableMeta result = tableMetaCacheWithStorage.getTableMeta(DBNAME, TBNAME, false, entryPosition); + assert result.getDdl().equalsIgnoreCase(DDL); + } +} diff --git a/pom.xml b/pom.xml index f48a488a..e9c2623b 100644 --- a/pom.xml +++ b/pom.xml @@ -254,7 +254,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_366 + 2.0.0_preview_186 com.alibaba From cd0bf60824e6e469982c2a7b04448393c5198529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 7 Aug 2018 16:19:58 +0800 Subject: [PATCH 042/360] fixed compiler java 1.7 --- example/pom.xml | 13 ------------- pom.xml | 6 +++--- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/example/pom.xml b/example/pom.xml index ba7a6dde..78e48c4b 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -21,11 +21,6 @@ canal.protocol ${project.version} - - com.alibaba.otter - canal.instance.spring - ${project.version} - org.apache.ddlutils ddlutils @@ -135,14 +130,6 @@ false - - org.apache.maven.plugins - maven-compiler-plugin - - 7 - 7 - - diff --git a/pom.xml b/pom.xml index 9a14868b..24fe92db 100644 --- a/pom.xml +++ b/pom.xml @@ -96,8 +96,8 @@ true true - 1.6 - 1.6 + 1.7 + 1.7 UTF-8 3.2.9.RELEASE @@ -332,7 +332,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.2 + 3.8.0 ${java_source_version} ${java_target_version} From 4006cedaad464e2b2accd6d92b7401a4cbf6d643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 7 Aug 2018 16:44:40 +0800 Subject: [PATCH 043/360] =?UTF-8?q?fixed=20issue=20#781,=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=83=A8=E5=88=86DDL=E8=A7=A3=E6=9E=90=E5=BC=82?= =?UTF-8?q?=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 24fe92db..1e4bdbf6 100644 --- a/pom.xml +++ b/pom.xml @@ -247,7 +247,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_520 + 2.0.0_preview_538 com.alibaba From 6d3584b6fb0f8060427ecf23a64776e27c50edad Mon Sep 17 00:00:00 2001 From: winger Date: Tue, 7 Aug 2018 16:59:55 +0800 Subject: [PATCH 044/360] =?UTF-8?q?=E7=A7=BB=E9=99=A4canal.instance.spring?= =?UTF-8?q?=20=E4=BE=9D=E8=B5=96=E5=90=8E=EF=BC=8C=E9=9C=80=E8=A6=81?= =?UTF-8?q?=E5=8A=A0=E5=85=A5druid=E4=B8=8Emysql=20connector=E7=9A=84?= =?UTF-8?q?=E4=BE=9D=E8=B5=96=202.=20PropertyPlaceholderConfigurer?= =?UTF-8?q?=E7=94=B1canal.instance.spring=E7=A7=BB=E8=87=B3db?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- example/pom.xml | 8 + .../db/PropertyPlaceholderConfigurer.java | 169 ++++++++++++++++++ example/src/main/resources/client-spring.xml | 2 +- 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java diff --git a/example/pom.xml b/example/pom.xml index 78e48c4b..2897e821 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -21,6 +21,14 @@ canal.protocol ${project.version} + + com.alibaba + druid + + + mysql + mysql-connector-java + org.apache.ddlutils ddlutils diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java b/example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java new file mode 100644 index 00000000..65b4827c --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java @@ -0,0 +1,169 @@ +package com.alibaba.otter.canal.example.db; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * 扩展Spring的 + * {@linkplain org.springframework.beans.factory.config.PropertyPlaceholderConfigurer} + * ,增加默认值的功能。 例如:${placeholder:defaultValue},假如placeholder的值不存在,则默认取得 + * defaultValue。 + * + * @author jianghang 2013-1-24 下午03:37:56 + * @version 1.0.0 + */ +public class PropertyPlaceholderConfigurer extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer implements ResourceLoaderAware, InitializingBean { + + private static final String PLACEHOLDER_PREFIX = "${"; + private static final String PLACEHOLDER_SUFFIX = "}"; + private ResourceLoader loader; + private String[] locationNames; + + public PropertyPlaceholderConfigurer(){ + setIgnoreUnresolvablePlaceholders(true); + } + + public void setResourceLoader(ResourceLoader loader) { + this.loader = loader; + } + + public void setLocationNames(String[] locations) { + this.locationNames = locations; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(loader, "no resourceLoader"); + + if (locationNames != null) { + for (int i = 0; i < locationNames.length; i++) { + locationNames[i] = resolveSystemPropertyPlaceholders(locationNames[i]); + } + } + + if (locationNames != null) { + List resources = new ArrayList(locationNames.length); + + for (String location : locationNames) { + location = trimToNull(location); + + if (location != null) { + resources.add(loader.getResource(location)); + } + } + + super.setLocations(resources.toArray(new Resource[resources.size()])); + } + } + + private String resolveSystemPropertyPlaceholders(String text) { + StringBuilder buf = new StringBuilder(text); + + for (int startIndex = buf.indexOf(PLACEHOLDER_PREFIX); startIndex >= 0;) { + int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length()); + + if (endIndex != -1) { + String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); + int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length(); + + try { + String value = resolveSystemPropertyPlaceholder(placeholder); + + if (value != null) { + buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), value); + nextIndex = startIndex + value.length(); + } else { + System.err.println("Could not resolve placeholder '" + + placeholder + + "' in [" + + text + + "] as system property: neither system property nor environment variable found"); + } + } catch (Throwable ex) { + System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text + + "] as system property: " + ex); + } + + startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex); + } else { + startIndex = -1; + } + } + + return buf.toString(); + } + + private String resolveSystemPropertyPlaceholder(String placeholder) { + DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder); + String value = System.getProperty(dp.placeholder); + + if (value == null) { + value = System.getenv(dp.placeholder); + } + + if (value == null) { + value = dp.defaultValue; + } + + return value; + } + + @Override + protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) { + DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder); + String value = super.resolvePlaceholder(dp.placeholder, props, systemPropertiesMode); + + if (value == null) { + value = dp.defaultValue; + } + + return trimToEmpty(value); + } + + private static class DefaultablePlaceholder { + + private final String defaultValue; + private final String placeholder; + + public DefaultablePlaceholder(String placeholder){ + int commaIndex = placeholder.indexOf(":"); + String defaultValue = null; + + if (commaIndex >= 0) { + defaultValue = trimToEmpty(placeholder.substring(commaIndex + 1)); + placeholder = trimToEmpty(placeholder.substring(0, commaIndex)); + } + + this.placeholder = placeholder; + this.defaultValue = defaultValue; + } + } + + private String trimToNull(String str) { + if (str == null) { + return null; + } + + String result = str.trim(); + + if (result == null || result.length() == 0) { + return null; + } + + return result; + } + + public static String trimToEmpty(String str) { + if (str == null) { + return ""; + } + + return str.trim(); + } +} diff --git a/example/src/main/resources/client-spring.xml b/example/src/main/resources/client-spring.xml index d9319007..29eba1af 100644 --- a/example/src/main/resources/client-spring.xml +++ b/example/src/main/resources/client-spring.xml @@ -4,7 +4,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd" default-autowire="byName"> - + From f36ce647e690349bb710c01287f359274dd656c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 7 Aug 2018 17:46:36 +0800 Subject: [PATCH 045/360] fixed issue #781 , upgrade fastsql 540 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 1e4bdbf6..56ac821a 100644 --- a/pom.xml +++ b/pom.xml @@ -247,7 +247,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_538 + 2.0.0_preview_540 com.alibaba From 23f7b3b5d82c3571454877e32bc6f5bbd020b4c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 7 Aug 2018 18:51:23 +0800 Subject: [PATCH 046/360] fixed issue #818 , tableMeta tsdb bugfix --- .../otter/canal/deployer/CanalController.java | 3 ++- .../inbound/mysql/AbstractMysqlEventParser.java | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 0a2c91f8..6b0fc934 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -34,6 +34,7 @@ import com.alibaba.otter.canal.instance.core.CanalInstanceGenerator; import com.alibaba.otter.canal.instance.manager.CanalConfigClient; import com.alibaba.otter.canal.instance.manager.ManagerCanalInstanceGenerator; import com.alibaba.otter.canal.instance.spring.SpringCanalInstanceGenerator; +import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; import com.alibaba.otter.canal.server.exception.CanalServerException; import com.alibaba.otter.canal.server.netty.CanalServerWithNetty; @@ -303,7 +304,7 @@ public class CanalController { return instanceGenerator.generate(destination); } else if (config.getMode().isSpring()) { SpringCanalInstanceGenerator instanceGenerator = new SpringCanalInstanceGenerator(); - synchronized (this) { + synchronized (CanalEventParser.class) { try { // 设置当前正在加载的通道,加载spring查找文件时会用到该变量 System.setProperty(CanalConstants.CANAL_DESTINATION_PROPERTY, destination); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index 8188cf83..c0d2f190 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.filter.CanalEventFilter; import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; +import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; @@ -91,8 +92,16 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { public void start() throws CanalParseException { if (enableTsdb) { if (tableMetaTSDB == null) { - // 初始化 - tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml); + synchronized (CanalEventParser.class) { + try { + // 设置当前正在加载的通道,加载spring查找文件时会用到该变量 + System.setProperty("canal.instance.destination", destination); + // 初始化 + tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml); + } finally { + System.setProperty("canal.instance.destination", ""); + } + } } } From 9e816bc48f9955f9e2839873993cef9627c2f389 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 8 Aug 2018 18:54:57 +0800 Subject: [PATCH 047/360] support otter use tableMetaTSDB --- .../dbsync/binlog/event/RowsLogBuffer.java | 23 ++- .../manager/CanalInstanceWithManager.java | 30 +-- .../manager/model/CanalParameter.java | 44 +++- .../mysql/AbstractMysqlEventParser.java | 43 ++-- .../parse/inbound/mysql/MysqlEventParser.java | 28 +-- .../inbound/mysql/dbsync/LogEventConvert.java | 25 +-- .../inbound/mysql/dbsync/TableMetaCache.java | 7 +- .../tablemeta/HistoryTableMetaCache.java | 191 ------------------ .../tablemeta/TableMetaCacheInterface.java | 20 -- .../tablemeta/TableMetaCacheWithStorage.java | 105 ---------- .../mysql/tablemeta/TableMetaEntry.java | 55 ----- .../mysql/tablemeta/TableMetaStorage.java | 18 -- .../tablemeta/TableMetaStorageFactory.java | 9 - .../exception/CacheConnectionNull.java | 9 - .../exception/NoHistoryException.java | 21 -- .../impl/mysql/MySqlTableMetaCallback.java | 14 -- .../impl/mysql/MySqlTableMetaStorage.java | 48 ----- .../mysql/MySqlTableMetaStorageFactory.java | 26 --- .../tsdb/DefaultTableMetaTSDBFactory.java | 19 ++ .../mysql/tsdb/TableMetaTSDBBuilder.java | 7 +- .../mysql/tsdb/TableMetaTSDBFactory.java | 18 ++ 21 files changed, 149 insertions(+), 611 deletions(-) delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DefaultTableMetaTSDBFactory.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBFactory.java diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java index fdfb3ebc..bc0f3dde 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java @@ -490,7 +490,6 @@ public final class RowsLogBuffer { // t % 100); StringBuilder builder = new StringBuilder(); - builder.append(26); appendNumber4(builder, d / 10000); builder.append('-'); appendNumber2(builder, (d % 10000) / 100); @@ -615,7 +614,13 @@ public final class RowsLogBuffer { if (i32 < 0) { builder.append('-'); } - appendNumber2(builder, u32 / 10000); + + int d = u32 / 10000; + if (d > 100) { + builder.append(String.valueOf(d)); + } else { + appendNumber2(builder, d); + } builder.append(':'); appendNumber2(builder, (u32 % 10000) / 100); builder.append(':'); @@ -724,7 +729,13 @@ public final class RowsLogBuffer { if (ltime < 0) { builder.append('-'); } - appendNumber2(builder, (int) ((intpart >> 12) % (1 << 10))); + + int d = (int) ((intpart >> 12) % (1 << 10)); + if (d > 100) { + builder.append(String.valueOf(d)); + } else { + appendNumber2(builder, d); + } builder.append(':'); appendNumber2(builder, (int) ((intpart >> 6) % (1 << 6))); builder.append(':'); @@ -1134,7 +1145,7 @@ public final class RowsLogBuffer { .append(digits[(d / 100) % 10]) .append(digits[(d / 10) % 10]) .append(digits[d % 10]); - } else if (d >= 100) { + } else { builder.append('0'); appendNumber3(builder, d); } @@ -1142,9 +1153,7 @@ public final class RowsLogBuffer { private void appendNumber3(StringBuilder builder, int d) { if (d >= 100) { - builder.append(digits[d / 100]) - .append(digits[(d / 10) % 10]) - .append(digits[d % 10]); + builder.append(digits[d / 100]).append(digits[(d / 10) % 10]).append(digits[d % 10]); } else { builder.append('0'); appendNumber2(builder, d); diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index 039a80b7..63ed2d02 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -6,11 +6,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; - -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.HistoryTableMetaCache; - -import com.alibaba.otter.canal.meta.FileMixedMetaManager; - import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,7 +20,14 @@ import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.instance.core.AbstractCanalInstance; import com.alibaba.otter.canal.instance.manager.model.Canal; import com.alibaba.otter.canal.instance.manager.model.CanalParameter; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.*; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.DataSourcing; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.HAMode; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.IndexMode; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.MetaMode; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.SourcingType; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageMode; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageScavengeMode; +import com.alibaba.otter.canal.meta.FileMixedMetaManager; import com.alibaba.otter.canal.meta.MemoryMetaManager; import com.alibaba.otter.canal.meta.PeriodMixedMetaManager; import com.alibaba.otter.canal.meta.ZooKeeperMetaManager; @@ -36,7 +38,12 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import com.alibaba.otter.canal.parse.index.*; +import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; +import com.alibaba.otter.canal.parse.index.FailbackLogPositionManager; +import com.alibaba.otter.canal.parse.index.MemoryLogPositionManager; +import com.alibaba.otter.canal.parse.index.MetaLogPositionManager; +import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager; +import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager; import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.sink.entry.EntryEventSink; @@ -114,7 +121,7 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { ZooKeeperMetaManager zooKeeperMetaManager = new ZooKeeperMetaManager(); zooKeeperMetaManager.setZkClientx(getZkclientx()); ((PeriodMixedMetaManager) metaManager).setZooKeeperMetaManager(zooKeeperMetaManager); - } else if (mode.isLocalFile()){ + } else if (mode.isLocalFile()) { FileMixedMetaManager fileMixedMetaManager = new FileMixedMetaManager(); fileMixedMetaManager.setDataDir(parameters.getDataDir()); fileMixedMetaManager.setPeriod(parameters.getMetaFileFlushPeriod()); @@ -241,13 +248,6 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { mysqlEventParser.setDetectingIntervalInSeconds(parameters.getDetectingIntervalInSeconds()); // 数据库信息参数 mysqlEventParser.setSlaveId(parameters.getSlaveId()); - mysqlEventParser.setTableMetaStorageFactory(parameters.getTableMetaStorageFactory()); - // Ctrip callback -// mysqlEventParser.setCallback(parameters.getCallback()); -// HistoryTableMetaCache cache = new HistoryTableMetaCache(); -// cache.init(parameters.getEntries()); -// mysqlEventParser.setHistoryTableMetaCache(cache); - if (!CollectionUtils.isEmpty(dbAddresses)) { mysqlEventParser.setMasterInfo(new AuthenticationInfo(dbAddresses.get(0), parameters.getDbUsername(), diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java index d707c679..40df1485 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java @@ -5,7 +5,6 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; @@ -94,6 +93,10 @@ public class CanalParameter implements Serializable { private Boolean filterTableError = Boolean.FALSE; // 是否忽略表解析异常 private String blackFilter = null; // 匹配黑名单,忽略解析 + private Boolean tsdbEnable = Boolean.FALSE; // 是否开启tableMetaTSDB + private String tsdbJdbcUrl; + private String tsdbJdbcUserName; + private String tsdbJdbcPassword; // ================================== 兼容字段处理 private InetSocketAddress masterAddress; // 主库信息 private String masterUsername; // 帐号 @@ -109,9 +112,6 @@ public class CanalParameter implements Serializable { private Long standbyLogfileOffest = null; private Long standbyTimestamp = null; - // Ctrip Table Meta - TableMetaStorageFactory tableMetaStorageFactory; - public static enum RunMode { /** 嵌入式 */ @@ -250,7 +250,7 @@ public class CanalParameter implements Serializable { ZOOKEEPER, /** 混合模式,内存+文件 */ MIXED, - /** 本地文件存储模式*/ + /** 本地文件存储模式 */ LOCAL_FILE; public boolean isMemory() { @@ -265,7 +265,7 @@ public class CanalParameter implements Serializable { return this.equals(MetaMode.MIXED); } - public boolean isLocalFile(){ + public boolean isLocalFile() { return this.equals(MetaMode.LOCAL_FILE); } } @@ -887,12 +887,36 @@ public class CanalParameter implements Serializable { this.blackFilter = blackFilter; } - public TableMetaStorageFactory getTableMetaStorageFactory() { - return tableMetaStorageFactory; + public Boolean getTsdbEnable() { + return tsdbEnable; } - public void setTableMetaStorageFactory(TableMetaStorageFactory tableMetaStorageFactory) { - this.tableMetaStorageFactory = tableMetaStorageFactory; + public void setTsdbEnable(Boolean tsdbEnable) { + this.tsdbEnable = tsdbEnable; + } + + public String getTsdbJdbcUrl() { + return tsdbJdbcUrl; + } + + public void setTsdbJdbcUrl(String tsdbJdbcUrl) { + this.tsdbJdbcUrl = tsdbJdbcUrl; + } + + public String getTsdbJdbcUserName() { + return tsdbJdbcUserName; + } + + public void setTsdbJdbcUserName(String tsdbJdbcUserName) { + this.tsdbJdbcUserName = tsdbJdbcUserName; + } + + public String getTsdbJdbcPassword() { + return tsdbJdbcPassword; + } + + public void setTsdbJdbcPassword(String tsdbJdbcPassword) { + this.tsdbJdbcPassword = tsdbJdbcPassword; } public String toString() { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index c0d2f190..17eb08f4 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -14,27 +14,30 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; import com.alibaba.otter.canal.parse.inbound.BinlogParser; import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert; +import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DefaultTableMetaTSDBFactory; import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB; -import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder; +import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBFactory; import com.alibaba.otter.canal.protocol.position.EntryPosition; public abstract class AbstractMysqlEventParser extends AbstractEventParser { - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - protected static final long BINLOG_START_OFFEST = 4L; + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + protected static final long BINLOG_START_OFFEST = 4L; + + protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory(); + protected boolean enableTsdb = false; + protected String tsdbSpringXml; + protected TableMetaTSDB tableMetaTSDB; - protected boolean enableTsdb = false; - protected String tsdbSpringXml; - protected TableMetaTSDB tableMetaTSDB; // 编码信息 - protected byte connectionCharsetNumber = (byte) 33; - protected Charset connectionCharset = Charset.forName("UTF-8"); - protected boolean filterQueryDcl = false; - protected boolean filterQueryDml = false; - protected boolean filterQueryDdl = false; - protected boolean filterRows = false; - protected boolean filterTableError = false; - protected boolean useDruidDdlFilter = true; + protected byte connectionCharsetNumber = (byte) 33; + protected Charset connectionCharset = Charset.forName("UTF-8"); + protected boolean filterQueryDcl = false; + protected boolean filterQueryDml = false; + protected boolean filterQueryDdl = false; + protected boolean filterRows = false; + protected boolean filterTableError = false; + protected boolean useDruidDdlFilter = true; protected BinlogParser buildParser() { LogEventConvert convert = new LogEventConvert(); @@ -97,7 +100,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { // 设置当前正在加载的通道,加载spring查找文件时会用到该变量 System.setProperty("canal.instance.destination", destination); // 初始化 - tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml); + tableMetaTSDB = tableMetaTSDBFactory.build(destination, tsdbSpringXml); } finally { System.setProperty("canal.instance.destination", ""); } @@ -110,7 +113,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { public void stop() throws CanalParseException { if (enableTsdb) { - TableMetaTSDBBuilder.destory(destination); + tableMetaTSDBFactory.destory(destination); tableMetaTSDB = null; } @@ -182,7 +185,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { if (this.enableTsdb) { if (tableMetaTSDB == null) { // 初始化 - tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml); + tableMetaTSDB = tableMetaTSDBFactory.build(destination, tsdbSpringXml); } } } @@ -192,9 +195,13 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { if (this.enableTsdb) { if (tableMetaTSDB == null) { // 初始化 - tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml); + tableMetaTSDB = tableMetaTSDBFactory.build(destination, tsdbSpringXml); } } } + public void setTableMetaTSDBFactory(TableMetaTSDBFactory tableMetaTSDBFactory) { + this.tableMetaTSDBFactory = tableMetaTSDBFactory; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index aeb3b2ea..53e4e5e5 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -10,11 +10,6 @@ import java.util.Map; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicLong; -import com.alibaba.otter.canal.parse.inbound.*; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheWithStorage; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; @@ -25,6 +20,9 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.ha.CanalHAController; +import com.alibaba.otter.canal.parse.inbound.ErosaConnection; +import com.alibaba.otter.canal.parse.inbound.HeartBeatCallback; +import com.alibaba.otter.canal.parse.inbound.SinkFunction; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogFormat; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert; @@ -64,8 +62,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE // 心跳检查信息 private String detectingSQL; // 心跳sql private MysqlConnection metaConnection; // 查询meta信息的链接 - private TableMetaCacheInterface tableMetaCache; // 对应meta - // cache + private TableMetaCache tableMetaCache; // 对应meta private int fallbackIntervalInSeconds = 60; // 切换回退时间 private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 @@ -74,8 +71,6 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private int dumpErrorCount = 0; // binlogDump失败异常计数 private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 - private TableMetaStorageFactory tableMetaStorageFactory; - protected ErosaConnection buildErosaConnection() { return buildMysqlConnection(this.runningInfo); } @@ -129,13 +124,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter); } - - TableMetaStorage storage = null; - if (tableMetaStorageFactory != null) { - storage = tableMetaStorageFactory.getTableMetaStorage(); - } - - tableMetaCache = new TableMetaCacheWithStorage(metaConnection, storage); + tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB); ((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache); } } @@ -921,11 +910,4 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE this.dumpErrorCountThreshold = dumpErrorCountThreshold; } - public TableMetaStorageFactory getTableMetaStorageFactory() { - return tableMetaStorageFactory; - } - - public void setTableMetaStorageFactory(TableMetaStorageFactory tableMetaStorageFactory) { - this.tableMetaStorageFactory = tableMetaStorageFactory; - } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 72369f07..dc8e57d7 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -10,9 +10,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; @@ -90,10 +87,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private volatile AviaterRegexFilter nameFilter; // 运行时引用可能会有变化,比如规则发生变化时 private volatile AviaterRegexFilter nameBlackFilter; - - private TableMetaCacheInterface tableMetaCache; - private String binlogFileName = "mysql-bin.000001"; - + private TableMetaCache tableMetaCache; private Charset charset = Charset.defaultCharset(); private boolean filterQueryDcl = false; private boolean filterQueryDml = false; @@ -268,8 +262,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar if (!isSeek) { // 使用新的表结构元数据管理方式 EntryPosition position = createPosition(event.getHeader()); - String fulltbName = schemaName+"."+tableName; - tableMetaCache.apply(position, fulltbName, queryString, null); + tableMetaCache.apply(position, event.getDbName(), queryString, null); } Header header = createHeader(event.getHeader(), schemaName, tableName, type); @@ -548,12 +541,16 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar tableError |= parseOneRow(rowDataBuilder, event, buffer, changeColumns, true, tableMeta); } - rowsCount ++; + rowsCount++; rowChangeBuider.addRowDatas(rowDataBuilder.build()); } TableMapLogEvent table = event.getTable(); - Header header = createHeader(event.getHeader(), table.getDbName(), table.getTableName(), eventType, rowsCount); + Header header = createHeader(event.getHeader(), + table.getDbName(), + table.getTableName(), + eventType, + rowsCount); RowChange rowChange = rowChangeBuider.build(); if (tableError) { @@ -808,12 +805,12 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar return createEntry(header, EntryType.ROWDATA, rowChangeBuider.build().toByteString()); } - private Header createHeader(LogHeader logHeader, String schemaName, String tableName, EventType eventType) { return createHeader(logHeader, schemaName, tableName, eventType, -1); } - private Header createHeader(LogHeader logHeader, String schemaName, String tableName, EventType eventType, Integer rowsCount) { + private Header createHeader(LogHeader logHeader, String schemaName, String tableName, EventType eventType, + Integer rowsCount) { // header会做信息冗余,方便以后做检索或者过滤 Header.Builder headerBuilder = Header.newBuilder(); headerBuilder.setVersion(version); @@ -940,7 +937,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar this.nameBlackFilter = nameBlackFilter; } - public void setTableMetaCache(TableMetaCacheInterface tableMetaCache) { + public void setTableMetaCache(TableMetaCache tableMetaCache) { this.tableMetaCache = tableMetaCache; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java index 5c782aa1..4c1cef3c 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java @@ -6,7 +6,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; @@ -30,7 +29,7 @@ import com.google.common.cache.LoadingCache; * @author jianghang 2013-1-17 下午10:15:16 * @version 1.0.0 */ -public class TableMetaCache implements TableMetaCacheInterface { +public class TableMetaCache { public static final String COLUMN_NAME = "COLUMN_NAME"; public static final String COLUMN_TYPE = "COLUMN_TYPE"; @@ -100,10 +99,6 @@ public class TableMetaCache implements TableMetaCacheInterface { String createDDL = packet.getFieldValues().get(1); MemoryTableMeta memoryTableMeta = new MemoryTableMeta(); memoryTableMeta.apply(DatabaseTableMeta.INIT_POSITION, schema, createDDL, null); - String[] strings = table.split("\\."); - if (strings.length > 1) { - table = strings[1]; - } TableMeta tableMeta = memoryTableMeta.find(schema, table); return tableMeta.getFields(); } else { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java deleted file mode 100644 index 0964627d..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; -import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.CacheConnectionNull; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - -import java.io.IOException; -import java.util.*; - -public class HistoryTableMetaCache { - private TableMetaStorage tableMetaStorage; - private MysqlConnection metaConnection; - private LoadingCache> cache; // 第一层:数据库名.表名,第二层时间戳,TableMeta - - public HistoryTableMetaCache() { - cache = CacheBuilder.newBuilder().build(new CacheLoader>() { - @Override - public Map load(String tableName) throws Exception { - Long timestamp = new Date().getTime(); - String[] strs = tableName.split("\\."); - String schema = strs[0]; - if (tableMetaStorage != null) { - init(tableMetaStorage.fetchByTableName(tableName)); // 从存储中读取表的历史ddl - } - ResultSetPacket resultSetPacket = connectionQuery("show create table " + tableName); // 获取当前ddl - String currentDdl = resultSetPacket.getFieldValues().get(1); - if (cache.asMap().containsKey(tableName)) { - Map tableMetaMap = cache.getUnchecked(tableName); - if (tableMetaMap.isEmpty()) { - put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema,取时间为当前时间-1s - } else { // 如果table存在历史 - Iterator iterator = tableMetaMap.keySet().iterator(); - Long firstTimestamp = iterator.next(); - TableMeta first = tableMetaMap.get(firstTimestamp); // 拿第一条ddl - if (!first.getDdl().equalsIgnoreCase(currentDdl)) { // 当前ddl与历史第一条不一致,放入当前ddl - put(schema, tableName, currentDdl, calculateNewTimestamp(firstTimestamp)); // 计算放入的timestamp,设为第一条时间+1s - } - } - } else { - put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema - } - return cache.get(tableName); - } - }); - } - - public void init(List entries) throws IOException { - if (entries == null) { - return; - } - for (TableMetaEntry entry : entries) { - try { - put(entry.getSchema(), entry.getTable(), entry.getDdl(), entry.getTimestamp()); - } catch (CacheConnectionNull cacheConnectionNull) { - cacheConnectionNull.printStackTrace(); - } - } - } - - public TableMeta put(String schema, String table, String ddl, Long timestamp) throws CacheConnectionNull, IOException { - ResultSetPacket resultSetPacket; - if (!(ddl.contains("CREATE TABLE") || ddl.contains("create table"))) { // 尝试直接从数据库拉取CREATE TABLE的DDL - resultSetPacket = connectionQuery("show create table " + table); - ddl = resultSetPacket.getFieldValues().get(1); - } else { // CREATE TABLE 的 DDL - resultSetPacket = new ResultSetPacket(); - List fields = new ArrayList(); - String[] strings = table.split("\\."); - String shortTable = table; - if (strings.length > 1) { - shortTable = strings[1]; - } - fields.add(0, shortTable); - fields.add(1, ddl); - resultSetPacket.setFieldValues(fields); - if (metaConnection != null) { - resultSetPacket.setSourceAddress(metaConnection.getAddress()); - } - } - Map tableMetaMap; - if (!cache.asMap().containsKey(table)) { - tableMetaMap = new TreeMap(new Comparator() { - @Override - public int compare(Long o1, Long o2) { - return o2.compareTo(o1); - } - }); - cache.put(table, tableMetaMap); - } else { - tableMetaMap = cache.getUnchecked(table); - } - eliminate(tableMetaMap); // 淘汰旧的TableMeta - TableMeta tableMeta = new TableMeta(schema, table, TableMetaCache.parseTableMeta(schema, table, resultSetPacket)); - if (tableMeta.getDdl() == null) { // 生成的TableMeta有时DDL为null - tableMeta.setDdl(ddl); - } - tableMetaMap.put(timestamp, tableMeta); - return tableMeta; - } - - public TableMeta get(String schema, String table, Long timestamp) throws NoHistoryException, CacheConnectionNull { - Map tableMetaMap = cache.getUnchecked(table); - Iterator iterator = tableMetaMap.keySet().iterator(); - Long selected = null; - while(iterator.hasNext()) { - Long temp = iterator.next(); - if (timestamp > temp) { - selected = temp; - break; - } - } - - if (selected == null) { - iterator = tableMetaMap.keySet().iterator(); - if (iterator.hasNext()) { - selected = iterator.next(); - } else { - throw new NoHistoryException(schema, table); - } - } - - return tableMetaMap.get(selected); - } - - public void clearTableMeta() { - cache.invalidateAll(); - } - - public void clearTableMetaWithSchemaName(String schema) { - for (String tableName : cache.asMap().keySet()) { - String[] strs = tableName.split("\\."); - if (schema.equalsIgnoreCase(strs[0])) { - cache.invalidate(tableName); - } - } - } - - public void clearTableMeta(String schema, String table) { - if (!table.contains(".")) { - table = schema+"."+table; - } - cache.invalidate(table); - } - - // eliminate older table meta in cache - private void eliminate(Map tableMetaMap) { - int MAX_CAPABILITY = 20; - if (tableMetaMap.keySet().size() < MAX_CAPABILITY) { - return; - } - Iterator iterator = tableMetaMap.keySet().iterator(); - while(iterator.hasNext()) { - iterator.next(); - } - iterator.remove(); - } - - private Long calculateNewTimestamp(Long oldTimestamp) { - return oldTimestamp + 1000; - } - - private ResultSetPacket connectionQuery(String query) throws CacheConnectionNull, IOException { - if (metaConnection == null) { - throw new CacheConnectionNull(); - } - try { - return metaConnection.query(query); - } catch (IOException e) { - try { - metaConnection.reconnect(); - return metaConnection.query(query); - } catch (IOException e1) { - throw e1; - } - } - } - - public void setMetaConnection(MysqlConnection metaConnection) { - this.metaConnection = metaConnection; - } - - public void setTableMetaStorage(TableMetaStorage tableMetaStorage) { - this.tableMetaStorage = tableMetaStorage; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java deleted file mode 100644 index 5196ee08..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.protocol.position.EntryPosition; - -public interface TableMetaCacheInterface { - - TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position); - - void clearTableMeta(); - - void clearTableMetaWithSchemaName(String schema); - - void clearTableMeta(String schema, String table); - - boolean apply(EntryPosition position, String schema, String ddl, String extra); - - boolean isOnRDS(); - -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java deleted file mode 100644 index eb43158b..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; -import com.alibaba.otter.canal.protocol.position.EntryPosition; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.InetSocketAddress; - -public class TableMetaCacheWithStorage implements TableMetaCacheInterface { - - private static Logger logger = LoggerFactory.getLogger(TableMetaCacheWithStorage.class); - private TableMetaStorage tableMetaStorage; // TableMeta存储 - private HistoryTableMetaCache cache = new HistoryTableMetaCache(); // cache - - public TableMetaCacheWithStorage(MysqlConnection con, TableMetaStorage tableMetaStorage) { - this.tableMetaStorage = tableMetaStorage; - InetSocketAddress address = con.getAddress(); - this.tableMetaStorage.setDbAddress(address.getHostName()+":"+address.getPort()); - cache.setMetaConnection(con); - cache.setTableMetaStorage(tableMetaStorage); - if (tableMetaStorage != null) { - try { - cache.init(tableMetaStorage.fetch()); // 初始化,从存储拉取TableMeta - } catch (IOException e) { - logger.error(e.getMessage()); - } - } - } - - @Override - public boolean apply(EntryPosition position, String fullTableName, String ddl, String extra) { - String[] strs = fullTableName.split("\\."); - String schema = strs[0]; - if (schema.equalsIgnoreCase("null")) { // ddl schema为null,放弃处理 - return false; - } - try { - TableMeta tableMeta = cache.get(schema, fullTableName, position.getTimestamp()); - if (!compare(tableMeta, ddl)) { // 获取最近的TableMeta,进行比对 - TableMeta result = cache.put(schema, fullTableName, ddl, calTimestamp(position.getTimestamp())); - if (tableMetaStorage != null && result != null) { // 储存 - tableMetaStorage.store(schema, fullTableName, result.getDdl(), calTimestamp(position.getTimestamp())); - } - } - return true; - } catch (Exception e) { - logger.error(e.toString()); - } - - return false; - } - - @Override - public boolean isOnRDS() { - return false; - } - - /*** - * - * @param schema dbname - * @param table tablename - * @param useCache unused - * @param position timestamp - * @return - */ - @Override - public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) { - String fulltbName = schema + "." + table; - try { - return cache.get(schema, fulltbName, position.getTimestamp()); - } catch (Exception e) { - logger.error(e.toString()); - } - return null; - } - - @Override - public void clearTableMeta() { - cache.clearTableMeta(); - } - - @Override - public void clearTableMetaWithSchemaName(String schema) { - cache.clearTableMetaWithSchemaName(schema); - } - - @Override - public void clearTableMeta(String schema, String table) { - cache.clearTableMeta(schema, table); - } - - private boolean compare(TableMeta tableMeta, String ddl) { - if (tableMeta == null) { - return false; - } - return tableMeta.getDdl().equalsIgnoreCase(ddl); - } - - private Long calTimestamp(Long timestamp) { - return timestamp; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java deleted file mode 100644 index 03e63ed7..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import java.io.Serializable; - -public class TableMetaEntry implements Serializable { - - private static final long serialVersionUID = -1350200637109107904L; - - private String dbAddress; - private String schema; - private String table; - private String ddl; - private Long timestamp; - - - public String getSchema() { - return schema; - } - - public void setSchema(String schema) { - this.schema = schema; - } - - public String getTable() { - return table; - } - - public void setTable(String table) { - this.table = table; - } - - public String getDdl() { - return ddl; - } - - public void setDdl(String ddl) { - this.ddl = ddl; - } - - public Long getTimestamp() { - return timestamp; - } - - public void setTimestamp(Long timestamp) { - this.timestamp = timestamp; - } - - public String getDbAddress() { - return dbAddress; - } - - public void setDbAddress(String dbAddress) { - this.dbAddress = dbAddress; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java deleted file mode 100644 index ac9d7a0a..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import java.util.List; - -public interface TableMetaStorage { - - void store(String schema, String table, String ddl, Long timestamp); - - List fetch(); - - List fetchByTableName(String tableName); - - String getDbName(); - - String getDbAddress(); - - void setDbAddress(String address); -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java deleted file mode 100644 index 0e532ebb..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -public interface TableMetaStorageFactory { - - TableMetaStorage getTableMetaStorage(); - - String getDbName(); - -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java deleted file mode 100644 index 1dadd78a..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; - -public class CacheConnectionNull extends Exception{ - - @Override - public String toString() { - return "CacheConnectionNull"; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java deleted file mode 100644 index d8f1d16b..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; - -public class NoHistoryException extends Exception{ - - private String dbName; - private String tbName; - - public NoHistoryException(String dbName, String tbName) { - this.dbName = dbName; - this.tbName = tbName; - } - - public void printTableName() { - System.out.println(dbName+"."+tbName); - } - - @Override - public String toString() { - return "NioHistoryException: " + dbName + " " + tbName; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java deleted file mode 100644 index 661986a1..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; - -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; - -import java.util.List; - -public interface MySqlTableMetaCallback { - - void save(String dbAddress, String schema, String table,String ddl, Long timestamp); - - List fetch(String dbAddress, String dbName); - - List fetch(String dbAddress, String dbName, String tableName); -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java deleted file mode 100644 index f120ef80..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; - -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; - -import java.util.List; - -public class MySqlTableMetaStorage implements TableMetaStorage { - private MySqlTableMetaCallback mySqlTableMetaCallback; - private String dbName; - private String dbAddress; - - MySqlTableMetaStorage(MySqlTableMetaCallback callback, String dbName) { - mySqlTableMetaCallback = callback; - this.dbName = dbName; - } - - - @Override - public void store(String schema, String table, String ddl, Long timestamp) { - mySqlTableMetaCallback.save(dbAddress, schema, table, ddl, timestamp); - } - - @Override - public List fetch() { - return mySqlTableMetaCallback.fetch(dbAddress, dbName); - } - - @Override - public List fetchByTableName(String tableName) { - return mySqlTableMetaCallback.fetch(dbAddress, dbName, tableName); - } - - @Override - public String getDbName() { - return dbName; - } - - @Override - public String getDbAddress() { - return dbAddress; - } - - @Override - public void setDbAddress(String address) { - this.dbAddress = address; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java deleted file mode 100644 index 0eee235d..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; - -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; - -public class MySqlTableMetaStorageFactory implements TableMetaStorageFactory { - - private MySqlTableMetaCallback mySQLTableMetaCallback; - private String dbName; - - public MySqlTableMetaStorageFactory(MySqlTableMetaCallback callback, String dbName) { - mySQLTableMetaCallback = callback; - this.dbName = dbName; - } - - @Override - public TableMetaStorage getTableMetaStorage() { - return new MySqlTableMetaStorage(mySQLTableMetaCallback, dbName); - } - - @Override - public String getDbName() { - return dbName; - } - -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DefaultTableMetaTSDBFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DefaultTableMetaTSDBFactory.java new file mode 100644 index 00000000..00e2744b --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DefaultTableMetaTSDBFactory.java @@ -0,0 +1,19 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tsdb; + +/** + * @author agapple 2017年10月11日 下午8:45:40 + * @since 1.0.25 + */ +public class DefaultTableMetaTSDBFactory implements TableMetaTSDBFactory { + + /** + * 代理一下tableMetaTSDB的获取,使用隔离的spring定义 + */ + public TableMetaTSDB build(String destination, String springXml) { + return TableMetaTSDBBuilder.build(destination, springXml); + } + + public void destory(String destination) { + TableMetaTSDBBuilder.destory(destination); + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java index 8e37ef71..107e1013 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java @@ -10,12 +10,15 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; import com.google.common.collect.Maps; /** - * @author agapple 2017年10月11日 下午8:45:40 + * tableMeta构造器 + * + * @author agapple 2018年8月8日 上午11:01:08 * @since 1.0.25 */ + public class TableMetaTSDBBuilder { - protected final static Logger logger = LoggerFactory.getLogger(TableMetaTSDBBuilder.class); + protected final static Logger logger = LoggerFactory.getLogger(DefaultTableMetaTSDBFactory.class); private static ConcurrentMap contexts = Maps.newConcurrentMap(); /** diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBFactory.java new file mode 100644 index 00000000..950645a8 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBFactory.java @@ -0,0 +1,18 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tsdb; + +/** + * tableMeta构造器,允许重载实现 + * + * @author agapple 2018年8月8日 上午11:01:08 + * @since 1.0.26 + */ + +public interface TableMetaTSDBFactory { + + /** + * 代理一下tableMetaTSDB的获取,使用隔离的spring定义 + */ + public TableMetaTSDB build(String destination, String springXml); + + public void destory(String destination); +} From 61c7f7738d33dfb6bd939c3a4211caf0ce86e218 Mon Sep 17 00:00:00 2001 From: "charles.lin" <848497337@qq.com> Date: Wed, 8 Aug 2018 19:29:52 +0800 Subject: [PATCH 048/360] =?UTF-8?q?rds=20binlog=20=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + README.md | 10 + .../client/impl/SimpleCanalConnector.java | 3 +- .../dbsync/binlog/event/GtidLogEvent.java | 5 +- .../tddl/dbsync/FetcherPerformanceTest.java | 52 ++ .../dbsync/binlog/DirectLogFetcherTest.java | 93 ++-- deployer/pom.xml | 8 + deployer/src/main/bin/metrics_env.sh | 15 + deployer/src/main/bin/startup.sh | 7 +- .../otter/canal/deployer/CanalController.java | 16 +- deployer/src/main/resources/canal.properties | 10 +- .../resources/example/instance.properties | 19 +- .../resources/spring/default-instance.xml | 5 +- .../main/resources/spring/file-instance.xml | 5 +- .../main/resources/spring/group-instance.xml | 9 +- .../main/resources/spring/local-instance.xml | 3 +- .../main/resources/spring/memory-instance.xml | 5 +- docker/Dockerfile | 67 +++ docker/build.sh | 30 ++ docker/image/admin/app.sh | 117 +++++ docker/image/admin/bin/clean_log | 2 + docker/image/admin/bin/clean_log.sh | 45 ++ docker/image/admin/health.sh | 13 + docker/image/alidata/bin/exec_rc_local.sh | 11 + docker/image/alidata/bin/lark-wait | 6 + docker/image/alidata/bin/main.sh | 27 + docker/image/alidata/init/02init-sshd.sh | 19 + docker/image/alidata/init/fix-hosts.py | 66 +++ docker/image/alidata/lib/proc.sh | 40 ++ docker/run.sh | 92 ++++ .../parse/driver/mysql/MysqlConnector.java | 2 +- .../client/BinlogDumpCommandPacket.java | 2 +- .../driver/mysql/socket/BioSocketChannel.java | 4 +- example/pom.xml | 76 +++ .../example/AbstractCanalClientTest.java | 1 + .../SimpleCanalClientPermanceTest.java | 68 +++ .../canal/example/db/AbstractDbClient.java | 144 ++++++ .../example/db/CanalConnectorClient.java | 488 ++++++++++++++++++ .../canal/example/db/MysqlLoadLauncher.java | 35 ++ .../db/PropertyPlaceholderConfigurer.java | 169 ++++++ .../canal/example/db/ServiceLocator.java | 44 ++ .../example/db/dialect/AbstractDbDialect.java | 121 +++++ .../db/dialect/AbstractSqlTemplate.java | 105 ++++ .../canal/example/db/dialect/DbDialect.java | 20 + .../canal/example/db/dialect/SqlTemplate.java | 40 ++ .../canal/example/db/dialect/TableType.java | 93 ++++ .../db/dialect/mysql/MysqlDialect.java | 32 ++ .../db/dialect/mysql/MysqlSqlTemplate.java | 84 +++ .../example/db/mysql/AbstractMysqlClient.java | 207 ++++++++ .../canal/example/db/mysql/MysqlClient.java | 23 + .../example/db/utils/ByteArrayConverter.java | 50 ++ .../canal/example/db/utils/DdlUtils.java | 326 ++++++++++++ .../db/utils/SqlTimestampConverter.java | 140 +++++ .../canal/example/db/utils/SqlUtils.java | 315 +++++++++++ example/src/main/resources/client-spring.xml | 53 ++ example/src/main/resources/client.properties | 16 + .../manager/CanalInstanceWithManager.java | 32 +- .../manager/model/CanalParameter.java | 38 +- .../kafka/client/MessageDeserializer.java | 4 +- .../client/running/ClientRunningData.java | 4 +- .../client/running/ClientRunningMonitor.java | 82 +-- .../otter/canal/kafka/CanalServerStarter.java | 6 +- .../kafka/producer/CanalKafkaProducer.java | 42 +- .../kafka/producer/CanalKafkaStarter.java | 7 +- .../canal/kafka/producer/KafkaProperties.java | 25 +- .../kafka/producer/MessageSerializer.java | 62 ++- kafka/src/main/resources/kafka.yml | 1 + .../exception/PositionNotFoundException.java | 30 ++ .../exception/ServerIdNotMatchException.java | 30 ++ .../parse/inbound/AbstractEventParser.java | 46 +- .../canal/parse/inbound/ErosaConnection.java | 2 + .../parse/inbound/MultiStageCoprocessor.java | 4 +- .../parse/inbound/ParserExceptionHandler.java | 9 + .../mysql/AbstractMysqlEventParser.java | 13 +- .../inbound/mysql/LocalBinLogConnection.java | 56 +- .../parse/inbound/mysql/MysqlConnection.java | 20 +- .../parse/inbound/mysql/MysqlEventParser.java | 38 +- .../mysql/MysqlMultiStageCoprocessor.java | 70 +-- .../mysql/dbsync/DirectLogFetcher.java | 3 - .../inbound/mysql/dbsync/LogEventConvert.java | 14 +- .../inbound/mysql/dbsync/TableMetaCache.java | 7 +- .../mysql/rds/BinlogDownloadQueue.java | 260 ++++++++++ .../mysql/rds/RdsBinlogEventParserProxy.java | 149 ++++++ .../inbound/mysql/rds/RdsBinlogOpenApi.java | 61 ++- .../mysql/rds/RdsLocalBinlogEventParser.java | 159 ++++-- .../inbound/mysql/rds/data/BinlogFile.java | 72 +++ .../rds/data/DescribeBinlogFileResult.java | 62 +++ .../mysql/rds/data/RdsBackupPolicy.java | 69 +++ .../parse/inbound/mysql/rds/data/RdsItem.java | 19 + .../mysql/rds/request/AbstractRequest.java | 250 +++++++++ .../request/DescribeBackupPolicyRequest.java | 41 ++ .../request/DescribeBinlogFilesRequest.java | 56 ++ .../tablemeta/HistoryTableMetaCache.java | 191 +++++++ .../tablemeta/TableMetaCacheInterface.java | 20 + .../tablemeta/TableMetaCacheWithStorage.java | 105 ++++ .../mysql/tablemeta/TableMetaEntry.java | 55 ++ .../mysql/tablemeta/TableMetaStorage.java | 18 + .../tablemeta/TableMetaStorageFactory.java | 9 + .../exception/CacheConnectionNull.java | 9 + .../exception/NoHistoryException.java | 21 + .../impl/mysql/MySqlTableMetaCallback.java | 14 + .../impl/mysql/MySqlTableMetaStorage.java | 48 ++ .../mysql/MySqlTableMetaStorageFactory.java | 26 + .../parse/MysqlBinlogDumpPerformanceTest.java | 89 ++++ .../MysqlBinlogEventPerformanceTest.java | 82 +++ .../MysqlBinlogParsePerformanceTest.java | 192 +++++++ .../parse/inbound/mysql/MysqlDumpTest.java | 3 + .../mysql/RdsBinlogEventParserProxyTest.java | 153 ++++++ .../mysql/tablemeta/NoStorageTest.java | 38 ++ .../inbound/mysql/tablemeta/StorageTest.java | 77 +++ pom.xml | 19 +- prometheus/pom.xml | 67 +++ .../prometheus/CanalInstanceExports.java | 98 ++++ .../canal/prometheus/CanalServerExports.java | 21 + .../canal/prometheus/PrometheusProvider.java | 15 + .../canal/prometheus/PrometheusService.java | 110 ++++ .../impl/InboundThroughputAspect.java | 79 +++ .../impl/InstanceMetaCollector.java | 64 +++ .../prometheus/impl/MemoryStoreCollector.java | 75 +++ .../impl/OutboundThroughputAspect.java | 80 +++ ...PrometheusCanalEventDownStreamHandler.java | 73 +++ .../src/main/resources/META-INF/aop.xml | 11 + ...ibaba.otter.canal.spi.CanalMetricsProvider | 1 + .../embedded/CanalServerWithEmbedded.java | 59 ++- .../otter/canal/server/netty/NettyUtils.java | 20 +- .../server/netty/handler/SessionHandler.java | 84 ++- .../otter/canal/spi/CanalMetricsProvider.java | 24 + .../otter/canal/spi/CanalMetricsService.java | 42 ++ .../canal/spi/NopCanalMetricsService.java | 38 ++ .../CanalServerWithEmbedded_FileModeTest.java | 53 ++ .../otter/canal/server/ProtocolTest.java | 92 ++++ 131 files changed, 7250 insertions(+), 328 deletions(-) create mode 100644 dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java create mode 100644 deployer/src/main/bin/metrics_env.sh create mode 100644 docker/Dockerfile create mode 100644 docker/build.sh create mode 100755 docker/image/admin/app.sh create mode 100644 docker/image/admin/bin/clean_log create mode 100755 docker/image/admin/bin/clean_log.sh create mode 100755 docker/image/admin/health.sh create mode 100755 docker/image/alidata/bin/exec_rc_local.sh create mode 100755 docker/image/alidata/bin/lark-wait create mode 100755 docker/image/alidata/bin/main.sh create mode 100755 docker/image/alidata/init/02init-sshd.sh create mode 100755 docker/image/alidata/init/fix-hosts.py create mode 100644 docker/image/alidata/lib/proc.sh create mode 100644 docker/run.sh create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java create mode 100644 example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java create mode 100644 example/src/main/resources/client-spring.xml create mode 100644 example/src/main/resources/client.properties create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java create mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java create mode 100644 prometheus/pom.xml create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java create mode 100644 prometheus/src/main/resources/META-INF/aop.xml create mode 100644 prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider create mode 100644 server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java create mode 100644 server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java create mode 100644 server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java create mode 100644 server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java create mode 100644 server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java diff --git a/.gitignore b/.gitignore index 3f1a5d6b..90bd720f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ jtester.properties .idea/ *.iml .DS_Store +*.tar.gz +*.rpm diff --git a/README.md b/README.md index 456bd2db..86ef8755 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@

    1. canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
    2. canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
    3. +
    4. Canal已在阿里云推出商业化版本 数据传输服务DTS, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。DTS产品使用文档
    5. +DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,立即体验>>>

    背景

    @@ -73,6 +75,14 @@ See the wiki page for : wiki文
  • 阿里巴巴去Oracle数据迁移同步工具(目标支持MySQL/DRDS):http://github.com/alibaba/yugong
  • +

    相关产品

    +
      +
    1. 阿里云分布式数据库DRDS
    2. +
    3. 阿里云数据传输服务DTS
    4. +
    5. 阿里云数据库备份服务DBS
    6. +
    7. 阿里云数据管理服务DMS
    8. +
    +

    问题反馈

    1. qq交流群: 161559791
    2. diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java index 508c01dc..0f29794d 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java @@ -319,7 +319,8 @@ public class SimpleCanalConnector implements CanalConnector { } private Message receiveMessages() throws IOException { - Packet p = Packet.parseFrom(readNextPacket()); + byte[] data = readNextPacket(); + Packet p = Packet.parseFrom(data); switch (p.getType()) { case MESSAGES: { if (!p.getCompression().equals(Compression.NONE)) { diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java index b645e954..5c2678b5 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java @@ -44,13 +44,14 @@ public class GtidLogEvent extends LogEvent { gno = buffer.getLong64(); // support gtid lastCommitted and sequenceNumber - // 42 = 1+16+8+1+8+8 - if (buffer.capacity() > 42 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) { + // fix bug #776 + if (buffer.hasRemaining() && buffer.remaining() > 16 && buffer.getUint8() == LOGICAL_TIMESTAMP_TYPE_CODE) { lastCommitted = buffer.getLong64(); sequenceNumber = buffer.getLong64(); } + // ignore gtid info read // sid.copy_from((uchar *)ptr_buffer); // ptr_buffer+= ENCODED_SID_LENGTH; diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java new file mode 100644 index 00000000..f5af66e3 --- /dev/null +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java @@ -0,0 +1,52 @@ +package com.taobao.tddl.dbsync; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.Statement; +import java.util.concurrent.atomic.AtomicLong; + +import com.taobao.tddl.dbsync.binlog.DirectLogFetcher; +import com.taobao.tddl.dbsync.binlog.LogEvent; + +public class FetcherPerformanceTest { + + public static void main(String args[]) { + DirectLogFetcher fetcher = new DirectLogFetcher(); + try { + Class.forName("com.mysql.jdbc.Driver"); + Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", + "root", + "hello"); + Statement statement = connection.createStatement(); + statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'"); + statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'"); + + fetcher.open(connection, "mysql-bin.000006", 120L, 2); + + AtomicLong sum = new AtomicLong(0); + long start = System.currentTimeMillis(); + long last = 0; + long end = 0; + + while (fetcher.fetch()) { + sum.incrementAndGet(); + long current = sum.get(); + if (current - last >= 100000) { + end = System.currentTimeMillis(); + long tps = ((current - last) * 1000) / (end - start); + System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps); + last = current; + start = end; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + fetcher.close(); + } catch (IOException e) { + } + } + } +} diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java index 23a10f01..78d5b586 100644 --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/DirectLogFetcherTest.java @@ -8,15 +8,6 @@ import java.sql.Statement; import org.junit.Assert; import org.junit.Test; -import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.XidLogEvent; -import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; - public class DirectLogFetcherTest extends BaseLogFetcherTest { @Test @@ -24,56 +15,54 @@ public class DirectLogFetcherTest extends BaseLogFetcherTest { DirectLogFetcher fecther = new DirectLogFetcher(); try { Class.forName("com.mysql.jdbc.Driver"); - Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello"); + Connection connection = DriverManager.getConnection("jdbc:mysql://100.81.154.142:3306", "root", "hello"); Statement statement = connection.createStatement(); statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'"); statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'"); - fecther.open(connection, "mysql-bin.000001", 4L, 2); + fecther.open(connection, "mysql-bin.000006", 120L, 2); - LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.UNKNOWN_EVENT); LogContext context = new LogContext(); while (fecther.fetch()) { - LogEvent event = null; - event = decoder.decode(fecther, context); - - if (event == null) { - continue; - // throw new RuntimeException("parse failed"); - } - - int eventType = event.getHeader().getType(); - switch (eventType) { - case LogEvent.ROTATE_EVENT: - binlogFileName = ((RotateLogEvent) event).getFilename(); - break; - case LogEvent.WRITE_ROWS_EVENT_V1: - case LogEvent.WRITE_ROWS_EVENT: - parseRowsEvent((WriteRowsLogEvent) event); - break; - case LogEvent.UPDATE_ROWS_EVENT_V1: - case LogEvent.UPDATE_ROWS_EVENT: - parseRowsEvent((UpdateRowsLogEvent) event); - break; - case LogEvent.DELETE_ROWS_EVENT_V1: - case LogEvent.DELETE_ROWS_EVENT: - parseRowsEvent((DeleteRowsLogEvent) event); - break; - case LogEvent.QUERY_EVENT: - parseQueryEvent((QueryLogEvent) event); - break; - case LogEvent.ROWS_QUERY_LOG_EVENT: - parseRowsQueryEvent((RowsQueryLogEvent) event); - break; - case LogEvent.ANNOTATE_ROWS_EVENT: - parseAnnotateRowsEvent((AnnotateRowsEvent) event); - break; - case LogEvent.XID_EVENT: - parseXidEvent((XidLogEvent) event); - break; - default: - break; - } + decoder.decode(fecther, context); + continue; + // if (event == null) { + // continue; + // } + // + // int eventType = event.getHeader().getType(); + // switch (eventType) { + // case LogEvent.ROTATE_EVENT: + // binlogFileName = ((RotateLogEvent) event).getFilename(); + // break; + // case LogEvent.WRITE_ROWS_EVENT_V1: + // case LogEvent.WRITE_ROWS_EVENT: + // parseRowsEvent((WriteRowsLogEvent) event); + // break; + // case LogEvent.UPDATE_ROWS_EVENT_V1: + // case LogEvent.UPDATE_ROWS_EVENT: + // parseRowsEvent((UpdateRowsLogEvent) event); + // break; + // case LogEvent.DELETE_ROWS_EVENT_V1: + // case LogEvent.DELETE_ROWS_EVENT: + // parseRowsEvent((DeleteRowsLogEvent) event); + // break; + // case LogEvent.QUERY_EVENT: + // parseQueryEvent((QueryLogEvent) event); + // break; + // case LogEvent.ROWS_QUERY_LOG_EVENT: + // parseRowsQueryEvent((RowsQueryLogEvent) event); + // break; + // case LogEvent.ANNOTATE_ROWS_EVENT: + // parseAnnotateRowsEvent((AnnotateRowsEvent) event); + // break; + // case LogEvent.XID_EVENT: + // parseXidEvent((XidLogEvent) event); + // break; + // default: + // break; + // } } } catch (Exception e) { e.printStackTrace(); diff --git a/deployer/pom.xml b/deployer/pom.xml index 5fa2efc8..75f05805 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -16,6 +16,14 @@ canal.server ${project.version} + + + + + + + + diff --git a/deployer/src/main/bin/metrics_env.sh b/deployer/src/main/bin/metrics_env.sh new file mode 100644 index 00000000..1085eae2 --- /dev/null +++ b/deployer/src/main/bin/metrics_env.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Additional line arg for current prometheus solution +case "`uname`" in +Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + ;; +*) + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +base=${bin_abs_path}/.. +if [ $(ls $base/lib/aspectjweaver*.jar | wc -l) -eq 1 ]; then + WEAVER=$(ls $base/lib/aspectjweaver*.jar) + METRICS_OPTS=" -javaagent:"${WEAVER}" " +fi diff --git a/deployer/src/main/bin/startup.sh b/deployer/src/main/bin/startup.sh index 5655c713..b87652c0 100644 --- a/deployer/src/main/bin/startup.sh +++ b/deployer/src/main/bin/startup.sh @@ -94,7 +94,12 @@ then echo LOG CONFIGURATION : $logback_configurationFile echo canal conf : $canal_conf echo CLASSPATH :$CLASSPATH - $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 & +# metrics support options +# if [ -x $base/bin/metrics_env.sh ]; then +# . $base/bin/metrics_env.sh +# echo METRICS_OPTS $METRICS_OPTS +# fi + $JAVA $JAVA_OPTS $METRICS_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 & echo $! > $base/bin/canal.pid echo "cd to $current_path for continue" diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 88f6e9a3..6b0fc934 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -34,6 +34,7 @@ import com.alibaba.otter.canal.instance.core.CanalInstanceGenerator; import com.alibaba.otter.canal.instance.manager.CanalConfigClient; import com.alibaba.otter.canal.instance.manager.ManagerCanalInstanceGenerator; import com.alibaba.otter.canal.instance.spring.SpringCanalInstanceGenerator; +import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; import com.alibaba.otter.canal.server.exception.CanalServerException; import com.alibaba.otter.canal.server.netty.CanalServerWithNetty; @@ -303,7 +304,7 @@ public class CanalController { return instanceGenerator.generate(destination); } else if (config.getMode().isSpring()) { SpringCanalInstanceGenerator instanceGenerator = new SpringCanalInstanceGenerator(); - synchronized (this) { + synchronized (CanalEventParser.class) { try { // 设置当前正在加载的通道,加载spring查找文件时会用到该变量 System.setProperty(CanalConstants.CANAL_DESTINATION_PROPERTY, destination); @@ -379,7 +380,18 @@ public class CanalController { } private String getProperty(Properties properties, String key) { - return StringUtils.trim(properties.getProperty(StringUtils.trim(key))); + key = StringUtils.trim(key); + String value = System.getProperty(key); + + if (value == null) { + value = System.getenv(key); + } + + if (value == null) { + value = properties.getProperty(key); + } + + return StringUtils.trim(value); } public void start() throws Throwable { diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 23ec03a1..2bff29ae 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -3,7 +3,7 @@ ################################################# canal.id= 1 canal.ip= -canal.port= 11111 +canal.port=11111 canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 @@ -43,6 +43,7 @@ canal.instance.filter.query.dml = false canal.instance.filter.query.ddl = false canal.instance.filter.table.error = false canal.instance.filter.rows = false +canal.instance.filter.transaction.entry = false # binlog format/image check canal.instance.binlog.format = ROW,STATEMENT,MIXED @@ -58,6 +59,13 @@ canal.instance.parser.parallel = true ## disruptor ringbuffer size, must be power of 2 canal.instance.parser.parallelBufferSize = 256 +# table meta tsdb info +canal.instance.tsdb.enable=true +canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:} +canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; +canal.instance.tsdb.dbUsername=canal +canal.instance.tsdb.dbPassword=canal + ################################################# ######### destinations ############# ################################################# diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index a3703b86..e0cf0064 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -1,11 +1,12 @@ ################################################# -## mysql serverId -canal.instance.mysql.slaveId=0 +## mysql serverId , v1.0.26+ will autoGen +# canal.instance.mysql.slaveId=0 + +# enable gtid use true/false +canal.instance.gtidon=false # position info canal.instance.master.address=127.0.0.1:3306 -# enable gtid use true/false -canal.instance.gtidon=false canal.instance.master.journal.name= canal.instance.master.position= canal.instance.master.timestamp= @@ -13,23 +14,21 @@ canal.instance.master.gtid= # table meta tsdb info canal.instance.tsdb.enable=true -canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:} -canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; #canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb -canal.instance.tsdb.dbUsername=canal -canal.instance.tsdb.dbPassword=canal - +#canal.instance.tsdb.dbUsername=canal +#canal.instance.tsdb.dbPassword=canal #canal.instance.standby.address = #canal.instance.standby.journal.name = #canal.instance.standby.position = #canal.instance.standby.timestamp = #canal.instance.standby.gtid= + # username/password canal.instance.dbUsername=canal canal.instance.dbPassword=canal -canal.instance.defaultDatabaseName=test canal.instance.connectionCharset=UTF-8 + # table regex canal.instance.filter.regex=.*\\..* # table black regex diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index c4465df1..6b455c83 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -81,6 +81,7 @@ + @@ -148,7 +149,7 @@ - + @@ -156,7 +157,7 @@ - + diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 340a8158..377428a9 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -67,6 +67,7 @@ + @@ -133,7 +134,7 @@ - + @@ -141,7 +142,7 @@ - + diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index 60d069dc..44f1ab3b 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -64,6 +64,7 @@ + @@ -130,7 +131,7 @@ - + @@ -138,7 +139,7 @@ - + @@ -228,7 +229,7 @@ - + @@ -236,7 +237,7 @@ - + diff --git a/deployer/src/main/resources/spring/local-instance.xml b/deployer/src/main/resources/spring/local-instance.xml index 0b2f96aa..930ecb7b 100644 --- a/deployer/src/main/resources/spring/local-instance.xml +++ b/deployer/src/main/resources/spring/local-instance.xml @@ -67,6 +67,7 @@ + @@ -113,7 +114,7 @@ - + diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index 8fec8918..cd387b8f 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -64,6 +64,7 @@ + @@ -121,7 +122,7 @@ - + @@ -129,7 +130,7 @@ - + diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..e44796cb --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,67 @@ +FROM centos:centos6.7 + +MAINTAINER agapple (jianghang115@gmail.com) + +# install system +RUN \ + /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo 'root:Hello1234' | chpasswd && \ + groupadd -r admin && useradd -g admin admin && \ + yum install -y man && \ + yum install -y dstat && \ + yum install -y unzip && \ + yum install -y nc && \ + yum install -y openssh-server && \ + yum install -y tar && \ + yum install -y which && \ + yum install -y wget && \ + yum install -y perl && \ + yum install -y file && \ + ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \ + ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \ + sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \ + sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \ + mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \ + yum install -y cronie && \ + sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \ + yum clean all && \ + true + +# install canal +COPY image/ /tmp/docker/ +COPY canal.deployer-*.tar.gz /home/admin/ +COPY jdk-8-linux-x64.rpm /tmp/ + +RUN \ + cp -R /tmp/docker/alidata /alidata && \ + chmod +x /alidata/bin/* && \ + mkdir -p /home/admin && \ + cp -R /tmp/docker/admin/* /home/admin/ && \ + /bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \ + + touch /var/lib/rpm/* && \ + yum -y install /tmp/jdk-8-linux-x64.rpm && \ + /bin/rm -f /tmp/jdk-8-linux-x64.rpm && \ + + echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \ + echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \ + /bin/mv /home/admin/bin/clean_log /etc/cron.d && \ + + mkdir -p /home/admin/canal-server && \ + tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \ + /bin/rm -f /home/admin/canal.deployer-*.tar.gz && \ + + mkdir -p home/admin/canal-server/logs && \ + chmod +x /home/admin/*.sh && \ + chmod +x /home/admin/bin/*.sh && \ + chown admin: -R /home/admin && \ + yum clean all && \ + true + +# 2222 sys , 8080 web , 8000 debug , 11111 canal +EXPOSE 2222 11111 8000 8080 + +WORKDIR /home/admin + +ENTRYPOINT [ "/alidata/bin/main.sh" ] +CMD [ "/home/admin/app.sh" ] \ No newline at end of file diff --git a/docker/build.sh b/docker/build.sh new file mode 100644 index 00000000..58424fb3 --- /dev/null +++ b/docker/build.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +current_path=`pwd` +case "`uname`" in + Darwin) + bin_abs_path=`cd $(dirname $0); pwd` + ;; + Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + ;; + *) + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +BASE=${bin_abs_path} + +if [ ! -f $BASE/jdk*.rpm ] ; then + DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz" + wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm +fi + +cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; + +if [ "$1" == "kafka" ] ; then + cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ +else + cp $BASE/../target/canal.deployer-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ +fi diff --git a/docker/image/admin/app.sh b/docker/image/admin/app.sh new file mode 100755 index 00000000..254f7e48 --- /dev/null +++ b/docker/image/admin/app.sh @@ -0,0 +1,117 @@ +#!/bin/bash +set -e + +source /etc/profile +export JAVA_HOME=/usr/java/latest +export PATH=$JAVA_HOME/bin:$PATH +touch /tmp/start.log +chown admin: /tmp/start.log +chown -R admin: /home/admin/canal-server +host=`hostname -i` + +# waitterm +# wait TERM/INT signal. +# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html +waitterm() { + local PID + # any process to block + tail -f /dev/null & + PID="$!" + # setup trap, could do nothing, or just kill the blocker + trap "kill -TERM ${PID}" TERM INT + # wait for signal, ignore wait exit code + wait "${PID}" || true + # clear trap + trap - TERM INT + # wait blocker, ignore blocker exit code + wait "${PID}" 2>/dev/null || true +} + +# waittermpid "${PIDFILE}". +# monitor process by pidfile && wait TERM/INT signal. +# if the process disappeared, return 1, means exit with ERROR. +# if TERM or INT signal received, return 0, means OK to exit. +waittermpid() { + local PIDFILE PID do_run error + PIDFILE="${1?}" + do_run=true + error=0 + trap "do_run=false" TERM INT + while "${do_run}" ; do + PID="$(cat "${PIDFILE}")" + if ! ps -p "${PID}" >/dev/null 2>&1 ; then + do_run=false + error=1 + else + sleep 1 + fi + done + trap - TERM INT + return "${error}" +} + + +function checkStart() { + local name=$1 + local cmd=$2 + local timeout=$3 + cost=5 + while [ $timeout -gt 0 ]; do + ST=`eval $cmd` + if [ "$ST" == "0" ]; then + sleep 1 + let timeout=timeout-1 + let cost=cost+1 + elif [ "$ST" == "" ]; then + sleep 1 + let timeout=timeout-1 + let cost=cost+1 + else + break + fi + done + echo "start $name successful" +} + + +function start_canal() { + echo "start canal ..." + serverPort=`perl -le 'print $ENV{"canal.port"}'` + if [ -z "$serverPort" ] ; then + serverPort=11111 + fi + + destination=`perl -le 'print $ENV{"canal.destinations"}'` + if [[ "$destination" =~ ',' ]]; then + echo "multi destination:$destination is not support" + exit 1; + else + mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination + fi + su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1' + sleep 5 + #check start + checkStart "canal" "nc 127.0.0.1 $serverPort -w 1 -z | wc -l" 30 +} + +function stop_canal() { + echo "stop canal" + su admin -c 'cd /home/admin/canal-server/bin/ && sh stop.sh 1>>/tmp/start.log 2>&1' + echo "stop canal successful ..." +} + +echo "==> START ..." + +start_canal + +echo "==> START SUCCESSFUL ..." + +tail -f /dev/null & +# wait TERM signal +waitterm + +echo "==> STOP" + +stop_canal + +echo "==> STOP SUCCESSFUL ..." diff --git a/docker/image/admin/bin/clean_log b/docker/image/admin/bin/clean_log new file mode 100644 index 00000000..df183614 --- /dev/null +++ b/docker/image/admin/bin/clean_log @@ -0,0 +1,2 @@ +# cron clean log once per minute +*/2 * * * * admin /home/admin/bin/clean_log.sh >>/tmp/clean_log.log 2>&1 diff --git a/docker/image/admin/bin/clean_log.sh b/docker/image/admin/bin/clean_log.sh new file mode 100755 index 00000000..9d0a0bef --- /dev/null +++ b/docker/image/admin/bin/clean_log.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Global Settings +PATH="$HOME/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/root/bin" +export PATH + +CUTOFF="85" +#获取磁盘使用率最高的分区 +USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) +before=$USAGE + +baseClean(){ + #删除tmp目录15天前的文件。 + #更新文档时间戳 + if [ -d /tmp/hsperfdata_admin ] + then + touch /tmp/hsperfdata_admin + touch /tmp/hsperfdata_admin/* + fi + + find /tmp/ -type f -mtime +15 | xargs -t rm -rf >/dev/null 2>&1 + + + now=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + echo "before:$before; now:$now" +} + +CANAL_DIR="/home/admin/canal-server/logs" +if [[ -d $CANAL_DIR ]]; then + USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + if [[ $USAGE -ge 90 ]]; then + find $CANAL_DIR -type f -mtime +7 | xargs rm -rf {} + fi + USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + if [[ $USAGE -ge 80 ]]; then + find $CANAL_DIR -type f -mtime +3 | xargs rm -rf {} + fi + USAGE=$(df -h|awk 'NR>1 {gsub(/%$/,"",$5);print $5 }'|sort -nr|head -1) + if [[ $USAGE -ge 80 ]]; then + find $CANAL_DIR -type d -empty -mtime +3 | grep -v canal | xargs rm -rf {} + find $CANAL_DIR -type f -iname '*.tmp' | xargs rm -rf {} + fi + baseClean + exit 0 +fi \ No newline at end of file diff --git a/docker/image/admin/health.sh b/docker/image/admin/health.sh new file mode 100755 index 00000000..17a89740 --- /dev/null +++ b/docker/image/admin/health.sh @@ -0,0 +1,13 @@ +#!/bin/sh +CHECK_URL="http://127.0.0.1:8080/metrics" +CHECK_POINT="success" +CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT` +if [ $CHECK_COUNT -eq 0 ]; then + echo "[FAILED]" + status=0 + error=1 +else + echo "[ OK ]" + status=1 + error=0 +fi \ No newline at end of file diff --git a/docker/image/alidata/bin/exec_rc_local.sh b/docker/image/alidata/bin/exec_rc_local.sh new file mode 100755 index 00000000..7cc8aa3f --- /dev/null +++ b/docker/image/alidata/bin/exec_rc_local.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ "${SKIP_EXEC_RC_LOCAL}" = "YES" ] ; then + echo "skip /etc/rc.local: SKIP_EXEC_RC_LOCAL=${SKIP_EXEC_RC_LOCAL}" + exit +fi + +if [ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] ; then + echo "skip /etc/rc.local: DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}" + exit +fi \ No newline at end of file diff --git a/docker/image/alidata/bin/lark-wait b/docker/image/alidata/bin/lark-wait new file mode 100755 index 00000000..db7be708 --- /dev/null +++ b/docker/image/alidata/bin/lark-wait @@ -0,0 +1,6 @@ +#!/bin/bash +set -e + +chown admin: -R /home/admin/ +source /alidata/lib/proc.sh +waitterm diff --git a/docker/image/alidata/bin/main.sh b/docker/image/alidata/bin/main.sh new file mode 100755 index 00000000..a57551a7 --- /dev/null +++ b/docker/image/alidata/bin/main.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +[ -n "${DOCKER_DEPLOY_TYPE}" ] || DOCKER_DEPLOY_TYPE="VM" +echo "DOCKER_DEPLOY_TYPE=${DOCKER_DEPLOY_TYPE}" + +# run init scripts +for e in $(ls /alidata/init/*) ; do + [ -x "${e}" ] || continue + echo "==> INIT $e" + $e + echo "==> EXIT CODE: $?" +done + +echo "==> INIT DEFAULT" +service sshd start +service crond start + +#echo "check hostname -i: `hostname -i`" +#hti_num=`hostname -i|awk '{print NF}'` +#if [ $hti_num -gt 1 ];then +# echo "hostname -i result error:`hostname -i`" +# exit 120 +#fi + +echo "==> INIT DONE" +echo "==> RUN ${*}" +exec "${@}" \ No newline at end of file diff --git a/docker/image/alidata/init/02init-sshd.sh b/docker/image/alidata/init/02init-sshd.sh new file mode 100755 index 00000000..118ee553 --- /dev/null +++ b/docker/image/alidata/init/02init-sshd.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# set port +if [ -z "${SSHD_PORT}" ] ; then + SSHD_PORT=22 + [ "${DOCKER_DEPLOY_TYPE}" = "HOST" ] && SSHD_PORT=2222 +fi + +sed -r -i '/^OPTIONS=/ d' /etc/sysconfig/sshd +echo 'OPTIONS="-p '"${SSHD_PORT}"'"' >> /etc/sysconfig/sshd + +# set admin ssh pulic key +if [ "${USE_ADMIN_PASSAGE}" = "YES" ] ; then + echo "set admin passage" + mkdir -p /home/admin/.ssh + chown admin:admin /home/admin/.ssh + chown admin:admin /home/admin/.ssh/authorized_keys + chmod 644 /home/admin/.ssh/authorized_keys +fi diff --git a/docker/image/alidata/init/fix-hosts.py b/docker/image/alidata/init/fix-hosts.py new file mode 100755 index 00000000..4ee8abb5 --- /dev/null +++ b/docker/image/alidata/init/fix-hosts.py @@ -0,0 +1,66 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +#****************************************************************# +# Create Date: 2017-01-06 17:58 +#***************************************************************# + +import socket +import shutil +from time import gmtime, strftime + +# get host_name +host_name = socket.gethostname() +tmp_file = "/tmp/.lark-fix-host.hosts" +host_file = "/etc/hosts" +bak_file_name = "/tmp/hosts-fix-bak.%s" % ( strftime("%Y-%m-%d_%H-%M-%S", gmtime()) ) + +# load /etc/hosts file context +FH = open(host_file,"r") +file_lines = [ i.rstrip() for i in FH.readlines()] +FH.close() +file_lines_reverse = file_lines[::-1] +new_lines = [] +bad_lines = [] +last_match_line = "" + +for line in file_lines_reverse: + if line.find(host_name) < 0: # 不匹配的行直接跳过 + new_lines.append(line + "\n") + continue + + cols = line.split() + new_cols = [] + if cols[0].startswith("#"): # 跳过已经注释掉的行 + new_lines.append(line + "\n") + continue + for col in cols: + if not col == host_name: # 跳过不匹配的列 + new_cols.append(col) + continue + + if cols[0] == "127.0.0.1": # 如果第一列是 127.0.0.1 就跳过匹配的列, 防止 hostname -i 返回 127.0.0.1 + continue + + # 如果已经发现过匹配的列, 就丢掉重复的列 + if not len(last_match_line) == 0: + continue + + new_cols.append(col) + last_match_line = line + + # 跳过 xx.xx.xx.xx hostname 这样的重复列 + if len(new_cols) == 1: + continue + + new_l = "%s\n" % " ".join(new_cols) + new_lines.append(new_l) + +# save tmp hosts + +FH2=file(tmp_file,"w+") +FH2.writelines( new_lines[::-1]) +FH2.close() + +# mv to /etc/hosts +shutil.copy(host_file, bak_file_name) +shutil.move(tmp_file, host_file) diff --git a/docker/image/alidata/lib/proc.sh b/docker/image/alidata/lib/proc.sh new file mode 100644 index 00000000..40ddff93 --- /dev/null +++ b/docker/image/alidata/lib/proc.sh @@ -0,0 +1,40 @@ +# waitterm +# wait TERM/INT signal. +# see: http://veithen.github.io/2014/11/16/sigterm-propagation.html +waitterm() { + local PID + # any process to block + tail -f /dev/null & + PID="$!" + # setup trap, could do nothing, or just kill the blocker + trap "kill -TERM ${PID}" TERM INT + # wait for signal, ignore wait exit code + wait "${PID}" || true + # clear trap + trap - TERM INT + # wait blocker, ignore blocker exit code + wait "${PID}" 2>/dev/null || true +} + +# waittermpid "${PIDFILE}". +# monitor process by pidfile && wait TERM/INT signal. +# if the process disappeared, return 1, means exit with ERROR. +# if TERM or INT signal received, return 0, means OK to exit. +waittermpid() { + local PIDFILE PID do_run error + PIDFILE="${1?}" + do_run=true + error=0 + trap "do_run=false" TERM INT + while "${do_run}" ; do + PID="$(cat "${PIDFILE}")" + if ! ps -p "${PID}" >/dev/null 2>&1 ; then + do_run=false + error=1 + else + sleep 1 + fi + done + trap - TERM INT + return "${error}" +} diff --git a/docker/run.sh b/docker/run.sh new file mode 100644 index 00000000..7c56e2ba --- /dev/null +++ b/docker/run.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +function usage() { + echo "Usage:" + echo " run.sh [CONFIG]" + echo "example:" + echo " run.sh -e canal.instance.master.address=127.0.0.1:3306 \\" + echo " -e canal.instance.dbUsername=canal \\" + echo " -e canal.instance.dbPassword=canal \\" + echo " -e canal.instance.connectionCharset=UTF-8 \\" + echo " -e canal.instance.tsdb.enable=true \\" + echo " -e canal.instance.gtidon=false \\" + echo " -e canal.instance.filter.regex=.*\\..* " + exit +} + +function check_port() { + local port=$1 + local TL=$(which telnet) + if [ -f $TL ]; then + data=`echo quit | telnet 127.0.0.1 $port| grep -ic connected` + echo $data + return + fi + + local NC=$(which nc) + if [ -f $NC ]; then + data=`nc -z -w 1 127.0.0.1 $port | grep -ic succeeded` + echo $data + return + fi + echo "0" + return +} + +function getMyIp() { + case "`uname`" in + Darwin) + myip=`echo "show State:/Network/Global/IPv4" | scutil | grep PrimaryInterface | awk '{print $3}' | xargs ifconfig | grep inet | grep -v inet6 | awk '{print $2}'` + ;; + *) + myip=`ip route get 1 | awk '{print $NF;exit}'` + ;; + esac + echo $myip +} + +NET_MODE="" +case "`uname`" in + Darwin) + bin_abs_path=`cd $(dirname $0); pwd` + ;; + Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + NET_MODE="--net=host" + ;; + *) + NET_MODE="--net=host" + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +BASE=${bin_abs_path} +if [ $# -eq 0 ]; then + usage +elif [ "$1" == "-h" ] ; then + usage +elif [ "$1" == "help" ] ; then + usage +fi + +DATA="$BASE/data" +mkdir -p $DATA +CONFIG=${@:1} +#VOLUMNS="-v $DATA:/home/admin/canal-server/logs" +PORTLIST="8000 8080 2222 11111" +PORTS="" +for PORT in $PORTLIST ; do + #exist=`check_port $PORT` + exist="0" + if [ "$exist" == "0" ]; then + PORTS="$PORTS -p $PORT:$PORT" + else + echo "port $PORT is used , pls check" + exit 1 + fi +done + +MEMORY="-m 4096m" +LOCALHOST=`getMyIp` +cmd="docker run -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server" +echo $cmd +eval $cmd \ No newline at end of file diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java index 29b96852..0f01cbd5 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/MysqlConnector.java @@ -32,7 +32,7 @@ public class MysqlConnector { private String password; private byte charsetNumber = 33; - private String defaultSchema = "retl"; + private String defaultSchema = "test"; private int soTimeout = 30 * 1000; private int connTimeout = 5 * 1000; private int receiveBufferSize = 16 * 1024; diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java index 70624549..f0fba11a 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/packets/client/BinlogDumpCommandPacket.java @@ -54,7 +54,7 @@ public class BinlogDumpCommandPacket extends CommandPacket { // 1. write 4 bytes bin-log position to start at ByteHelper.writeUnsignedIntLittleEndian(binlogPosition, out); // 2. write 2 bytes bin-log flags - int binlog_flags = BINLOG_DUMP_NON_BLOCK; + int binlog_flags = 0; binlog_flags |= BINLOG_SEND_ANNOTATE_ROWS_EVENT; out.write(binlog_flags); out.write(0x00); diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java index 2ed318e8..f746189e 100644 --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.parse.driver.mysql.socket; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -24,7 +25,7 @@ public class BioSocketChannel implements SocketChannel { BioSocketChannel(Socket socket) throws IOException{ this.socket = socket; - this.input = socket.getInputStream(); + this.input = new BufferedInputStream(socket.getInputStream(), 16384); this.output = socket.getOutputStream(); } @@ -164,5 +165,4 @@ public class BioSocketChannel implements SocketChannel { this.socket = null; } - } diff --git a/example/pom.xml b/example/pom.xml index 1f60bc01..2897e821 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -21,6 +21,82 @@ canal.protocol ${project.version} + + com.alibaba + druid + + + mysql + mysql-connector-java + + + org.apache.ddlutils + ddlutils + 1.0 + + + commons-beanutils + commons-beanutils-core + + + commons-lang + commons-lang + + + commons-dbcp + commons-dbcp + + + commons-pool + commons-pool + + + commons-logging + commons-logging-api + + + dom4j + dom4j + + + stax + stax-api + + + commons-collections + commons-collections + + + commons-digester + commons-digester + + + commons-betwixt + commons-betwixt + + + + + org.apache.commons + commons-pool2 + 2.5.0 + + + commons-beanutils + commons-beanutils + 1.8.2 + + + org.apache.commons + commons-lang3 + 3.7 + + + commons-collections + commons-collections + 3.2 + + junit diff --git a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java index 1b84463b..d1ddd88e 100644 --- a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java +++ b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.example; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; diff --git a/example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java b/example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java new file mode 100644 index 00000000..c58eb869 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/SimpleCanalClientPermanceTest.java @@ -0,0 +1,68 @@ +package com.alibaba.otter.canal.example; +import java.net.InetSocketAddress; +import java.util.concurrent.ArrayBlockingQueue; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; +import com.alibaba.otter.canal.protocol.Message; + +public class SimpleCanalClientPermanceTest { + + public static void main(String args[]) { + String destination = "example"; + String ip = "127.0.0.1"; + int batchSize = 1024; + int count = 0; + int sum = 0; + int perSum = 0; + long start = System.currentTimeMillis(); + long end = 0; + final ArrayBlockingQueue queue = new ArrayBlockingQueue(100); + try { + final CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(ip, 11111), + destination, + "", + ""); + + Thread ackThread = new Thread(new Runnable() { + + @Override + public void run() { + while (true) { + try { + long batchId = queue.take(); + connector.ack(batchId); + } catch (InterruptedException e) { + } + } + } + }); + ackThread.start(); + + ((SimpleCanalConnector) connector).setLazyParseEntry(true); + connector.connect(); + connector.subscribe(); + while (true) { + Message message = connector.getWithoutAck(batchSize); + long batchId = message.getId(); + int size = message.getRawEntries().size(); + sum += size; + perSum += size; + count++; + queue.add(batchId); + if (count % 10 == 0) { + end = System.currentTimeMillis(); + long tps = (perSum * 1000) / (end - start); + System.out.println(" total : " + sum + " , current : " + perSum + " , cost : " + (end - start) + + " , tps : " + tps); + start = end; + perSum = 0; + } + } + } catch (Throwable e) { + e.printStackTrace(); + } + } + +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java new file mode 100644 index 00000000..ec998e55 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/AbstractDbClient.java @@ -0,0 +1,144 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.Message; +import org.slf4j.MDC; + +import java.util.Date; +import java.util.List; + +public abstract class AbstractDbClient extends CanalConnectorClient { + + + public abstract void insert(CanalEntry.Header header, List afterColumns); + + public abstract void update(CanalEntry.Header header, List afterColumns); + + public abstract void delete(CanalEntry.Header header, List beforeColumns); + + + @Override + public synchronized void start() { + if (running) { + return; + } + super.start(); + } + + @Override + public synchronized void stop() { + if (!running) { + return; + } + super.stop(); + MDC.remove("destination"); + } + + @Override + protected void processMessage(Message message) { + long batchId = message.getId(); + //遍历每条消息 + for (CanalEntry.Entry entry : message.getEntries()) { + session(entry);//no exception + } + //ack all the time。 + connector.ack(batchId); + } + + private void session(CanalEntry.Entry entry) { + CanalEntry.EntryType entryType = entry.getEntryType(); + int times = 0; + boolean success = false; + while (!success) { + if (times > 0) { + /** + * 1:retry,重试,重试默认为3次,由retryTimes参数决定,如果重试次数达到阈值,则跳过,并且记录日志。 + * 2:ignore,直接忽略,不重试,记录日志。 + */ + if (exceptionStrategy == ExceptionStrategy.RETRY.code) { + if (times >= retryTimes) { + break; + } + } else { + break; + } + } + try { + switch (entryType) { + case TRANSACTIONBEGIN: + transactionBegin(entry); + break; + case TRANSACTIONEND: + transactionEnd(entry); + break; + case ROWDATA: + rowData(entry); + break; + default: + break; + } + success = true; + } catch (Exception e) { + times++; + logger.error("parse event has an error ,times: + " + times + ", data:" + entry.toString(), e); + } + + } + } + + private void rowData(CanalEntry.Entry entry) throws Exception { + CanalEntry.RowChange rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + CanalEntry.EventType eventType = rowChange.getEventType(); + CanalEntry.Header header = entry.getHeader(); + long executeTime = header.getExecuteTime(); + long delayTime = new Date().getTime() - executeTime; + String sql = rowChange.getSql(); + + try { + if (!isDML(eventType) || rowChange.getIsDdl()) { + processDDL(header, eventType, sql); + return; + } + //处理DML数据 + processDML(header, eventType, rowChange, sql); + } catch (Exception e) { + logger.error("process event error ,", e); + logger.error(rowFormat, + new Object[]{header.getLogfileName(), String.valueOf(header.getLogfileOffset()), + header.getSchemaName(), header.getTableName(), eventType, + String.valueOf(executeTime), String.valueOf(delayTime)}); + throw e;//重新抛出 + } + } + + /** + * 处理 dml 数据 + * + * @param header + * @param eventType + * @param rowChange + * @param sql + */ + protected void processDML(CanalEntry.Header header, CanalEntry.EventType eventType, CanalEntry.RowChange rowChange, String sql) { + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + switch (eventType) { + case DELETE: + delete(header, rowData.getBeforeColumnsList()); + break; + case INSERT: + insert(header, rowData.getAfterColumnsList()); + break; + case UPDATE: + update(header, rowData.getAfterColumnsList()); + break; + default: + whenOthers(header, sql); + } + } + } + +} + + + + diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java new file mode 100644 index 00000000..844f60db --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java @@ -0,0 +1,488 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.Message; +import org.apache.commons.lang.SystemUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.MDC; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.CollectionUtils; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public abstract class CanalConnectorClient extends AbstractCanalLifeCycle implements InitializingBean { + + protected static final Logger logger = LoggerFactory.getLogger(CanalConnectorClient.class); + protected static final String SEP = SystemUtils.LINE_SEPARATOR; + protected static String contextFormat; + protected static String rowFormat; + protected static String transactionFormat; + protected static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; + + static { + StringBuilder sb = new StringBuilder(); + sb.append(SEP) + .append("-------------Batch-------------") + .append(SEP) + .append("* Batch Id: [{}] ,count : [{}] , Mem size : [{}] , Time : {}") + .append(SEP) + .append("* Start : [{}] ") + .append(SEP) + .append("* End : [{}] ") + .append(SEP) + .append("-------------------------------") + .append(SEP); + contextFormat = sb.toString(); + + sb = new StringBuilder(); + sb.append(SEP) + .append("+++++++++++++Row+++++++++++++>>>") + .append("binlog[{}:{}] , name[{},{}] , eventType : {} , executeTime : {} , delay : {}ms") + .append(SEP); + rowFormat = sb.toString(); + + sb = new StringBuilder(); + sb.append(SEP) + .append("===========Transaction {} : {}=======>>>") + .append("binlog[{}:{}] , executeTime : {} , delay : {}ms") + .append(SEP); + transactionFormat = sb.toString(); + } + + private String zkServers;//cluster + private String address;//single,ip:port + private String destination; + private String username; + private String password; + private int batchSize = 5 * 1024; + private String filter = "";//同canal filter,用于过滤database或者table的相关数据。 + protected boolean debug = false;//开启debug,会把每条消息的详情打印 + + //1:retry,重试,重试默认为3次,由retryTimes参数决定,如果重试次数达到阈值,则跳过,并且记录日志。 + //2:ignore,直接忽略,不重试,记录日志。 + protected int exceptionStrategy = 1; + protected int retryTimes = 3; + protected int waitingTime = 100;//当binlog没有数据时,主线程等待的时间,单位ms,大于0 + + + protected CanalConnector connector; + protected Thread thread; + + protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { + + public void uncaughtException(Thread t, Throwable e) { + logger.error("process message has an error", e); + } + }; + + @Override + public void afterPropertiesSet() { + if (waitingTime <= 0) { + throw new IllegalArgumentException("waitingTime must be greater than 0"); + } + if (ExceptionStrategy.codeOf(exceptionStrategy) == null) { + throw new IllegalArgumentException("exceptionStrategy is not valid,1 or 2"); + } + start(); + } + + @Override + public void start() { + if (running) { + return; + } + super.start(); + initConnector(); + + thread = new Thread(new Runnable() { + + public void run() { + process(); + } + }); + + thread.setUncaughtExceptionHandler(handler); + thread.start(); + } + + @Override + public void stop() { + if (!running) { + return; + } + super.stop(); + quietlyStop(thread); + } + + protected void quietlyStop(Thread task) { + if (task != null) { + task.interrupt(); + try { + task.join(); + } catch (InterruptedException e) { + // ignore + } + } + } + + public void process() { + int times = 0; + while (running) { + try { + sleepWhenFailed(times); + //after block, should check the status of thread. + if (!running) { + break; + } + MDC.put("destination", destination); + connector.connect(); + connector.subscribe(filter); + connector.rollback(); + times = 0;//reset; + + while (running) { + // 获取指定数量的数据,不确认 + Message message = connector.getWithoutAck(batchSize); + + long batchId = message.getId(); + int size = message.getEntries().size(); + + if (batchId == -1 || size == 0) { + try { + Thread.sleep(waitingTime); + } catch (InterruptedException e) { + // + } + continue; + } + //logger + printBatch(message, batchId); + + processMessage(message); + + } + } catch (Exception e) { + logger.error("process error!", e); + if (times > 20) { + times = 0; + } + times++; + } finally { + connector.disconnect(); + MDC.remove("destination"); + } + } + } + + protected abstract void processMessage(Message message); + + + private void initConnector() { + if (zkServers != null && zkServers.length() > 0) { + connector = CanalConnectors.newClusterConnector(zkServers, destination, username, password); + } else if (address != null) { + String[] segments = address.split(":"); + SocketAddress socketAddress = new InetSocketAddress(segments[0], Integer.valueOf(segments[1])); + connector = CanalConnectors.newSingleConnector(socketAddress, destination, username, password); + } else { + throw new IllegalArgumentException("zkServers or address cant be null at same time,you should specify one of them!"); + } + + } + + /** + * 用于控制当连接异常时,重试的策略,我们不应该每次都是立即重试,否则将可能导致大量的错误,在空转时导致CPU过高的问题 + * sleep策略基于简单的累加 + * + * @param times + */ + private void sleepWhenFailed(int times) { + if (times <= 0) { + return; + } + try { + int sleepTime = 1000 + times * 100;//最大sleep 3s。 + Thread.sleep(sleepTime); + } catch (Exception ex) { + // + } + } + + /** + * 打印当前batch的摘要信息 + * + * @param message + * @param batchId + */ + protected void printBatch(Message message, long batchId) { + if (!debug) { + return; + } + List entries = message.getEntries(); + if (CollectionUtils.isEmpty(entries)) { + return; + } + + long memSize = 0; + for (CanalEntry.Entry entry : entries) { + memSize += entry.getHeader().getEventLength(); + } + int size = entries.size(); + String startPosition = buildPosition(entries.get(0)); + String endPosition = buildPosition(message.getEntries().get(size - 1)); + + SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); + logger.info(contextFormat, new Object[]{batchId, size, memSize, format.format(new Date()), startPosition, endPosition}); + } + + protected String buildPosition(CanalEntry.Entry entry) { + CanalEntry.Header header = entry.getHeader(); + long time = header.getExecuteTime(); + Date date = new Date(time); + SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); + StringBuilder sb = new StringBuilder(); + sb.append(header.getLogfileName()) + .append(":") + .append(header.getLogfileOffset()) + .append(":") + .append(header.getExecuteTime()) + .append("(") + .append(format.format(date)) + .append(")"); + return sb.toString(); + } + + /** + * default,only logging information + * + * @param entry + */ + protected void transactionBegin(CanalEntry.Entry entry) { + if (!debug) { + return; + } + try { + CanalEntry.TransactionBegin begin = CanalEntry.TransactionBegin.parseFrom(entry.getStoreValue()); + // 打印事务头信息,执行的线程id,事务耗时 + CanalEntry.Header header = entry.getHeader(); + long executeTime = header.getExecuteTime(); + long delayTime = new Date().getTime() - executeTime; + logger.info(transactionFormat, + new Object[]{"begin", begin.getTransactionId(), header.getLogfileName(), + String.valueOf(header.getLogfileOffset()), + String.valueOf(header.getExecuteTime()), String.valueOf(delayTime)}); + } catch (Exception e) { + logger.error("parse event has an error , data:" + entry.toString(), e); + } + } + + protected void transactionEnd(CanalEntry.Entry entry) { + if (!debug) { + return; + } + try { + CanalEntry.TransactionEnd end = CanalEntry.TransactionEnd.parseFrom(entry.getStoreValue()); + // 打印事务提交信息,事务id + CanalEntry.Header header = entry.getHeader(); + long executeTime = header.getExecuteTime(); + long delayTime = new Date().getTime() - executeTime; + + logger.info(transactionFormat, + new Object[]{"end", end.getTransactionId(), header.getLogfileName(), + String.valueOf(header.getLogfileOffset()), + String.valueOf(header.getExecuteTime()), String.valueOf(delayTime)}); + } catch (Exception e) { + logger.error("parse event has an error , data:" + entry.toString(), e); + } + } + + /** + * 判断事件类型为DML 数据 + * + * @param eventType + * @return + */ + protected boolean isDML(CanalEntry.EventType eventType) { + switch (eventType) { + case INSERT: + case UPDATE: + case DELETE: + return true; + default: + return false; + } + } + + /** + * 处理 DDL数据 + * + * @param header + * @param eventType + * @param sql + */ + + protected void processDDL(CanalEntry.Header header, CanalEntry.EventType eventType, String sql) { + if (!debug) { + return; + } + String table = header.getSchemaName() + "." + header.getTableName(); + //对于DDL,直接执行,因为没有行变更数据 + switch (eventType) { + case CREATE: + logger.warn("parse create table event, table: {}, sql: {}", table, sql); + return; + case ALTER: + logger.warn("parse alter table event, table: {}, sql: {}", table, sql); + return; + case TRUNCATE: + logger.warn("parse truncate table event, table: {}, sql: {}", table, sql); + return; + case ERASE: + case QUERY: + logger.warn("parse event : {}, sql: {} . ignored!", eventType.name(), sql); + return; + case RENAME: + logger.warn("parse rename table event, table: {}, sql: {}", table, sql); + return; + case CINDEX: + logger.warn("parse create index event, table: {}, sql: {}", table, sql); + return; + case DINDEX: + logger.warn("parse delete index event, table: {}, sql: {}", table, sql); + return; + default: + logger.warn("parse unknown event: {}, table: {}, sql: {}", new String[]{eventType.name(), table, sql}); + break; + } + } + + /** + * 强烈建议捕获异常,非上述已列出的其他操作,非核心 + * 除了“insert”、“update”、“delete”操作之外的,其他类型的操作. + * 默认实现为“无操作” + * + * @param header 可以从header中获得schema、table的名称 + * @param sql + */ + public void whenOthers(CanalEntry.Header header, String sql) { + String schema = header.getSchemaName(); + String table = header.getTableName(); + logger.error("ignore event,schema: {},table: {},SQL: {}", new String[]{schema, table, sql}); + } + + public enum ExceptionStrategy { + RETRY(1), IGNORE(2); + public int code; + + ExceptionStrategy(int code) { + this.code = code; + } + + public static ExceptionStrategy codeOf(Integer code) { + if (code != null) { + for (ExceptionStrategy e : ExceptionStrategy.values()) { + if (e.code == code) { + return e; + } + } + } + return null; + } + } + + public String getZkServers() { + return zkServers; + } + + public void setZkServers(String zkServers) { + this.zkServers = zkServers; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public int getBatchSize() { + return batchSize; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } + + public String getFilter() { + return filter; + } + + public void setFilter(String filter) { + this.filter = filter; + } + + public boolean isDebug() { + return debug; + } + + public void setDebug(boolean debug) { + this.debug = debug; + } + + public int getExceptionStrategy() { + return exceptionStrategy; + } + + public void setExceptionStrategy(int exceptionStrategy) { + this.exceptionStrategy = exceptionStrategy; + } + + public int getRetryTimes() { + return retryTimes; + } + + public void setRetryTimes(int retryTimes) { + this.retryTimes = retryTimes; + } + + public int getWaitingTime() { + return waitingTime; + } + + public void setWaitingTime(int waitingTime) { + this.waitingTime = waitingTime; + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java b/example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java new file mode 100644 index 00000000..b9936106 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/MysqlLoadLauncher.java @@ -0,0 +1,35 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.example.db.mysql.MysqlClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class MysqlLoadLauncher { + private static final Logger logger = LoggerFactory.getLogger(MysqlLoadLauncher.class); + + public static void main(String[] args) { + try { + logger.info("## start the canal mysql client."); + final MysqlClient client = ServiceLocator.getMysqlClient(); + logger.info("## the canal consumer is running now ......"); + client.start(); + Runtime.getRuntime().addShutdownHook(new Thread() { + + public void run() { + try { + logger.info("## stop the canal consumer"); + client.stop(); + } catch (Throwable e) { + logger.warn("##something goes wrong when stopping canal consumer:\n{}", e); + } finally { + logger.info("## canal consumer is down."); + } + } + + }); + } catch (Throwable e) { + logger.error("## Something goes wrong when starting up the canal consumer:\n{}", e); + System.exit(0); + } + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java b/example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java new file mode 100644 index 00000000..65b4827c --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/PropertyPlaceholderConfigurer.java @@ -0,0 +1,169 @@ +package com.alibaba.otter.canal.example.db; + +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; +import org.springframework.util.Assert; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * 扩展Spring的 + * {@linkplain org.springframework.beans.factory.config.PropertyPlaceholderConfigurer} + * ,增加默认值的功能。 例如:${placeholder:defaultValue},假如placeholder的值不存在,则默认取得 + * defaultValue。 + * + * @author jianghang 2013-1-24 下午03:37:56 + * @version 1.0.0 + */ +public class PropertyPlaceholderConfigurer extends org.springframework.beans.factory.config.PropertyPlaceholderConfigurer implements ResourceLoaderAware, InitializingBean { + + private static final String PLACEHOLDER_PREFIX = "${"; + private static final String PLACEHOLDER_SUFFIX = "}"; + private ResourceLoader loader; + private String[] locationNames; + + public PropertyPlaceholderConfigurer(){ + setIgnoreUnresolvablePlaceholders(true); + } + + public void setResourceLoader(ResourceLoader loader) { + this.loader = loader; + } + + public void setLocationNames(String[] locations) { + this.locationNames = locations; + } + + public void afterPropertiesSet() throws Exception { + Assert.notNull(loader, "no resourceLoader"); + + if (locationNames != null) { + for (int i = 0; i < locationNames.length; i++) { + locationNames[i] = resolveSystemPropertyPlaceholders(locationNames[i]); + } + } + + if (locationNames != null) { + List resources = new ArrayList(locationNames.length); + + for (String location : locationNames) { + location = trimToNull(location); + + if (location != null) { + resources.add(loader.getResource(location)); + } + } + + super.setLocations(resources.toArray(new Resource[resources.size()])); + } + } + + private String resolveSystemPropertyPlaceholders(String text) { + StringBuilder buf = new StringBuilder(text); + + for (int startIndex = buf.indexOf(PLACEHOLDER_PREFIX); startIndex >= 0;) { + int endIndex = buf.indexOf(PLACEHOLDER_SUFFIX, startIndex + PLACEHOLDER_PREFIX.length()); + + if (endIndex != -1) { + String placeholder = buf.substring(startIndex + PLACEHOLDER_PREFIX.length(), endIndex); + int nextIndex = endIndex + PLACEHOLDER_SUFFIX.length(); + + try { + String value = resolveSystemPropertyPlaceholder(placeholder); + + if (value != null) { + buf.replace(startIndex, endIndex + PLACEHOLDER_SUFFIX.length(), value); + nextIndex = startIndex + value.length(); + } else { + System.err.println("Could not resolve placeholder '" + + placeholder + + "' in [" + + text + + "] as system property: neither system property nor environment variable found"); + } + } catch (Throwable ex) { + System.err.println("Could not resolve placeholder '" + placeholder + "' in [" + text + + "] as system property: " + ex); + } + + startIndex = buf.indexOf(PLACEHOLDER_PREFIX, nextIndex); + } else { + startIndex = -1; + } + } + + return buf.toString(); + } + + private String resolveSystemPropertyPlaceholder(String placeholder) { + DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder); + String value = System.getProperty(dp.placeholder); + + if (value == null) { + value = System.getenv(dp.placeholder); + } + + if (value == null) { + value = dp.defaultValue; + } + + return value; + } + + @Override + protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) { + DefaultablePlaceholder dp = new DefaultablePlaceholder(placeholder); + String value = super.resolvePlaceholder(dp.placeholder, props, systemPropertiesMode); + + if (value == null) { + value = dp.defaultValue; + } + + return trimToEmpty(value); + } + + private static class DefaultablePlaceholder { + + private final String defaultValue; + private final String placeholder; + + public DefaultablePlaceholder(String placeholder){ + int commaIndex = placeholder.indexOf(":"); + String defaultValue = null; + + if (commaIndex >= 0) { + defaultValue = trimToEmpty(placeholder.substring(commaIndex + 1)); + placeholder = trimToEmpty(placeholder.substring(0, commaIndex)); + } + + this.placeholder = placeholder; + this.defaultValue = defaultValue; + } + } + + private String trimToNull(String str) { + if (str == null) { + return null; + } + + String result = str.trim(); + + if (result == null || result.length() == 0) { + return null; + } + + return result; + } + + public static String trimToEmpty(String str) { + if (str == null) { + return ""; + } + + return str.trim(); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java b/example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java new file mode 100644 index 00000000..39ad4c62 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/ServiceLocator.java @@ -0,0 +1,44 @@ +package com.alibaba.otter.canal.example.db; + +import com.alibaba.otter.canal.example.db.mysql.MysqlClient; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.util.Assert; + +public class ServiceLocator implements DisposableBean { + + private static ApplicationContext applicationContext = null; + + static { + try { + applicationContext = new ClassPathXmlApplicationContext("classpath:client-spring.xml"); + } catch (RuntimeException e) { + throw e; + } + } + + private static T getBean(String name) { + assertContextInjected(); + return (T) applicationContext.getBean(name); + } + + + private static void clearHolder() { + ServiceLocator.applicationContext = null; + } + + @Override + public void destroy() throws Exception { + ServiceLocator.clearHolder(); + } + + private static void assertContextInjected() { + Assert.state(applicationContext != null, "ApplicationContext not set"); + } + + + public static MysqlClient getMysqlClient() { + return getBean("mysqlClient"); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java new file mode 100644 index 00000000..ec99b316 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractDbDialect.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect; + +import com.alibaba.otter.canal.example.db.utils.DdlUtils; +import com.google.common.base.Function; +import com.google.common.collect.MigrateMap; +import org.apache.commons.lang.exception.NestableRuntimeException; +import org.apache.ddlutils.model.Table; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.datasource.DataSourceTransactionManager; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.support.TransactionTemplate; +import org.springframework.util.Assert; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public abstract class AbstractDbDialect implements DbDialect { + + protected int databaseMajorVersion; + protected int databaseMinorVersion; + protected String databaseName; + protected JdbcTemplate jdbcTemplate; + protected TransactionTemplate transactionTemplate; + protected LobHandler lobHandler; + protected Map, Table> tables; + + public AbstractDbDialect(final JdbcTemplate jdbcTemplate, LobHandler lobHandler) { + this.jdbcTemplate = jdbcTemplate; + this.lobHandler = lobHandler; + // 初始化transction + this.transactionTemplate = new TransactionTemplate(); + transactionTemplate.setTransactionManager(new DataSourceTransactionManager(jdbcTemplate.getDataSource())); + transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + + // 初始化一些数据 + jdbcTemplate.execute(new ConnectionCallback() { + + public Object doInConnection(Connection c) throws SQLException, DataAccessException { + DatabaseMetaData meta = c.getMetaData(); + databaseName = meta.getDatabaseProductName(); + databaseMajorVersion = meta.getDatabaseMajorVersion(); + databaseMinorVersion = meta.getDatabaseMinorVersion(); + + return null; + } + }); + + initTables(jdbcTemplate); + } + + public Table findTable(String schema, String table, boolean useCache) { + List key = Arrays.asList(schema, table); + if (useCache == false) { + tables.remove(key); + } + + return tables.get(key); + } + + public Table findTable(String schema, String table) { + return findTable(schema, table, true); + } + + public LobHandler getLobHandler() { + return lobHandler; + } + + public JdbcTemplate getJdbcTemplate() { + return jdbcTemplate; + } + + public TransactionTemplate getTransactionTemplate() { + return transactionTemplate; + } + + private void initTables(final JdbcTemplate jdbcTemplate) { + this.tables = MigrateMap.makeComputingMap(new Function, Table>() { + + public Table apply(List names) { + Assert.isTrue(names.size() == 2); + try { + Table table = DdlUtils.findTable(jdbcTemplate, names.get(0), names.get(0), names.get(1)); + if (table == null) { + throw new NestableRuntimeException("no found table [" + names.get(0) + "." + names.get(1) + + "] , pls check"); + } else { + return table; + } + } catch (Exception e) { + throw new NestableRuntimeException("find table [" + names.get(0) + "." + names.get(1) + "] error", + e); + } + } + }); + } + + +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java new file mode 100644 index 00000000..fbbecabe --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/AbstractSqlTemplate.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect; + +/** + * 默认的基于标准SQL实现的CRUD sql封装 + * + * @author jianghang 2011-10-27 下午01:37:00 + * @version 4.0.0 + */ +public abstract class AbstractSqlTemplate implements SqlTemplate { + + private static final String DOT = "."; + + public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { + StringBuilder sql = new StringBuilder("select "); + int size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(columnNames[i])).append((i + 1 < size) ? " , " : ""); + } + + sql.append(" from ").append(getFullName(schemaName, tableName)).append(" where ( "); + appendColumnEquals(sql, pkNames, "and"); + sql.append(" ) "); + return sql.toString().intern();// 不使用intern,避免方法区内存消耗过多 + } + + public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { + StringBuilder sql = new StringBuilder("update " + getFullName(schemaName, tableName) + " set "); + appendColumnEquals(sql, columnNames, ","); + sql.append(" where ("); + appendColumnEquals(sql, pkNames, "and"); + sql.append(")"); + return sql.toString().intern(); // 不使用intern,避免方法区内存消耗过多 + } + + public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames) { + StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "("); + String[] allColumns = new String[pkNames.length + columnNames.length]; + System.arraycopy(columnNames, 0, allColumns, 0, columnNames.length); + System.arraycopy(pkNames, 0, allColumns, columnNames.length, pkNames.length); + + int size = allColumns.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(allColumns[i])).append((i + 1 < size) ? "," : ""); + } + + sql.append(") values ("); + appendColumnQuestions(sql, allColumns); + sql.append(")"); + return sql.toString().intern();// intern优化,避免出现大量相同的字符串 + } + + public String getDeleteSql(String schemaName, String tableName, String[] pkNames) { + StringBuilder sql = new StringBuilder("delete from " + getFullName(schemaName, tableName) + " where "); + appendColumnEquals(sql, pkNames, "and"); + return sql.toString().intern();// intern优化,避免出现大量相同的字符串 + } + + protected String getFullName(String schemaName, String tableName) { + StringBuilder sb = new StringBuilder(); + if (schemaName != null) { + sb.append(appendEscape(schemaName)).append(DOT); + } + sb.append(appendEscape(tableName)); + return sb.toString().intern(); + } + + // ================ helper method ============ + + protected String appendEscape(String columnName) { + return columnName; + } + + protected void appendColumnQuestions(StringBuilder sql, String[] columns) { + int size = columns.length; + for (int i = 0; i < size; i++) { + sql.append("?").append((i + 1 < size) ? " , " : ""); + } + } + + protected void appendColumnEquals(StringBuilder sql, String[] columns, String separator) { + int size = columns.length; + for (int i = 0; i < size; i++) { + sql.append(" ").append(appendEscape(columns[i])).append(" = ").append("? "); + if (i != size - 1) { + sql.append(separator); + } + } + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java new file mode 100644 index 00000000..060f476d --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/DbDialect.java @@ -0,0 +1,20 @@ +package com.alibaba.otter.canal.example.db.dialect; + +import org.apache.ddlutils.model.Table; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.lob.LobHandler; +import org.springframework.transaction.support.TransactionTemplate; + +public interface DbDialect { + + LobHandler getLobHandler(); + + JdbcTemplate getJdbcTemplate(); + + TransactionTemplate getTransactionTemplate(); + + Table findTable(String schema, String table); + + Table findTable(String schema, String table, boolean useCache); + +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java new file mode 100644 index 00000000..a90bd5ce --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/SqlTemplate.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect; + +/** + * sql构造模板操作 + * + * @author jianghang 2011-10-27 下午01:31:15 + * @version 4.0.0 + */ +public interface SqlTemplate { + + public String getSelectSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); + + public String getUpdateSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); + + public String getDeleteSql(String schemaName, String tableName, String[] pkNames); + + public String getInsertSql(String schemaName, String tableName, String[] pkNames, String[] columnNames); + + /** + * 获取对应的mergeSql + */ + public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames, + String[] viewColumnNames, boolean updatePks); +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java new file mode 100644 index 00000000..58cf662a --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/TableType.java @@ -0,0 +1,93 @@ +package com.alibaba.otter.canal.example.db.dialect; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * An enumeration wrapper around JDBC table types. + */ +public enum TableType { + + /** + * Unknown + */ + unknown, + + /** + * System table + */ + system_table, + + /** + * Global temporary + */ + global_temporary, + + /** + * Local temporary + */ + local_temporary, + + /** + * Table + */ + table, + + /** + * View + */ + view, + + /** + * Alias + */ + alias, + + /** + * Synonym + */ + synonym,; + + /** + * Converts an array of table types to an array of their corresponding string values. + * + * @param tableTypes Array of table types + * @return Array of string table types + */ + public static String[] toStrings(final TableType[] tableTypes) { + if ((tableTypes == null) || (tableTypes.length == 0)) { + return new String[0]; + } + + final List tableTypeStrings = new ArrayList(tableTypes.length); + + for (final TableType tableType : tableTypes) { + if (tableType != null) { + tableTypeStrings.add(tableType.toString().toUpperCase(Locale.ENGLISH)); + } + } + + return tableTypeStrings.toArray(new String[tableTypeStrings.size()]); + } + + /** + * Converts an array of string table types to an array of their corresponding enumeration values. + * + * @param tableTypeStrings Array of string table types + * @return Array of table types + */ + public static TableType[] valueOf(final String[] tableTypeStrings) { + if ((tableTypeStrings == null) || (tableTypeStrings.length == 0)) { + return new TableType[0]; + } + + final List tableTypes = new ArrayList(tableTypeStrings.length); + + for (final String tableTypeString : tableTypeStrings) { + tableTypes.add(valueOf(tableTypeString.toLowerCase(Locale.ENGLISH))); + } + + return tableTypes.toArray(new TableType[tableTypes.size()]); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java new file mode 100644 index 00000000..7bdfe1df --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlDialect.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect.mysql; + +import com.alibaba.otter.canal.example.db.dialect.AbstractDbDialect; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.lob.LobHandler; + +public class MysqlDialect extends AbstractDbDialect { + + public MysqlDialect(JdbcTemplate jdbcTemplate, LobHandler lobHandler) { + super(jdbcTemplate, lobHandler); + } + + public boolean isEmptyStringNulled() { + return false; + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java new file mode 100644 index 00000000..0cf987ff --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/dialect/mysql/MysqlSqlTemplate.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2010-2101 Alibaba Group Holding Limited. + * + * 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 com.alibaba.otter.canal.example.db.dialect.mysql; + +import com.alibaba.otter.canal.example.db.dialect.AbstractSqlTemplate; + +/** + * mysql sql生成模板 + * + * @author jianghang 2011-10-27 下午01:41:20 + * @version 4.0.0 + */ +public class MysqlSqlTemplate extends AbstractSqlTemplate { + + private static final String ESCAPE = "`"; + + public String getMergeSql(String schemaName, String tableName, String[] pkNames, String[] columnNames, + String[] viewColumnNames, boolean includePks) { + StringBuilder sql = new StringBuilder("insert into " + getFullName(schemaName, tableName) + "("); + int size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(columnNames[i])).append(" , "); + } + size = pkNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(pkNames[i])).append((i + 1 < size) ? " , " : ""); + } + + sql.append(") values ("); + size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append("?").append(" , "); + } + size = pkNames.length; + for (int i = 0; i < size; i++) { + sql.append("?").append((i + 1 < size) ? " , " : ""); + } + sql.append(")"); + sql.append(" on duplicate key update "); + + size = columnNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(columnNames[i])) + .append("=values(") + .append(appendEscape(columnNames[i])) + .append(")"); + if (includePks) { + sql.append(" , "); + } else { + sql.append((i + 1 < size) ? " , " : ""); + } + } + + if (includePks) { + // mysql merge sql匹配了uniqe / primary key时都会执行update,所以需要更新pk信息 + size = pkNames.length; + for (int i = 0; i < size; i++) { + sql.append(appendEscape(pkNames[i])).append("=values(").append(appendEscape(pkNames[i])).append(")"); + sql.append((i + 1 < size) ? " , " : ""); + } + } + + return sql.toString().intern();// intern优化,避免出现大量相同的字符串 + } + + protected String appendEscape(String columnName) { + return ESCAPE + columnName + ESCAPE; + } + +} \ No newline at end of file diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java new file mode 100644 index 00000000..608b81e6 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/AbstractMysqlClient.java @@ -0,0 +1,207 @@ +package com.alibaba.otter.canal.example.db.mysql; + +import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.example.db.AbstractDbClient; +import com.alibaba.otter.canal.example.db.dialect.DbDialect; +import com.alibaba.otter.canal.example.db.dialect.mysql.MysqlDialect; +import com.alibaba.otter.canal.example.db.dialect.mysql.MysqlSqlTemplate; +import com.alibaba.otter.canal.example.db.dialect.SqlTemplate; +import com.alibaba.otter.canal.example.db.utils.SqlUtils; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import org.apache.commons.lang.StringUtils; +import org.apache.ddlutils.model.Column; +import org.apache.ddlutils.model.Table; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.PreparedStatementSetter; +import org.springframework.jdbc.core.StatementCreatorUtils; +import org.springframework.jdbc.support.lob.DefaultLobHandler; +import org.springframework.jdbc.support.lob.LobCreator; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallback; + +import javax.sql.DataSource; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.sql.Types; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public abstract class AbstractMysqlClient extends AbstractDbClient { + + private DataSource dataSource; + + private DbDialect dbDialect; + private SqlTemplate sqlTemplate; + + protected Integer execute(final CanalEntry.Header header, final List columns) { + final String sql = getSql(header, columns); + final LobCreator lobCreator = dbDialect.getLobHandler().getLobCreator(); + dbDialect.getTransactionTemplate().execute(new TransactionCallback() { + + public Object doInTransaction(TransactionStatus status) { + try { + JdbcTemplate template = dbDialect.getJdbcTemplate(); + int affect = template.update(sql, new PreparedStatementSetter() { + + public void setValues(PreparedStatement ps) throws SQLException { + doPreparedStatement(ps, dbDialect, lobCreator, header, columns); + } + }); + return affect; + } finally { + lobCreator.close(); + } + } + }); + return 0; + } + + private String getSql(CanalEntry.Header header, List columns) { + List pkNames = new ArrayList<>(); + List colNames = new ArrayList<>(); + for (CanalEntry.Column column : columns) { + if (column.getIsKey()) { + pkNames.add(column.getName()); + } else { + colNames.add(column.getName()); + } + } + String sql = ""; + CanalEntry.EventType eventType = header.getEventType(); + switch (eventType) { + case INSERT: + sql = sqlTemplate.getInsertSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{}), colNames.toArray(new String[]{})); + break; + case UPDATE: + sql = sqlTemplate.getUpdateSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{}), colNames.toArray(new String[]{})); + break; + case DELETE: + sql = sqlTemplate.getDeleteSql(header.getSchemaName(), header.getTableName(), pkNames.toArray(new String[]{})); + } + logger.info("Execute sql: {}", sql); + return sql; + } + + private void doPreparedStatement(PreparedStatement ps, DbDialect dbDialect, LobCreator lobCreator, + CanalEntry.Header header, List columns) throws SQLException { + + List rebuildColumns = new ArrayList<>(columns.size()); + + List keyColumns = new ArrayList<>(columns.size()); + List notKeyColumns = new ArrayList<>(columns.size()); + for (CanalEntry.Column column : columns) { + if (column.getIsKey()) { + keyColumns.add(column); + } else { + notKeyColumns.add(column); + } + } + CanalEntry.EventType eventType = header.getEventType(); + switch (eventType) { + case INSERT: + case UPDATE: + // insert/update语句对应的字段数序都是将主键排在后面 + rebuildColumns.addAll(notKeyColumns); + rebuildColumns.addAll(keyColumns); + break; + case DELETE: + rebuildColumns.addAll(keyColumns); + } + + // 获取一下当前字段名的数据是否必填 + Table table = dbDialect.findTable(header.getSchemaName(), header.getTableName()); + Map isRequiredMap = new HashMap(); + for (Column tableColumn : table.getColumns()) { + isRequiredMap.put(StringUtils.lowerCase(tableColumn.getName()), tableColumn.isRequired()); + } + + List values = new ArrayList<>(rebuildColumns.size()); + for (int i = 0; i < rebuildColumns.size(); i++) { + int paramIndex = i + 1; + CanalEntry.Column column = rebuildColumns.get(i); + int sqlType = column.getSqlType(); + + Boolean isRequired = isRequiredMap.get(StringUtils.lowerCase(column.getName())); + if (isRequired == null) { + // 清理一下目标库的表结构,二次检查一下 + table = dbDialect.findTable(header.getSchemaName(), header.getTableName()); + + isRequiredMap = new HashMap<>(); + for (Column tableColumn : table.getColumns()) { + isRequiredMap.put(StringUtils.lowerCase(tableColumn.getName()), tableColumn.isRequired()); + } + + isRequired = isRequiredMap.get(StringUtils.lowerCase(column.getName())); + if (isRequired == null) { + throw new CanalClientException(String.format("column name %s is not found in Table[%s]", + column.getName(), + table.toString())); + } + } + + Object param; + if (sqlType == Types.TIME || sqlType == Types.TIMESTAMP || sqlType == Types.DATE) { + // 解决mysql的0000-00-00 00:00:00问题,直接依赖mysql + // driver进行处理,如果转化为Timestamp会出错 + param = column.getValue(); + if (param instanceof String && StringUtils.isEmpty(String.valueOf(param))) { + param = null; + } + } else { + param = SqlUtils.stringToSqlValue(column.getValue(), + sqlType, + isRequired, + column.getIsNull()); + } + + try { + switch (sqlType) { + case Types.CLOB: + lobCreator.setClobAsString(ps, paramIndex, (String) param); + break; + case Types.BLOB: + lobCreator.setBlobAsBytes(ps, paramIndex, (byte[]) param); + break; + case Types.TIME: + case Types.TIMESTAMP: + case Types.DATE: + ps.setObject(paramIndex, param); + break; + case Types.BIT: + StatementCreatorUtils.setParameterValue(ps, paramIndex, Types.DECIMAL, null, param); + break; + default: + StatementCreatorUtils.setParameterValue(ps, paramIndex, sqlType, null, param); + break; + } + values.add(param); + } catch (SQLException ex) { + logger.error("## SetParam error , [sqltype={}, value={}]", + new Object[]{sqlType, param}); + throw ex; + } + } + logger.info("## sql values: {}", JSON.toJSONString(values)); + } + + @Override + public void afterPropertiesSet() { + JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); + DefaultLobHandler lobHandler = new DefaultLobHandler(); + lobHandler.setStreamAsLob(true); + dbDialect = new MysqlDialect(jdbcTemplate, lobHandler); + sqlTemplate = new MysqlSqlTemplate(); + } + + public DataSource getDataSource() { + return dataSource; + } + + public void setDataSource(DataSource dataSource) { + this.dataSource = dataSource; + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java new file mode 100644 index 00000000..f16b6146 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/mysql/MysqlClient.java @@ -0,0 +1,23 @@ +package com.alibaba.otter.canal.example.db.mysql; + +import com.alibaba.otter.canal.protocol.CanalEntry; + +import java.util.List; + +public class MysqlClient extends AbstractMysqlClient { + + @Override + public void insert(CanalEntry.Header header, List afterColumns) { + execute(header, afterColumns); + } + + @Override + public void update(CanalEntry.Header header, List afterColumns) { + execute(header, afterColumns); + } + + @Override + public void delete(CanalEntry.Header header, List beforeColumns) { + execute(header, beforeColumns); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java new file mode 100644 index 00000000..6bd33686 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/ByteArrayConverter.java @@ -0,0 +1,50 @@ +package com.alibaba.otter.canal.example.db.utils; + +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.Converter; +import org.apache.commons.beanutils.converters.ArrayConverter; +import org.apache.commons.beanutils.converters.ByteConverter; + +public class ByteArrayConverter implements Converter { + + public static final Converter SQL_BYTES = new ByteArrayConverter(null); + private static final Converter converter = new ArrayConverter(byte[].class, new ByteConverter()); + + protected final Object defaultValue; + protected final boolean useDefault; + + public ByteArrayConverter() { + this.defaultValue = null; + this.useDefault = false; + } + + public ByteArrayConverter(Object defaultValue) { + this.defaultValue = defaultValue; + this.useDefault = true; + } + + public Object convert(Class type, Object value) { + if (value == null) { + if (useDefault) { + return (defaultValue); + } else { + throw new ConversionException("No value specified"); + } + } + + if (value instanceof byte[]) { + return (value); + } + + // BLOB类型,canal直接存储为String("ISO-8859-1") + if (value instanceof String) { + try { + return ((String) value).getBytes("ISO-8859-1"); + } catch (Exception e) { + throw new ConversionException(e); + } + } + + return converter.convert(type, value); // byteConvertor进行转化 + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java new file mode 100644 index 00000000..7964b18d --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/DdlUtils.java @@ -0,0 +1,326 @@ +package com.alibaba.otter.canal.example.db.utils; + +import com.alibaba.otter.canal.example.db.dialect.TableType; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.builder.ToStringBuilder; +import org.apache.commons.lang.builder.ToStringStyle; +import org.apache.commons.lang.math.NumberUtils; +import org.apache.ddlutils.model.Column; +import org.apache.ddlutils.model.Table; +import org.apache.ddlutils.platform.DatabaseMetaDataWrapper; +import org.apache.ddlutils.platform.MetaDataColumnDescriptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.JdbcUtils; + +import java.sql.*; +import java.util.*; + + +public class DdlUtils { + + private static final Logger logger = LoggerFactory.getLogger(DdlUtils.class); + private static TableType[] SUPPORTED_TABLE_TYPES = new TableType[]{TableType.view, TableType.table}; + private final static Map _defaultSizes = new HashMap(); + + static { + _defaultSizes.put(new Integer(1), "254"); + _defaultSizes.put(new Integer(12), "254"); + _defaultSizes.put(new Integer(-1), "254"); + _defaultSizes.put(new Integer(-2), "254"); + _defaultSizes.put(new Integer(-3), "254"); + _defaultSizes.put(new Integer(-4), "254"); + _defaultSizes.put(new Integer(4), "32"); + _defaultSizes.put(new Integer(-5), "64"); + _defaultSizes.put(new Integer(7), "7,0"); + _defaultSizes.put(new Integer(6), "15,0"); + _defaultSizes.put(new Integer(8), "15,0"); + _defaultSizes.put(new Integer(3), "15,15"); + _defaultSizes.put(new Integer(2), "15,15"); + } + + + public static Table findTable(final JdbcTemplate jdbcTemplate, final String catalogName, final String schemaName, + final String tableName) { + return (Table) jdbcTemplate.execute(new ConnectionCallback() { + + public Object doInConnection(Connection con) throws SQLException, DataAccessException { + Table table = null; + DatabaseMetaDataWrapper metaData = new DatabaseMetaDataWrapper(); + try { + + DatabaseMetaData databaseMetaData = con.getMetaData(); + + metaData.setMetaData(databaseMetaData); + metaData.setTableTypes(TableType.toStrings(SUPPORTED_TABLE_TYPES)); + metaData.setCatalog(catalogName); + metaData.setSchemaPattern(schemaName); + + String convertTableName = tableName; + if (databaseMetaData.storesUpperCaseIdentifiers()) { + metaData.setCatalog(catalogName.toUpperCase()); + metaData.setSchemaPattern(schemaName.toUpperCase()); + convertTableName = tableName.toUpperCase(); + } + if (databaseMetaData.storesLowerCaseIdentifiers()) { + metaData.setCatalog(catalogName.toLowerCase()); + metaData.setSchemaPattern(schemaName.toLowerCase()); + convertTableName = tableName.toLowerCase(); + } + + ResultSet tableData = null; + try { + tableData = metaData.getTables(convertTableName); + + while ((tableData != null) && tableData.next()) { + Map values = readColumns(tableData, initColumnsForTable()); + + table = readTable(metaData, values); + if (table.getName().equalsIgnoreCase(tableName)) { + break; + } + } + } finally { + JdbcUtils.closeResultSet(tableData); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + + makeAllColumnsPrimaryKeysIfNoPrimaryKeysFound(table); + return table; + } + }); + } + + /** + * Treat tables with no primary keys as a table with all primary keys. + */ + private static void makeAllColumnsPrimaryKeysIfNoPrimaryKeysFound(Table table) { + if ((table != null) && (table.getPrimaryKeyColumns() != null) && (table.getPrimaryKeyColumns().length == 0)) { + Column[] allCoumns = table.getColumns(); + + for (Column column : allCoumns) { + column.setPrimaryKey(true); + } + } + } + + private static Table readTable(DatabaseMetaDataWrapper metaData, Map values) throws SQLException { + String tableName = (String) values.get("TABLE_NAME"); + Table table = null; + + if ((tableName != null) && (tableName.length() > 0)) { + table = new Table(); + table.setName(tableName); + table.setType((String) values.get("TABLE_TYPE")); + table.setCatalog((String) values.get("TABLE_CAT")); + table.setSchema((String) values.get("TABLE_SCHEM")); + table.setDescription((String) values.get("REMARKS")); + table.addColumns(readColumns(metaData, tableName)); + + Collection primaryKeys = readPrimaryKeyNames(metaData, tableName); + + for (Object key : primaryKeys) { + Column col = table.findColumn((String) key, true); + + if (col != null) { + col.setPrimaryKey(true); + } else { + throw new NullPointerException(String.format("%s pk %s is null - %s %s", + tableName, + key, + ToStringBuilder.reflectionToString(metaData, ToStringStyle.SIMPLE_STYLE), + ToStringBuilder.reflectionToString(values, ToStringStyle.SIMPLE_STYLE))); + } + } + } + + return table; + } + + private static List initColumnsForTable() { + List result = new ArrayList(); + + result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("TABLE_TYPE", Types.VARCHAR, "UNKNOWN")); + result.add(new MetaDataColumnDescriptor("TABLE_CAT", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("TABLE_SCHEM", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR)); + + return result; + } + + private static List initColumnsForColumn() { + List result = new ArrayList(); + + // As suggested by Alexandre Borgoltz, we're reading the COLUMN_DEF + // first because Oracle + // has problems otherwise (it seemingly requires a LONG column to be the + // first to be read) + // See also DDLUTILS-29 + result.add(new MetaDataColumnDescriptor("COLUMN_DEF", Types.VARCHAR)); + + // we're also reading the table name so that a model reader impl can + // filter manually + result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("TYPE_NAME", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("DATA_TYPE", Types.INTEGER, new Integer(Types.OTHER))); + result.add(new MetaDataColumnDescriptor("NUM_PREC_RADIX", Types.INTEGER, new Integer(10))); + result.add(new MetaDataColumnDescriptor("DECIMAL_DIGITS", Types.INTEGER, new Integer(0))); + result.add(new MetaDataColumnDescriptor("COLUMN_SIZE", Types.VARCHAR)); + result.add(new MetaDataColumnDescriptor("IS_NULLABLE", Types.VARCHAR, "YES")); + result.add(new MetaDataColumnDescriptor("REMARKS", Types.VARCHAR)); + + return result; + } + + private static List initColumnsForPK() { + List result = new ArrayList(); + + result.add(new MetaDataColumnDescriptor("COLUMN_NAME", Types.VARCHAR)); + + // we're also reading the table name so that a model reader impl can + // filter manually + result.add(new MetaDataColumnDescriptor("TABLE_NAME", Types.VARCHAR)); + + // the name of the primary key is currently only interesting to the pk + // index name resolution + result.add(new MetaDataColumnDescriptor("PK_NAME", Types.VARCHAR)); + + return result; + } + + private static List readColumns(DatabaseMetaDataWrapper metaData, String tableName) throws SQLException { + ResultSet columnData = null; + + try { + columnData = metaData.getColumns(tableName, null); + + List columns = new ArrayList(); + Map values; + + for (; columnData.next(); columns.add(readColumn(metaData, values))) { + Map tmp = readColumns(columnData, initColumnsForColumn()); + if (tableName.equalsIgnoreCase((String) tmp.get("TABLE_NAME"))) { + values = tmp; + } else { + break; + } + } + + return columns; + } finally { + JdbcUtils.closeResultSet(columnData); + } + } + + private static Column readColumn(DatabaseMetaDataWrapper metaData, Map values) throws SQLException { + Column column = new Column(); + + column.setName((String) values.get("COLUMN_NAME")); + column.setDefaultValue((String) values.get("COLUMN_DEF")); + column.setTypeCode(((Integer) values.get("DATA_TYPE")).intValue()); + + String typeName = (String) values.get("TYPE_NAME"); + // column.setType(typeName); + + if ((typeName != null) && typeName.startsWith("TIMESTAMP")) { + column.setTypeCode(Types.TIMESTAMP); + } + // modify 2013-09-25,处理下unsigned + if ((typeName != null) && StringUtils.containsIgnoreCase(typeName, "UNSIGNED")) { + // 如果为unsigned,往上调大一个量级,避免数据溢出 + switch (column.getTypeCode()) { + case Types.TINYINT: + column.setTypeCode(Types.SMALLINT); + break; + case Types.SMALLINT: + column.setTypeCode(Types.INTEGER); + break; + case Types.INTEGER: + column.setTypeCode(Types.BIGINT); + break; + case Types.BIGINT: + column.setTypeCode(Types.DECIMAL); + break; + default: + break; + } + } + + Integer precision = (Integer) values.get("NUM_PREC_RADIX"); + + if (precision != null) { + column.setPrecisionRadix(precision.intValue()); + } + + String size = (String) values.get("COLUMN_SIZE"); + + if (size == null) { + size = (String) _defaultSizes.get(new Integer(column.getTypeCode())); + } + + // we're setting the size after the precision and radix in case + // the database prefers to return them in the size value + column.setSize(size); + + int scale = 0; + Object dec_digits = values.get("DECIMAL_DIGITS"); + + if (dec_digits instanceof String) { + scale = (dec_digits == null) ? 0 : NumberUtils.toInt(dec_digits.toString()); + } else if (dec_digits instanceof Integer) { + scale = (dec_digits == null) ? 0 : (Integer) dec_digits; + } + + if (scale != 0) { + column.setScale(scale); + } + + column.setRequired("NO".equalsIgnoreCase(((String) values.get("IS_NULLABLE")).trim())); + column.setDescription((String) values.get("REMARKS")); + return column; + } + + private static Map readColumns(ResultSet resultSet, List columnDescriptors) + throws SQLException { + Map values = new HashMap(); + MetaDataColumnDescriptor descriptor; + + for (Iterator it = columnDescriptors.iterator(); it.hasNext(); values.put(descriptor.getName(), + descriptor.readColumn(resultSet))) { + descriptor = (MetaDataColumnDescriptor) it.next(); + } + + return values; + } + + private static Collection readPrimaryKeyNames(DatabaseMetaDataWrapper metaData, String tableName) + throws SQLException { + ResultSet pkData = null; + + try { + List pks = new ArrayList(); + Map values; + + for (pkData = metaData.getPrimaryKeys(tableName); pkData.next(); pks.add(readPrimaryKeyName(metaData, + values))) { + values = readColumns(pkData, initColumnsForPK()); + } + + return pks; + } finally { + JdbcUtils.closeResultSet(pkData); + } + } + + private static String readPrimaryKeyName(DatabaseMetaDataWrapper metaData, Map values) + throws SQLException { + return (String) values.get("COLUMN_NAME"); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java new file mode 100644 index 00000000..5743106d --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlTimestampConverter.java @@ -0,0 +1,140 @@ +package com.alibaba.otter.canal.example.db.utils; + +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.Converter; +import org.apache.commons.lang.time.DateFormatUtils; + +import java.sql.Timestamp; +import java.text.ParseException; +import java.text.ParsePosition; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +public class SqlTimestampConverter implements Converter { + + /** + * Field description + */ + public static final String[] DATE_FORMATS = new String[]{"yyyy-MM-dd", "HH:mm:ss", "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd hh:mm:ss.fffffffff", "EEE MMM dd HH:mm:ss zzz yyyy", + DateFormatUtils.ISO_DATETIME_FORMAT.getPattern(), + DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.getPattern(), + DateFormatUtils.SMTP_DATETIME_FORMAT.getPattern(),}; + + public static final Converter SQL_TIMESTAMP = new SqlTimestampConverter(null); + + /** + * The default value specified to our Constructor, if any. + */ + private final Object defaultValue; + + /** + * Should we return the default value on conversion errors? + */ + private final boolean useDefault; + + /** + * Create a {@link Converter} that will throw a {@link ConversionException} if a conversion error occurs. + */ + public SqlTimestampConverter() { + this.defaultValue = null; + this.useDefault = false; + } + + /** + * Create a {@link Converter} that will return the specified default value if a conversion error occurs. + * + * @param defaultValue The default value to be returned + */ + public SqlTimestampConverter(Object defaultValue) { + this.defaultValue = defaultValue; + this.useDefault = true; + } + + /** + * Convert the specified input object into an output object of the specified type. + * + * @param type Data type to which this value should be converted + * @param value The input value to be converted + * @throws ConversionException if conversion cannot be performed successfully + */ + public Object convert(Class type, Object value) { + if (value == null) { + if (useDefault) { + return (defaultValue); + } else { + throw new ConversionException("No value specified"); + } + } + + if (value instanceof java.sql.Date && java.sql.Date.class.equals(type)) { + return value; + } else if (value instanceof java.sql.Time && java.sql.Time.class.equals(type)) { + return value; + } else if (value instanceof Timestamp && Timestamp.class.equals(type)) { + return value; + } else { + try { + if (java.sql.Date.class.equals(type)) { + return new java.sql.Date(convertTimestamp2TimeMillis(value.toString())); + } else if (java.sql.Time.class.equals(type)) { + return new java.sql.Time(convertTimestamp2TimeMillis(value.toString())); + } else if (Timestamp.class.equals(type)) { + return new Timestamp(convertTimestamp2TimeMillis(value.toString())); + } else { + return new Timestamp(convertTimestamp2TimeMillis(value.toString())); + } + } catch (Exception e) { + throw new ConversionException("Value format invalid: " + e.getMessage(), e); + } + } + + } + + private Long convertTimestamp2TimeMillis(String input) { + if (input == null) { + return null; + } + + try { + // 先处理Timestamp类型 + return Timestamp.valueOf(input).getTime(); + } catch (Exception nfe) { + try { + try { + return parseDate(input, DATE_FORMATS, Locale.ENGLISH).getTime(); + } catch (Exception err) { + return parseDate(input, DATE_FORMATS, Locale.getDefault()).getTime(); + } + } catch (Exception err) { + // 最后处理long time的情况 + return Long.parseLong(input); + } + } + } + + private Date parseDate(String str, String[] parsePatterns, Locale locale) throws ParseException { + if ((str == null) || (parsePatterns == null)) { + throw new IllegalArgumentException("Date and Patterns must not be null"); + } + + SimpleDateFormat parser = null; + ParsePosition pos = new ParsePosition(0); + + for (int i = 0; i < parsePatterns.length; i++) { + if (i == 0) { + parser = new SimpleDateFormat(parsePatterns[0], locale); + } else { + parser.applyPattern(parsePatterns[i]); + } + pos.setIndex(0); + Date date = parser.parse(str, pos); + if ((date != null) && (pos.getIndex() == str.length())) { + return date; + } + } + + throw new ParseException("Unable to parse the date: " + str, -1); + } +} diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java new file mode 100644 index 00000000..565d5730 --- /dev/null +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/utils/SqlUtils.java @@ -0,0 +1,315 @@ +package com.alibaba.otter.canal.example.db.utils; + +import org.apache.commons.beanutils.ConvertUtilsBean; +import org.apache.commons.lang.StringUtils; + +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.*; +import java.util.HashMap; +import java.util.Map; + +public class SqlUtils { + + public static final String REQUIRED_FIELD_NULL_SUBSTITUTE = " "; + public static final String SQLDATE_FORMAT = "yyyy-MM-dd"; + public static final String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm:ss"; + private static final Map> sqlTypeToJavaTypeMap = new HashMap>(); + private static final ConvertUtilsBean convertUtilsBean = new ConvertUtilsBean(); + + static { + // regist Converter + convertUtilsBean.register(SqlTimestampConverter.SQL_TIMESTAMP, Date.class); + convertUtilsBean.register(SqlTimestampConverter.SQL_TIMESTAMP, Time.class); + convertUtilsBean.register(SqlTimestampConverter.SQL_TIMESTAMP, Timestamp.class); + convertUtilsBean.register(ByteArrayConverter.SQL_BYTES, byte[].class); + + // bool + sqlTypeToJavaTypeMap.put(Types.BOOLEAN, Boolean.class); + + // int + sqlTypeToJavaTypeMap.put(Types.TINYINT, Integer.class); + sqlTypeToJavaTypeMap.put(Types.SMALLINT, Integer.class); + sqlTypeToJavaTypeMap.put(Types.INTEGER, Integer.class); + + // long + sqlTypeToJavaTypeMap.put(Types.BIGINT, Long.class); + // mysql bit最多64位,无符号 + sqlTypeToJavaTypeMap.put(Types.BIT, BigInteger.class); + + // decimal + sqlTypeToJavaTypeMap.put(Types.REAL, Float.class); + sqlTypeToJavaTypeMap.put(Types.FLOAT, Float.class); + sqlTypeToJavaTypeMap.put(Types.DOUBLE, Double.class); + sqlTypeToJavaTypeMap.put(Types.NUMERIC, BigDecimal.class); + sqlTypeToJavaTypeMap.put(Types.DECIMAL, BigDecimal.class); + + // date + sqlTypeToJavaTypeMap.put(Types.DATE, Date.class); + sqlTypeToJavaTypeMap.put(Types.TIME, Time.class); + sqlTypeToJavaTypeMap.put(Types.TIMESTAMP, Timestamp.class); + + // blob + sqlTypeToJavaTypeMap.put(Types.BLOB, byte[].class); + + // byte[] + sqlTypeToJavaTypeMap.put(Types.REF, byte[].class); + sqlTypeToJavaTypeMap.put(Types.OTHER, byte[].class); + sqlTypeToJavaTypeMap.put(Types.ARRAY, byte[].class); + sqlTypeToJavaTypeMap.put(Types.STRUCT, byte[].class); + sqlTypeToJavaTypeMap.put(Types.SQLXML, byte[].class); + sqlTypeToJavaTypeMap.put(Types.BINARY, byte[].class); + sqlTypeToJavaTypeMap.put(Types.DATALINK, byte[].class); + sqlTypeToJavaTypeMap.put(Types.DISTINCT, byte[].class); + sqlTypeToJavaTypeMap.put(Types.VARBINARY, byte[].class); + sqlTypeToJavaTypeMap.put(Types.JAVA_OBJECT, byte[].class); + sqlTypeToJavaTypeMap.put(Types.LONGVARBINARY, byte[].class); + + // String + sqlTypeToJavaTypeMap.put(Types.CHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.VARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.LONGVARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.LONGNVARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.NCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.NVARCHAR, String.class); + sqlTypeToJavaTypeMap.put(Types.NCLOB, String.class); + sqlTypeToJavaTypeMap.put(Types.CLOB, String.class); + } + + /** + * 将指定java.sql.Types的ResultSet value转换成相应的String + * + * @param rs + * @param index + * @param sqlType + * @return + * @throws SQLException + */ + public static String sqlValueToString(ResultSet rs, int index, int sqlType) throws SQLException { + Class requiredType = sqlTypeToJavaTypeMap.get(sqlType); + if (requiredType == null) { + throw new IllegalArgumentException("unknow java.sql.Types - " + sqlType); + } + + return getResultSetValue(rs, index, requiredType); + } + + /** + * sqlValueToString方法的逆向过程 + * + * @param value + * @param sqlType + * @param isTextRequired + * @param isEmptyStringNulled + * @return + */ + public static Object stringToSqlValue(String value, int sqlType, boolean isRequired, boolean isEmptyStringNulled) { + // 设置变量 + String sourceValue = value; + if (SqlUtils.isTextType(sqlType)) { + if ((sourceValue == null) || (StringUtils.isEmpty(sourceValue) && isEmptyStringNulled)) { + return isRequired ? REQUIRED_FIELD_NULL_SUBSTITUTE : null; + } else { + return sourceValue; + } + } else { + if (StringUtils.isEmpty(sourceValue)) { + return null; + } else { + Class requiredType = sqlTypeToJavaTypeMap.get(sqlType); + if (requiredType == null) { + throw new IllegalArgumentException("unknow java.sql.Types - " + sqlType); + } else if (requiredType.equals(String.class)) { + return sourceValue; + } else if (isNumeric(sqlType)) { + return convertUtilsBean.convert(sourceValue.trim(), requiredType); + } else { + return convertUtilsBean.convert(sourceValue, requiredType); + } + } + } + } + + public static String encoding(String source, int sqlType, String sourceEncoding, String targetEncoding) { + switch (sqlType) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + case Types.NCHAR: + case Types.NVARCHAR: + case Types.LONGNVARCHAR: + case Types.CLOB: + case Types.NCLOB: + if (false == StringUtils.isEmpty(source)) { + String fromEncoding = StringUtils.isBlank(sourceEncoding) ? "UTF-8" : sourceEncoding; + String toEncoding = StringUtils.isBlank(targetEncoding) ? "UTF-8" : targetEncoding; + + // if (false == StringUtils.equalsIgnoreCase(fromEncoding, + // toEncoding)) { + try { + return new String(source.getBytes(fromEncoding), toEncoding); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e.getMessage(), e); + } + // } + } + } + + return source; + } + + /** + * Retrieve a JDBC column value from a ResultSet, using the specified value + * type. + *

      + * Uses the specifically typed ResultSet accessor methods, falling back to + * {@link #getResultSetValue(ResultSet, int)} for unknown types. + *

      + * Note that the returned value may not be assignable to the specified + * required type, in case of an unknown type. Calling code needs to deal + * with this case appropriately, e.g. throwing a corresponding exception. + * + * @param rs is the ResultSet holding the data + * @param index is the column index + * @param requiredType the required value type (may be null) + * @return the value object + * @throws SQLException if thrown by the JDBC API + */ + private static String getResultSetValue(ResultSet rs, int index, Class requiredType) throws SQLException { + if (requiredType == null) { + return getResultSetValue(rs, index); + } + + Object value = null; + boolean wasNullCheck = false; + + // Explicitly extract typed value, as far as possible. + if (String.class.equals(requiredType)) { + value = rs.getString(index); + } else if (boolean.class.equals(requiredType) || Boolean.class.equals(requiredType)) { + value = Boolean.valueOf(rs.getBoolean(index)); + wasNullCheck = true; + } else if (byte.class.equals(requiredType) || Byte.class.equals(requiredType)) { + value = new Byte(rs.getByte(index)); + wasNullCheck = true; + } else if (short.class.equals(requiredType) || Short.class.equals(requiredType)) { + value = new Short(rs.getShort(index)); + wasNullCheck = true; + } else if (int.class.equals(requiredType) || Integer.class.equals(requiredType)) { + value = new Long(rs.getLong(index)); + wasNullCheck = true; + } else if (long.class.equals(requiredType) || Long.class.equals(requiredType)) { + value = rs.getBigDecimal(index); + wasNullCheck = true; + } else if (float.class.equals(requiredType) || Float.class.equals(requiredType)) { + value = new Float(rs.getFloat(index)); + wasNullCheck = true; + } else if (double.class.equals(requiredType) || Double.class.equals(requiredType) + || Number.class.equals(requiredType)) { + value = new Double(rs.getDouble(index)); + wasNullCheck = true; + } else if (Time.class.equals(requiredType)) { + // try { + // value = rs.getTime(index); + // } catch (SQLException e) { + value = rs.getString(index);// 尝试拿为string对象,0000无法用Time表示 + // if (value == null && !rs.wasNull()) { + // value = "00:00:00"; // + // mysql设置了zeroDateTimeBehavior=convertToNull,出现0值时返回为null + // } + // } + } else if (Timestamp.class.equals(requiredType) || Date.class.equals(requiredType)) { + // try { + // value = convertTimestamp(rs.getTimestamp(index)); + // } catch (SQLException e) { + // 尝试拿为string对象,0000-00-00 00:00:00无法用Timestamp 表示 + value = rs.getString(index); + // if (value == null && !rs.wasNull()) { + // value = "0000:00:00 00:00:00"; // + // mysql设置了zeroDateTimeBehavior=convertToNull,出现0值时返回为null + // } + // } + } else if (BigDecimal.class.equals(requiredType)) { + value = rs.getBigDecimal(index); + } else if (BigInteger.class.equals(requiredType)) { + value = rs.getBigDecimal(index); + } else if (Blob.class.equals(requiredType)) { + value = rs.getBlob(index); + } else if (Clob.class.equals(requiredType)) { + value = rs.getClob(index); + } else if (byte[].class.equals(requiredType)) { + try { + byte[] bytes = rs.getBytes(index); + if (bytes == null) { + value = null; + } else { + value = new String(bytes, "ISO-8859-1");// 将binary转化为iso-8859-1的字符串 + } + } catch (UnsupportedEncodingException e) { + throw new SQLException(e); + } + } else { + // Some unknown type desired -> rely on getObject. + value = getResultSetValue(rs, index); + } + + // Perform was-null check if demanded (for results that the + // JDBC driver returns as primitives). + if (wasNullCheck && (value != null) && rs.wasNull()) { + value = null; + } + + return (value == null) ? null : convertUtilsBean.convert(value); + } + + /** + * Retrieve a JDBC column value from a ResultSet, using the most appropriate + * value type. The returned value should be a detached value object, not + * having any ties to the active ResultSet: in particular, it should not be + * a Blob or Clob object but rather a byte array respectively String + * representation. + *

      + * Uses the getObject(index) method, but includes additional + * "hacks" to get around Oracle 10g returning a non-standard object for its + * TIMESTAMP datatype and a java.sql.Date for DATE columns + * leaving out the time portion: These columns will explicitly be extracted + * as standard java.sql.Timestamp object. + * + * @param rs is the ResultSet holding the data + * @param index is the column index + * @return the value object + * @throws SQLException if thrown by the JDBC API + * @see Blob + * @see Clob + * @see Timestamp + */ + private static String getResultSetValue(ResultSet rs, int index) throws SQLException { + Object obj = rs.getObject(index); + return (obj == null) ? null : convertUtilsBean.convert(obj); + } + + // private static Object convertTimestamp(Timestamp timestamp) { + // return (timestamp == null) ? null : timestamp.getTime(); + // } + + /** + * Check whether the given SQL type is numeric. + */ + public static boolean isNumeric(int sqlType) { + return (Types.BIT == sqlType) || (Types.BIGINT == sqlType) || (Types.DECIMAL == sqlType) + || (Types.DOUBLE == sqlType) || (Types.FLOAT == sqlType) || (Types.INTEGER == sqlType) + || (Types.NUMERIC == sqlType) || (Types.REAL == sqlType) || (Types.SMALLINT == sqlType) + || (Types.TINYINT == sqlType); + } + + public static boolean isTextType(int sqlType) { + if (sqlType == Types.CHAR || sqlType == Types.VARCHAR || sqlType == Types.CLOB || sqlType == Types.LONGVARCHAR + || sqlType == Types.NCHAR || sqlType == Types.NVARCHAR || sqlType == Types.NCLOB + || sqlType == Types.LONGNVARCHAR) { + return true; + } else { + return false; + } + } +} diff --git a/example/src/main/resources/client-spring.xml b/example/src/main/resources/client-spring.xml new file mode 100644 index 00000000..29eba1af --- /dev/null +++ b/example/src/main/resources/client-spring.xml @@ -0,0 +1,53 @@ + + + + + + + + + classpath:client.properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/src/main/resources/client.properties b/example/src/main/resources/client.properties new file mode 100644 index 00000000..c5142912 --- /dev/null +++ b/example/src/main/resources/client.properties @@ -0,0 +1,16 @@ +# client 配置 +zk.servers=127.0.0.1:2181 +# 5 * 1024 +client.batch.size=5120 +client.debug=false +client.destination=example +client.username=canal +client.password=canal +client.exceptionstrategy=1 +client.retrytimes=3 +client.filter=.*\\..* + +# 同步目标: mysql 配置 +target.mysql.url=jdbc:mysql://127.0.0.1:4306 +target.mysql.username=root +target.mysql.password=123456 diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index bca5ba13..039a80b7 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -6,6 +6,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; + +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.HistoryTableMetaCache; + +import com.alibaba.otter.canal.meta.FileMixedMetaManager; + import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,13 +25,7 @@ import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.instance.core.AbstractCanalInstance; import com.alibaba.otter.canal.instance.manager.model.Canal; import com.alibaba.otter.canal.instance.manager.model.CanalParameter; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.DataSourcing; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.HAMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.IndexMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.MetaMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.SourcingType; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageMode; -import com.alibaba.otter.canal.instance.manager.model.CanalParameter.StorageScavengeMode; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.*; import com.alibaba.otter.canal.meta.MemoryMetaManager; import com.alibaba.otter.canal.meta.PeriodMixedMetaManager; import com.alibaba.otter.canal.meta.ZooKeeperMetaManager; @@ -37,12 +36,7 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; -import com.alibaba.otter.canal.parse.index.FailbackLogPositionManager; -import com.alibaba.otter.canal.parse.index.MemoryLogPositionManager; -import com.alibaba.otter.canal.parse.index.MetaLogPositionManager; -import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager; -import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager; +import com.alibaba.otter.canal.parse.index.*; import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.sink.entry.EntryEventSink; @@ -120,6 +114,11 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { ZooKeeperMetaManager zooKeeperMetaManager = new ZooKeeperMetaManager(); zooKeeperMetaManager.setZkClientx(getZkclientx()); ((PeriodMixedMetaManager) metaManager).setZooKeeperMetaManager(zooKeeperMetaManager); + } else if (mode.isLocalFile()){ + FileMixedMetaManager fileMixedMetaManager = new FileMixedMetaManager(); + fileMixedMetaManager.setDataDir(parameters.getDataDir()); + fileMixedMetaManager.setPeriod(parameters.getMetaFileFlushPeriod()); + metaManager = fileMixedMetaManager; } else { throw new CanalException("unsupport MetaMode for " + mode); } @@ -242,6 +241,13 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { mysqlEventParser.setDetectingIntervalInSeconds(parameters.getDetectingIntervalInSeconds()); // 数据库信息参数 mysqlEventParser.setSlaveId(parameters.getSlaveId()); + mysqlEventParser.setTableMetaStorageFactory(parameters.getTableMetaStorageFactory()); + // Ctrip callback +// mysqlEventParser.setCallback(parameters.getCallback()); +// HistoryTableMetaCache cache = new HistoryTableMetaCache(); +// cache.init(parameters.getEntries()); +// mysqlEventParser.setHistoryTableMetaCache(cache); + if (!CollectionUtils.isEmpty(dbAddresses)) { mysqlEventParser.setMasterInfo(new AuthenticationInfo(dbAddresses.get(0), parameters.getDbUsername(), diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java index 16c929ca..d707c679 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java @@ -5,6 +5,7 @@ import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.builder.ToStringBuilder; @@ -28,8 +29,10 @@ public class CanalParameter implements Serializable { private Long zkClusterId; // zk集群id,为管理方便 private List zkClusters; // zk集群地址 + private String dataDir = "../conf"; // 默认本地文件数据的目录默认是conf // meta相关参数 private MetaMode metaMode = MetaMode.MEMORY; // meta机制 + private Integer metaFileFlushPeriod = 1000; // meta刷新间隔 // storage存储 private Integer transactionSize = 1024; // 支持处理的transaction事务大小 @@ -106,6 +109,9 @@ public class CanalParameter implements Serializable { private Long standbyLogfileOffest = null; private Long standbyTimestamp = null; + // Ctrip Table Meta + TableMetaStorageFactory tableMetaStorageFactory; + public static enum RunMode { /** 嵌入式 */ @@ -243,7 +249,9 @@ public class CanalParameter implements Serializable { /** 文件存储模式 */ ZOOKEEPER, /** 混合模式,内存+文件 */ - MIXED; + MIXED, + /** 本地文件存储模式*/ + LOCAL_FILE; public boolean isMemory() { return this.equals(MetaMode.MEMORY); @@ -256,6 +264,10 @@ public class CanalParameter implements Serializable { public boolean isMixed() { return this.equals(MetaMode.MIXED); } + + public boolean isLocalFile(){ + return this.equals(MetaMode.LOCAL_FILE); + } } public static enum IndexMode { @@ -390,6 +402,22 @@ public class CanalParameter implements Serializable { return storageMode; } + public String getDataDir() { + return dataDir; + } + + public void setDataDir(String dataDir) { + this.dataDir = dataDir; + } + + public Integer getMetaFileFlushPeriod() { + return metaFileFlushPeriod; + } + + public void setMetaFileFlushPeriod(Integer metaFileFlushPeriod) { + this.metaFileFlushPeriod = metaFileFlushPeriod; + } + public void setStorageMode(StorageMode storageMode) { this.storageMode = storageMode; } @@ -859,6 +887,14 @@ public class CanalParameter implements Serializable { this.blackFilter = blackFilter; } + public TableMetaStorageFactory getTableMetaStorageFactory() { + return tableMetaStorageFactory; + } + + public void setTableMetaStorageFactory(TableMetaStorageFactory tableMetaStorageFactory) { + this.tableMetaStorageFactory = tableMetaStorageFactory; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java index 1cb3daba..70f2fc06 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java +++ b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java @@ -29,7 +29,9 @@ public class MessageDeserializer implements Deserializer { @Override public Message deserialize(String topic, byte[] data) { try { - if (data == null) return null; + if (data == null) { + return null; + } else { CanalPacket.Packet p = CanalPacket.Packet.parseFrom(data); switch (p.getType()) { diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java index 3eab85ed..7bbbabd7 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java +++ b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java @@ -8,8 +8,8 @@ package com.alibaba.otter.canal.kafka.client.running; */ public class ClientRunningData { - private String groupId; - private String address; + private String groupId; + private String address; private boolean active = true; public String getGroupId() { diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java index 3bdbfe6e..0f61211e 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java +++ b/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java @@ -1,12 +1,11 @@ package com.alibaba.otter.canal.kafka.client.running; -import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; -import com.alibaba.otter.canal.common.utils.AddressUtils; -import com.alibaba.otter.canal.common.utils.BooleanMutex; -import com.alibaba.otter.canal.common.utils.JsonUtils; -import com.alibaba.otter.canal.common.zookeeper.ZkClientx; -import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; -import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import java.text.MessageFormat; +import java.util.Random; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.exception.ZkException; import org.I0Itec.zkclient.exception.ZkInterruptedException; @@ -17,11 +16,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; -import java.text.MessageFormat; -import java.util.Random; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; +import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; +import com.alibaba.otter.canal.common.utils.AddressUtils; +import com.alibaba.otter.canal.common.utils.BooleanMutex; +import com.alibaba.otter.canal.common.utils.JsonUtils; +import com.alibaba.otter.canal.common.zookeeper.ZkClientx; +import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; + /** * kafka client running状态信息 @@ -31,13 +33,18 @@ import java.util.concurrent.TimeUnit; */ public class ClientRunningMonitor extends AbstractCanalLifeCycle { - private static final String TOPIC_ROOT_NODE = ZookeeperPathUtils.CANAL_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "topics"; + private static final String TOPIC_ROOT_NODE = ZookeeperPathUtils.CANAL_ROOT_NODE + + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "topics"; - private static final String TOPIC_NODE = TOPIC_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "{0}"; + private static final String TOPIC_NODE = TOPIC_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + + "{0}"; - private static final String TOPIC_CLIENTID_NODE = TOPIC_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "{1}"; + private static final String TOPIC_CLIENTID_NODE = TOPIC_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + + "{1}"; - private static final String TOPIC_CLIENTID_RUNNING_NODE = TOPIC_CLIENTID_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + ZookeeperPathUtils.RUNNING_NODE; + private static final String TOPIC_CLIENTID_RUNNING_NODE = TOPIC_CLIENTID_NODE + + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + + ZookeeperPathUtils.RUNNING_NODE; private static String getTopicClientRunning(String topic, String groupId) { return MessageFormat.format(TOPIC_CLIENTID_RUNNING_NODE, topic, groupId); @@ -47,21 +54,21 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { return MessageFormat.format(TOPIC_CLIENTID_NODE, topic, groupId); } - private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class); - private ZkClientx zkClient; - private String topic; - private ClientRunningData clientData; - private IZkDataListener dataListener; - private BooleanMutex mutex = new BooleanMutex(false); - private volatile boolean release = false; + private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class); + private ZkClientx zkClient; + private String topic; + private ClientRunningData clientData; + private IZkDataListener dataListener; + private BooleanMutex mutex = new BooleanMutex(false); + private volatile boolean release = false; private volatile ClientRunningData activeData; - private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1); - private ClientRunningListener listener; - private int delayTime = 5; + private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1); + private ClientRunningListener listener; + private int delayTime = 5; - private static Integer virtualPort; + private static Integer virtualPort; - public ClientRunningMonitor() { + public ClientRunningMonitor(){ if (virtualPort == null) { Random rand = new Random(); virtualPort = rand.nextInt(9000) + 1000; @@ -108,7 +115,6 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { } - public void start() { super.start(); @@ -123,7 +129,7 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { String path = getTopicClientRunning(this.topic, clientData.getGroupId()); zkClient.unsubscribeDataChanges(path, dataListener); releaseRunning(); // 尝试一下release - //Fix issue #697 + // Fix issue #697 if (delayExector != null) { delayExector.shutdown(); } @@ -159,13 +165,12 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { } } } catch (ZkNoNodeException e) { - zkClient.createPersistent(getClientIdNodePath(this.topic, clientData.getGroupId()), - true); // 尝试创建父节点 + zkClient.createPersistent(getClientIdNodePath(this.topic, clientData.getGroupId()), true); // 尝试创建父节点 initRunning(); } catch (Throwable t) { logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].", - topic), - t); + topic), + t); // 出现任何异常尝试release releaseRunning(); throw new CanalClientException("something goes wrong in initRunning method. ", t); @@ -187,7 +192,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { */ public boolean check() { String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - //ZookeeperPathUtils.getDestinationClientRunning(this.destination, clientData.getClientId()); + // ZookeeperPathUtils.getDestinationClientRunning(this.destination, + // clientData.getClientId()); try { byte[] bytes = zkClient.readData(path); ClientRunningData eventData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class); @@ -196,8 +202,8 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { boolean result = isMine(activeData.getAddress()); if (!result) { logger.warn("canal is running in [{}] , but not in [{}]", - activeData.getAddress(), - clientData.getAddress()); + activeData.getAddress(), + clientData.getAddress()); } return result; } catch (ZkNoNodeException e) { @@ -235,7 +241,7 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { if (listener != null) { // 触发回调 listener.processActiveEnter(); - this.clientData.setAddress(/*address*/AddressUtils.getHostIp() + ":" + virtualPort); + this.clientData.setAddress(/* address */AddressUtils.getHostIp() + ":" + virtualPort); String path = getTopicClientRunning(this.topic, clientData.getGroupId()); // 序列化 diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java index e0b2d1d8..573f0eac 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java @@ -17,9 +17,9 @@ import com.alibaba.otter.canal.deployer.CanalController; */ public class CanalServerStarter { - private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(CanalServerStarter.class); - private volatile static boolean running = false; + private static final String CLASSPATH_URL_PREFIX = "classpath:"; + private static final Logger logger = LoggerFactory.getLogger(CanalServerStarter.class); + private volatile static boolean running = false; public static void init() { try { diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java index 1543abf9..4543aac1 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.kafka.producer; +import java.io.IOException; import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; @@ -10,7 +11,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.kafka.producer.KafkaProperties.Topic; -import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.Message; /** @@ -49,21 +49,33 @@ public class CanalKafkaProducer { } } - public void send(Topic topic, Message message) { - boolean valid = false; - if (message != null && !message.getEntries().isEmpty()) { - for (CanalEntry.Entry entry : message.getEntries()) { - if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - valid = true; - break; - } - } - } - if (!valid) { - return; - } + public void send(Topic topic, Message message) throws IOException { + // set canal.instance.filter.transaction.entry = true + // boolean valid = false; + // if (message != null) { + // if (message.isRaw() && !message.getRawEntries().isEmpty()) { + // for (ByteString byteString : message.getRawEntries()) { + // CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString); + // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN + // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { + // valid = true; + // break; + // } + // } + // } else if (!message.getEntries().isEmpty()){ + // for (CanalEntry.Entry entry : message.getEntries()) { + // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN + // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { + // valid = true; + // break; + // } + // } + // } + // } + // if (!valid) { + // return; + // } ProducerRecord record; if (topic.getPartition() != null) { record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java index 0ff018c2..0eedfa7a 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java @@ -52,7 +52,10 @@ public class CanalKafkaStarter { // 初始化 kafka producer canalKafkaProducer = new CanalKafkaProducer(); canalKafkaProducer.init(kafkaProperties); - + // set filterTransactionEntry + if (kafkaProperties.isFilterTransactionEntry()) { + System.setProperty("canal.instance.filter.transaction.entry", "true"); + } // 对应每个instance启动一个worker线程 List destinations = kafkaProperties.getCanalDestinations(); @@ -118,7 +121,7 @@ public class CanalKafkaStarter { Message message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); // 获取指定数量的数据 long batchId = message.getId(); try { - int size = message.getEntries().size(); + int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); if (batchId != -1 && size != 0) { if (!StringUtils.isEmpty(destination.getTopic())) { Topic topic = new Topic(); diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java index 0b346137..8e193aac 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java @@ -13,15 +13,15 @@ import java.util.Set; */ public class KafkaProperties { - private String servers = "localhost:6667"; - private int retries = 0; - private int batchSize = 16384; - private int lingerMs = 1; - private long bufferMemory = 33554432L; + private String servers = "localhost:6667"; + private int retries = 0; + private int batchSize = 16384; + private int lingerMs = 1; + private long bufferMemory = 33554432L; + private boolean filterTransactionEntry = true; + private int canalBatchSize = 5; - private int canalBatchSize = 5; - - private List canalDestinations = new ArrayList(); + private List canalDestinations = new ArrayList(); public static class CanalDestination { @@ -158,4 +158,13 @@ public class KafkaProperties { public void setCanalDestinations(List canalDestinations) { this.canalDestinations = canalDestinations; } + + public boolean isFilterTransactionEntry() { + return filterTransactionEntry; + } + + public void setFilterTransactionEntry(boolean filterTransactionEntry) { + this.filterTransactionEntry = filterTransactionEntry; + } + } diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java index 339e4f8c..02336004 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.kafka.producer; +import java.util.List; import java.util.Map; import org.apache.kafka.common.errors.SerializationException; @@ -8,7 +9,11 @@ import org.springframework.util.CollectionUtils; import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalPacket; +import com.alibaba.otter.canal.protocol.CanalPacket.PacketType; import com.alibaba.otter.canal.protocol.Message; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; /** * Kafka Message类的序列化 @@ -25,22 +30,59 @@ public class MessageSerializer implements Serializer { @Override public byte[] serialize(String topic, Message data) { try { - if (data == null) return null; - else { - CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); - if (data.getId() != -1 && !CollectionUtils.isEmpty(data.getEntries())) { - for (CanalEntry.Entry entry : data.getEntries()) { - messageBuilder.addMessages(entry.toByteString()); + if (data != null) { + if (data.getId() != -1) { + if (data.isRaw() && !CollectionUtils.isEmpty(data.getRawEntries())) { + // for performance + List rowEntries = data.getRawEntries(); + // message size + int messageSize = 0; + messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, data.getId()); + + int dataSize = 0; + for (int i = 0; i < rowEntries.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + } + messageSize += dataSize; + messageSize += 1 * rowEntries.size(); + // packet size + int size = 0; + size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, + PacketType.MESSAGES.getNumber()); + size += com.google.protobuf.CodedOutputStream.computeTagSize(5) + + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + + messageSize; + // build data + byte[] body = new byte[size]; + CodedOutputStream output = CodedOutputStream.newInstance(body); + output.writeEnum(3, PacketType.MESSAGES.getNumber()); + + output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(messageSize); + // message + output.writeInt64(1, data.getId()); + for (int i = 0; i < rowEntries.size(); i++) { + output.writeBytes(2, rowEntries.get(i)); + } + output.checkNoSpaceLeft(); + return body; + } else if (!CollectionUtils.isEmpty(data.getEntries())) { + CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); + for (CanalEntry.Entry entry : data.getEntries()) { + messageBuilder.addMessages(entry.toByteString()); + } + + CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); + packetBuilder.setType(CanalPacket.PacketType.MESSAGES); + packetBuilder.setBody(messageBuilder.build().toByteString()); + return packetBuilder.build().toByteArray(); } } - CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); - packetBuilder.setType(CanalPacket.PacketType.MESSAGES); - packetBuilder.setBody(messageBuilder.build().toByteString()); - return packetBuilder.build().toByteArray(); } } catch (Exception e) { throw new SerializationException("Error when serializing message to byte[] "); } + return null; } @Override diff --git a/kafka/src/main/resources/kafka.yml b/kafka/src/main/resources/kafka.yml index ab214319..d6ccc51a 100644 --- a/kafka/src/main/resources/kafka.yml +++ b/kafka/src/main/resources/kafka.yml @@ -5,6 +5,7 @@ lingerMs: 1 bufferMemory: 33554432 # canal的批次大小,单位 k canalBatchSize: 50 +filterTransactionEntry: true canalDestinations: - canalDestination: example diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java new file mode 100644 index 00000000..d5552a9f --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java @@ -0,0 +1,30 @@ +package com.alibaba.otter.canal.parse.exception; + +/** + * @author chengjin.lyf on 2018/7/20 下午2:54 + * @since 1.0.25 + */ +public class PositionNotFoundException extends CanalParseException { + + private static final long serialVersionUID = -7382448928116244017L; + + public PositionNotFoundException(String errorCode) { + super(errorCode); + } + + public PositionNotFoundException(String errorCode, Throwable cause) { + super(errorCode, cause); + } + + public PositionNotFoundException(String errorCode, String errorDesc) { + super(errorCode, errorDesc); + } + + public PositionNotFoundException(String errorCode, String errorDesc, Throwable cause) { + super(errorCode, errorDesc, cause); + } + + public PositionNotFoundException(Throwable cause) { + super(cause); + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java new file mode 100644 index 00000000..35a6b790 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java @@ -0,0 +1,30 @@ +package com.alibaba.otter.canal.parse.exception; + +import com.alibaba.otter.canal.common.CanalException; + +/** + * @author chengjin.lyf on 2018/8/8 下午1:07 + * @since 1.0.25 + */ +public class ServerIdNotMatchException extends CanalException{ + + public ServerIdNotMatchException(String errorCode) { + super(errorCode); + } + + public ServerIdNotMatchException(String errorCode, Throwable cause) { + super(errorCode, cause); + } + + public ServerIdNotMatchException(String errorCode, String errorDesc) { + super(errorCode, errorDesc); + } + + public ServerIdNotMatchException(String errorCode, String errorDesc, Throwable cause) { + super(errorCode, errorDesc, cause); + } + + public ServerIdNotMatchException(Throwable cause) { + super(cause); + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index 54bb14e7..0034ba2b 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -8,6 +8,7 @@ import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.math.RandomUtils; @@ -94,6 +95,10 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle .availableProcessors() * 60 / 100; // 60%的能力跑解析,剩余部分处理网络 protected int parallelBufferSize = 256; // 必须为2的幂 protected MultiStageCoprocessor multiStageCoprocessor; + protected ParserExceptionHandler parserExceptionHandler; + protected long serverId; + + protected abstract BinlogParser buildParser(); @@ -170,11 +175,16 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle preDump(erosaConnection); erosaConnection.connect();// 链接 + + long queryServerId = erosaConnection.queryServerId(); + if (queryServerId != 0){ + serverId = queryServerId; + } // 4. 获取最后的位置信息 EntryPosition position = findStartPosition(erosaConnection); final EntryPosition startPosition = position; if (startPosition == null) { - throw new CanalParseException("can't find start position for " + destination); + throw new PositionNotFoundException("can't find start position for " + destination); } if (!processTableMeta(startPosition)) { @@ -277,6 +287,9 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle runningInfo.getAddress().toString()), e); sendAlarm(destination, ExceptionUtils.getFullStackTrace(e)); } + if (parserExceptionHandler!=null){ + parserExceptionHandler.handle(e); + } } finally { // 重新置为中断状态 Thread.interrupted(); @@ -303,7 +316,12 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle transactionBuffer.reset();// 重置一下缓冲队列,重新记录数据 binlogParser.reset();// 重新置位 if (multiStageCoprocessor != null) { - multiStageCoprocessor.reset(); + // 处理 RejectedExecutionException + try { + multiStageCoprocessor.reset(); + } catch (Throwable t) { + logger.debug("multi processor rejected:", t); + } } if (running) { @@ -331,6 +349,11 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle stopHeartBeat(); // 先停止心跳 parseThread.interrupt(); // 尝试中断 eventSink.interrupt(); + + if (multiStageCoprocessor != null && multiStageCoprocessor.isStart()) { + multiStageCoprocessor.stop(); + } + try { parseThread.join();// 等待其结束 } catch (InterruptedException e) { @@ -343,10 +366,6 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle if (transactionBuffer.isStart()) { transactionBuffer.stop(); } - - if (multiStageCoprocessor != null && multiStageCoprocessor.isStart()) { - multiStageCoprocessor.stop(); - } } protected boolean consumeTheEventAndProfilingIfNecessary(List entrys) throws CanalSinkException, @@ -609,4 +628,19 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle this.parallelBufferSize = parallelBufferSize; } + public ParserExceptionHandler getParserExceptionHandler() { + return parserExceptionHandler; + } + + public void setParserExceptionHandler(ParserExceptionHandler parserExceptionHandler) { + this.parserExceptionHandler = parserExceptionHandler; + } + + public long getServerId() { + return serverId; + } + + public void setServerId(long serverId) { + this.serverId = serverId; + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ErosaConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ErosaConnection.java index 6beb4a9e..5355e2e1 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ErosaConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ErosaConnection.java @@ -40,4 +40,6 @@ public interface ErosaConnection { public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException; ErosaConnection fork(); + + public long queryServerId() throws IOException; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java index 1c5c495d..96e063e9 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java @@ -21,9 +21,9 @@ public interface MultiStageCoprocessor extends CanalLifeCycle { /** * 网络数据投递 */ - public void publish(LogBuffer buffer); + public boolean publish(LogBuffer buffer); - public void publish(LogBuffer buffer, String binlogFileName); + public boolean publish(LogBuffer buffer, String binlogFileName); public void reset(); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java new file mode 100644 index 00000000..d0face01 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java @@ -0,0 +1,9 @@ +package com.alibaba.otter.canal.parse.inbound; + +/** + * @author chengjin.lyf on 2018/7/20 下午3:55 + * @since 1.0.25 + */ +public interface ParserExceptionHandler { + void handle(Throwable e); +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index 8188cf83..c0d2f190 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.filter.CanalEventFilter; import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; +import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; @@ -91,8 +92,16 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { public void start() throws CanalParseException { if (enableTsdb) { if (tableMetaTSDB == null) { - // 初始化 - tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml); + synchronized (CanalEventParser.class) { + try { + // 设置当前正在加载的通道,加载spring查找文件时会用到该变量 + System.setProperty("canal.instance.destination", destination); + // 初始化 + tableMetaTSDB = TableMetaTSDBBuilder.build(destination, tsdbSpringXml); + } finally { + System.setProperty("canal.instance.destination", ""); + } + } } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java index f97c4ccd..0358dfda 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.IOException; import java.util.List; +import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -36,6 +37,9 @@ public class LocalBinLogConnection implements ErosaConnection { private String directory; private int bufferSize = 16 * 1024; private boolean running = false; + private long serverId; + private FileParserListener parserListener; + public LocalBinLogConnection(){ } @@ -96,6 +100,9 @@ public class LocalBinLogConnection implements ErosaConnection { if (event == null) { continue; } + if (serverId != 0 && event.getServerId() != serverId){ + throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + } if (!func.sink(event)) { needContinue = false; @@ -103,8 +110,9 @@ public class LocalBinLogConnection implements ErosaConnection { } } + fetcher.close(); // 关闭上一个文件 + parserFinish(current.getName()); if (needContinue) {// 读取下一个 - fetcher.close(); // 关闭上一个文件 File nextFile; if (needWait) { @@ -160,6 +168,11 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogEvent event = decoder.decode(fetcher, context); if (event != null) { + + if (serverId != 0 && event.getServerId() != serverId){ + throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + } + if (event.getWhen() > timestampSeconds) { break; } @@ -221,11 +234,16 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); - coprocessor.publish(buffer, binlogfilename); // set filename + // set filename + if (!coprocessor.publish(buffer, binlogfilename)) { + needContinue = false; + break; + } } + fetcher.close(); // 关闭上一个文件 + parserFinish(binlogfilename); if (needContinue) {// 读取下一个 - fetcher.close(); // 关闭上一个文件 File nextFile; if (needWait) { @@ -254,6 +272,12 @@ public class LocalBinLogConnection implements ErosaConnection { } } + private void parserFinish(String fileName){ + if (parserListener != null){ + parserListener.onFinish(fileName); + } + } + @Override public void dump(long timestampMills, MultiStageCoprocessor coprocessor) throws IOException { List currentBinlogs = binlogs.currentBinlogs(); @@ -282,6 +306,11 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogEvent event = decoder.decode(fetcher, context); if (event != null) { + + if (serverId != 0 && event.getServerId() != serverId){ + throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + } + if (event.getWhen() > timestampSeconds) { break; } @@ -340,6 +369,11 @@ public class LocalBinLogConnection implements ErosaConnection { return connection; } + @Override + public long queryServerId() { + return 0; + } + public boolean isNeedWait() { return needWait; } @@ -364,4 +398,20 @@ public class LocalBinLogConnection implements ErosaConnection { this.bufferSize = bufferSize; } + public long getServerId() { + return serverId; + } + + public void setServerId(long serverId) { + this.serverId = serverId; + } + + public void setParserListener(FileParserListener parserListener) { + this.parserListener = parserListener; + } + + public interface FileParserListener{ + void onFinish(String fileName); + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index 46f7e300..5f681a54 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -9,8 +9,10 @@ import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.util.CollectionUtils; import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector; import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor; @@ -206,7 +208,9 @@ public class MysqlConnection implements ErosaConnection { while (fetcher.fetch()) { LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); - coprocessor.publish(buffer); + if (!coprocessor.publish(buffer)) { + break; + } } } finally { fetcher.close(); @@ -230,7 +234,9 @@ public class MysqlConnection implements ErosaConnection { while (fetcher.fetch()) { LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); - coprocessor.publish(buffer); + if (!coprocessor.publish(buffer)) { + break; + } } } finally { fetcher.close(); @@ -320,6 +326,16 @@ public class MysqlConnection implements ErosaConnection { return connection; } + @Override + public long queryServerId() throws IOException { + ResultSetPacket resultSetPacket = query("show variables like 'server_id'"); + List fieldValues = resultSetPacket.getFieldValues(); + if (fieldValues == null || fieldValues.size() != 2){ + return 0; + } + return NumberUtils.toLong(fieldValues.get(1)); + } + // ====================== help method ==================== /** diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index 6c2b9d4d..a3d1ba01 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -10,6 +10,11 @@ import java.util.Map; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicLong; +import com.alibaba.otter.canal.parse.inbound.*; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheWithStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; @@ -20,9 +25,6 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.ha.CanalHAController; -import com.alibaba.otter.canal.parse.inbound.ErosaConnection; -import com.alibaba.otter.canal.parse.inbound.HeartBeatCallback; -import com.alibaba.otter.canal.parse.inbound.SinkFunction; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogFormat; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection.BinlogImage; import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.LogEventConvert; @@ -53,16 +55,16 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private int receiveBufferSize = 64 * 1024; private int sendBufferSize = 64 * 1024; // 数据库信息 - private AuthenticationInfo masterInfo; // 主库 - private AuthenticationInfo standbyInfo; // 备库 + protected AuthenticationInfo masterInfo; // 主库 + protected AuthenticationInfo standbyInfo; // 备库 // binlog信息 - private EntryPosition masterPosition; - private EntryPosition standbyPosition; + protected EntryPosition masterPosition; + protected EntryPosition standbyPosition; private long slaveId; // 链接到mysql的slave // 心跳检查信息 private String detectingSQL; // 心跳sql private MysqlConnection metaConnection; // 查询meta信息的链接 - private TableMetaCache tableMetaCache; // 对应meta + private TableMetaCacheInterface tableMetaCache; // 对应meta // cache private int fallbackIntervalInSeconds = 60; // 切换回退时间 private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 @@ -72,6 +74,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private int dumpErrorCount = 0; // binlogDump失败异常计数 private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 + private TableMetaStorageFactory tableMetaStorageFactory; + protected ErosaConnection buildErosaConnection() { return buildMysqlConnection(this.runningInfo); } @@ -125,7 +129,13 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter); } - tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB); + + TableMetaStorage storage = null; + if (tableMetaStorageFactory != null) { + storage = tableMetaStorageFactory.getTableMetaStorage(); + } + + tableMetaCache = new TableMetaCacheWithStorage(metaConnection, storage); ((LogEventConvert) binlogParser).setTableMetaCache(tableMetaCache); } } @@ -643,6 +653,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE throw new CanalParseException("command : 'show master status' has an error! pls check. you need (at least one of) the SUPER,REPLICATION CLIENT privilege(s) for this operation"); } EntryPosition endPosition = new EntryPosition(fields.get(0), Long.valueOf(fields.get(1))); + if (isGTIDMode && fields.size() > 4) { + endPosition.setGtid(fields.get(4)); + } return endPosition; } catch (IOException e) { throw new CanalParseException("command : 'show master status' has an error!", e); @@ -908,4 +921,11 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE this.dumpErrorCountThreshold = dumpErrorCountThreshold; } + public TableMetaStorageFactory getTableMetaStorageFactory() { + return tableMetaStorageFactory; + } + + public void setTableMetaStorageFactory(TableMetaStorageFactory tableMetaStorageFactory) { + this.tableMetaStorageFactory = tableMetaStorageFactory; + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 76c70723..e93a1d20 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -52,17 +52,17 @@ import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; */ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor { - private LogEventConvert logEventConvert; - private EventTransactionBuffer transactionBuffer; - private ErosaConnection connection; + private LogEventConvert logEventConvert; + private EventTransactionBuffer transactionBuffer; + private ErosaConnection connection; - private int parserThreadCount; - private int ringBufferSize; - private RingBuffer disruptorMsgBuffer; - private ExecutorService parserExecutor; - private ExecutorService stageExecutor; - private String destination; - private CanalParseException exception; + private int parserThreadCount; + private int ringBufferSize; + private RingBuffer disruptorMsgBuffer; + private ExecutorService parserExecutor; + private ExecutorService stageExecutor; + private String destination; + private volatile CanalParseException exception; public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, EventTransactionBuffer transactionBuffer, String destination){ @@ -135,22 +135,31 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement } catch (Throwable e) { // ignore } - disruptorMsgBuffer = null; super.stop(); } /** * 网络数据投递 */ - public void publish(LogBuffer buffer) { - publish(buffer, null); + public boolean publish(LogBuffer buffer) { + return publish(buffer, null); } - public void publish(LogBuffer buffer, String binlogFileName) { - if (!isStart() && exception != null) { - throw exception; + public boolean publish(LogBuffer buffer, String binlogFileName) { + if (!isStart()) { + if (exception != null) { + throw exception; + } + return false; } + /** + * 由于改为processor仅终止自身stage而不是stop,那么需要由incident标识coprocessor是否正常工作。 + * 让dump线程能够及时感知 + */ + if (exception != null) { + throw exception; + } boolean interupted = false; do { try { @@ -168,10 +177,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement interupted = Thread.interrupted(); } } while (!interupted && isStart()); - - if (exception != null) { - throw exception; - } + return isStart(); } @Override @@ -197,10 +203,15 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement try { LogBuffer buffer = event.getBuffer(); if (StringUtils.isNotEmpty(event.getBinlogFileName()) - && !context.getLogPosition().getFileName().equals(event.getBinlogFileName())) { + && (context.getLogPosition() == null + || !context.getLogPosition().getFileName().equals(event.getBinlogFileName()))) { // set roate binlog file name - context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition() - .getPosition())); + if (context.getLogPosition() == null){ + context.setLogPosition(new LogPosition(event.getBinlogFileName(), 0)); + }else{ + context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition() + .getPosition())); + } } LogEvent logEvent = decoder.decode(buffer, context); @@ -249,9 +260,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - if (isStart()) { - stop(); - } + } } @@ -287,9 +296,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - if (isStart()) { - stop(); - } + } } @@ -328,9 +335,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void onShutdown() { - if (isStart()) { - stop(); - } + } } @@ -397,7 +402,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement @Override public void handleEventException(final Throwable ex, final long sequence, final Object event) { - throw new RuntimeException(ex); } @Override diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java index a85962cc..222d762a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/DirectLogFetcher.java @@ -64,9 +64,6 @@ public class DirectLogFetcher extends LogFetcher { if ("1".equals(dbsemi)) { issemi = true; } - // 和mysql driver一样,提供buffer机制,提升读取binlog速度 - // this.input = new - // BufferedInputStream(channel.socket().getInputStream(), 16384); } /** diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 09af808f..72369f07 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -10,6 +10,9 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; @@ -87,7 +90,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private volatile AviaterRegexFilter nameFilter; // 运行时引用可能会有变化,比如规则发生变化时 private volatile AviaterRegexFilter nameBlackFilter; - private TableMetaCache tableMetaCache; + + private TableMetaCacheInterface tableMetaCache; + private String binlogFileName = "mysql-bin.000001"; + private Charset charset = Charset.defaultCharset(); private boolean filterQueryDcl = false; private boolean filterQueryDml = false; @@ -262,7 +268,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar if (!isSeek) { // 使用新的表结构元数据管理方式 EntryPosition position = createPosition(event.getHeader()); - tableMetaCache.apply(position, event.getDbName(), queryString, null); + String fulltbName = schemaName+"."+tableName; + tableMetaCache.apply(position, fulltbName, queryString, null); } Header header = createHeader(event.getHeader(), schemaName, tableName, type); @@ -933,7 +940,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar this.nameBlackFilter = nameBlackFilter; } - public void setTableMetaCache(TableMetaCache tableMetaCache) { + public void setTableMetaCache(TableMetaCacheInterface tableMetaCache) { this.tableMetaCache = tableMetaCache; } @@ -960,5 +967,4 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar public void setGtidSet(GTIDSet gtidSet) { this.gtidSet = gtidSet; } - } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java index 4c1cef3c..5c782aa1 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java @@ -6,6 +6,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; @@ -29,7 +30,7 @@ import com.google.common.cache.LoadingCache; * @author jianghang 2013-1-17 下午10:15:16 * @version 1.0.0 */ -public class TableMetaCache { +public class TableMetaCache implements TableMetaCacheInterface { public static final String COLUMN_NAME = "COLUMN_NAME"; public static final String COLUMN_TYPE = "COLUMN_TYPE"; @@ -99,6 +100,10 @@ public class TableMetaCache { String createDDL = packet.getFieldValues().get(1); MemoryTableMeta memoryTableMeta = new MemoryTableMeta(); memoryTableMeta.apply(DatabaseTableMeta.INIT_POSITION, schema, createDDL, null); + String[] strings = table.split("\\."); + if (strings.length > 1) { + table = strings[1]; + } TableMeta tableMeta = memoryTableMeta.find(schema, table); return tableMeta.getFields(); } else { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java new file mode 100644 index 00000000..d95c8af1 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java @@ -0,0 +1,260 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds; + +import java.io.*; +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; + +import io.netty.handler.codec.http.HttpResponseStatus; + +/** + * @author chengjin.lyf on 2018/8/7 下午3:10 + * @since 1.0.25 + */ +public class BinlogDownloadQueue { + + private static final Logger logger = LoggerFactory.getLogger(BinlogDownloadQueue.class); + private static final int TIMEOUT = 10000; + + private LinkedBlockingQueue downloadQueue = new LinkedBlockingQueue(); + private LinkedBlockingQueue taskQueue = new LinkedBlockingQueue(); + private LinkedList binlogList; + private final int batchSize; + private Thread downloadThread; + public boolean running = true; + private final String destDir; + private String hostId; + private int currentSize; + private String lastDownload; + + public BinlogDownloadQueue(List downloadQueue, int batchSize, String destDir) throws IOException { + this.binlogList = new LinkedList(downloadQueue); + this.batchSize = batchSize; + this.destDir = destDir; + this.currentSize = 0; + prepareBinlogList(); + cleanDir(); + } + + private void prepareBinlogList(){ + for (BinlogFile binlog : this.binlogList) { + String fileName = StringUtils.substringBetween(binlog.getDownloadLink(), "mysql-bin.", "?"); + binlog.setFileName(fileName); + } + Collections.sort(this.binlogList, new Comparator() { + @Override + public int compare(BinlogFile o1, BinlogFile o2) { + return o1.getFileName().compareTo(o2.getFileName()); + } + }); + } + + public void cleanDir() throws IOException { + File destDirFile = new File(destDir); + FileUtils.forceMkdir(destDirFile); + FileUtils.cleanDirectory(destDirFile); + } + + public void silenceDownload() { + if (downloadThread != null) { + return; + } + downloadThread = new Thread(new DownloadThread()); + downloadThread.start(); + } + + + public BinlogFile tryOne() throws IOException { + BinlogFile binlogFile = binlogList.poll(); + download(binlogFile); + hostId = binlogFile.getHostInstanceID(); + this.currentSize ++; + return binlogFile; + } + + public void notifyNotMatch(){ + this.currentSize --; + filter(hostId); + } + + private void filter(String hostInstanceId){ + Iterator it = binlogList.iterator(); + while (it.hasNext()){ + BinlogFile bf = it.next(); + if(bf.getHostInstanceID().equalsIgnoreCase(hostInstanceId)){ + it.remove(); + }else{ + hostId = bf.getHostInstanceID(); + } + } + } + + public boolean isLastFile(String fileName){ + String needCompareName = lastDownload; + if (StringUtils.isNotEmpty(needCompareName) && StringUtils.endsWith(needCompareName, "tar")){ + needCompareName = needCompareName.substring(0, needCompareName.indexOf(".")); + } + return fileName.equalsIgnoreCase(needCompareName) && binlogList.isEmpty(); + } + + public void prepare() throws InterruptedException { + for (int i = this.currentSize; i < batchSize && !binlogList.isEmpty(); i++) { + BinlogFile binlogFile = null; + while (!binlogList.isEmpty()){ + binlogFile = binlogList.poll(); + if (!binlogFile.getHostInstanceID().equalsIgnoreCase(hostId)){ + continue; + } + break; + } + if (binlogFile == null){ + break; + } + this.downloadQueue.put(binlogFile); + this.lastDownload = "mysql-bin." + binlogFile.getFileName(); + this.currentSize ++; + } + } + + public void downOne(){ + this.currentSize --; + } + + public void release(){ + running = false; + this.currentSize = 0; + binlogList.clear(); + downloadQueue.clear(); + } + + private void download(BinlogFile binlogFile) throws IOException { + String downloadLink = binlogFile.getDownloadLink(); + String fileName = binlogFile.getFileName(); + HttpGet httpGet = new HttpGet(downloadLink); + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setMaxConnPerRoute(50) + .setMaxConnTotal(100) + .build(); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .setSocketTimeout(TIMEOUT) + .build(); + httpGet.setConfig(requestConfig); + HttpResponse response = httpClient.execute(httpGet); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpResponseStatus.OK.code()) { + throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:" + + statusCode); + } + saveFile(new File(destDir), "mysql-bin." + fileName, response); + } + + private static void saveFile(File parentFile, String fileName, HttpResponse response) throws IOException { + InputStream is = response.getEntity().getContent(); + long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue()); + if(response.getFirstHeader("Content-Disposition")!=null){ + fileName = response.getFirstHeader("Content-Disposition").getValue(); + fileName = StringUtils.substringAfter(fileName, "filename="); + } + boolean isTar = StringUtils.endsWith(fileName, ".tar"); + FileUtils.forceMkdir(parentFile); + FileOutputStream fos = null; + try { + if (isTar) { + TarArchiveInputStream tais = new TarArchiveInputStream(is); + TarArchiveEntry tarArchiveEntry = null; + while ((tarArchiveEntry = tais.getNextTarEntry()) != null) { + String name = tarArchiveEntry.getName(); + File tarFile = new File(parentFile, name + ".tmp"); + logger.info("start to download file " + tarFile.getName()); + BufferedOutputStream bos = null; + try { + bos = new BufferedOutputStream(new FileOutputStream(tarFile)); + int read = -1; + byte[] buffer = new byte[1024]; + while ((read = tais.read(buffer)) != -1) { + bos.write(buffer, 0, read); + } + logger.info("download file " + tarFile.getName() + " end!"); + tarFile.renameTo(new File(parentFile, name)); + } finally { + IOUtils.closeQuietly(bos); + } + } + tais.close(); + } else { + File file = new File(parentFile, fileName + ".tmp"); + if (!file.isFile()) { + file.createNewFile(); + } + try { + fos = new FileOutputStream(file); + byte[] buffer = new byte[1024]; + int len; + long copySize = 0; + long nextPrintProgress = 0; + logger.info("start to download file " + file.getName()); + while ((len = is.read(buffer)) != -1) { + fos.write(buffer, 0, len); + copySize += len; + long progress = copySize * 100 / totalSize; + if (progress >= nextPrintProgress) { + logger.info("download " + file.getName() + " progress : " + progress + + "% , download size : " + copySize + ", total size : " + totalSize); + nextPrintProgress += 10; + } + } + logger.info("download file " + file.getName() + " end!"); + fos.flush(); + } finally { + IOUtils.closeQuietly(fos); + } + file.renameTo(new File(parentFile, fileName)); + } + } finally { + IOUtils.closeQuietly(fos); + } + } + + public void execute(Runnable runnable) throws InterruptedException { + taskQueue.put(runnable); + } + + private class DownloadThread implements Runnable { + + @Override + public void run() { + while (running) { + try { + BinlogFile binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS); + if (binlogFile != null){ + download(binlogFile); + } + Runnable runnable = taskQueue.poll(5000, TimeUnit.MILLISECONDS); + if (runnable != null){ + runnable.run(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + } + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java new file mode 100644 index 00000000..5bcc8688 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java @@ -0,0 +1,149 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; +import com.alibaba.otter.canal.parse.inbound.ParserExceptionHandler; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; + +/** + * @author chengjin.lyf on 2018/7/20 上午10:52 + * @since 1.0.25 + */ +public class RdsBinlogEventParserProxy extends MysqlEventParser { + + private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址 + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id + private Long startTime; + private Long endTime; + private String directory; //binlog 目录 + private int batchSize = 4; //最多下载的binlog文件数量 + + private RdsLocalBinlogEventParser rdsBinlogEventParser = new RdsLocalBinlogEventParser(); + private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "rds-binlog-daemon-thread"); + t.setDaemon(true); + return t; + } + }); + + @Override + public void start() { + final ParserExceptionHandler targetHandler = this.getParserExceptionHandler(); + rdsBinlogEventParser.setLogPositionManager(this.getLogPositionManager()); + rdsBinlogEventParser.setDestination(destination); + rdsBinlogEventParser.setAlarmHandler(this.getAlarmHandler()); + rdsBinlogEventParser.setConnectionCharset(this.connectionCharset); + rdsBinlogEventParser.setConnectionCharsetNumber(this.connectionCharsetNumber); + rdsBinlogEventParser.setEnableTsdb(this.enableTsdb); + rdsBinlogEventParser.setEventBlackFilter(this.eventBlackFilter); + rdsBinlogEventParser.setFilterQueryDcl(this.filterQueryDcl); + rdsBinlogEventParser.setFilterQueryDdl(this.filterQueryDdl); + rdsBinlogEventParser.setFilterQueryDml(this.filterQueryDml); + rdsBinlogEventParser.setFilterRows(this.filterRows); + rdsBinlogEventParser.setFilterTableError(this.filterTableError); + rdsBinlogEventParser.setIsGTIDMode(this.isGTIDMode); + rdsBinlogEventParser.setMasterInfo(this.masterInfo); + rdsBinlogEventParser.setEventFilter(this.eventFilter); + rdsBinlogEventParser.setMasterPosition(this.masterPosition); + rdsBinlogEventParser.setTransactionSize(this.transactionSize); + rdsBinlogEventParser.setUrl(this.rdsOpenApiUrl); + rdsBinlogEventParser.setAccesskey(this.accesskey); + rdsBinlogEventParser.setSecretkey(this.secretkey); + rdsBinlogEventParser.setInstanceId(this.instanceId); + rdsBinlogEventParser.setEventSink(eventSink); + rdsBinlogEventParser.setDirectory(directory); + rdsBinlogEventParser.setBatchSize(batchSize); + rdsBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() { + @Override + public void onFinish() { + executorService.execute(new Runnable() { + @Override + public void run() { + rdsBinlogEventParser.stop(); + RdsBinlogEventParserProxy.this.start(); + } + }); + + } + }); + this.setParserExceptionHandler(new ParserExceptionHandler() { + + @Override + public void handle(Throwable e) { + handleMysqlParserException(e); + if (targetHandler != null) { + targetHandler.handle(e); + } + } + }); + super.start(); + } + + public void handleMysqlParserException(Throwable throwable) { + if (throwable instanceof PositionNotFoundException) { + logger.info("remove rds not found position, try download rds binlog!"); + executorService.execute(new Runnable() { + + @Override + public void run() { + try { + logger.info("stop mysql parser!"); + RdsBinlogEventParserProxy rdsBinlogEventParserProxy = RdsBinlogEventParserProxy.this; + long serverId = rdsBinlogEventParserProxy.getServerId(); + rdsBinlogEventParser.setServerId(serverId); + rdsBinlogEventParserProxy.stop(); + logger.info("start rds mysql binlog parser!"); + rdsBinlogEventParser.start(); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + } + + @Override + public void stop() { + super.stop(); + } + + @Override + public boolean isStart() { + return super.isStart(); + } + + public void setRdsOpenApiUrl(String rdsOpenApiUrl) { + this.rdsOpenApiUrl = rdsOpenApiUrl; + } + + + public void setAccesskey(String accesskey) { + this.accesskey = accesskey; + } + + + public void setSecretkey(String secretkey) { + this.secretkey = secretkey; + } + + + public void setInstanceId(String instanceId) { + this.instanceId = instanceId; + } + + public void setDirectory(String directory) { + this.directory = directory; + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java index eb638739..dced6d8d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java @@ -1,5 +1,9 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsItem; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFilesRequest; import io.netty.handler.codec.http.HttpResponseStatus; import java.io.BufferedOutputStream; @@ -9,16 +13,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URLEncoder; import java.text.SimpleDateFormat; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import java.util.TreeMap; -import java.util.UUID; +import java.util.*; import javax.crypto.Mac; import javax.crypto.SecretKey; @@ -56,6 +55,52 @@ public class RdsBinlogOpenApi { private static final String API_VERSION = "2014-08-15"; private static final String SIGNATURE_VERSION = "1.0"; + + public static List listBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, + Date endTime) { + DescribeBinlogFilesRequest request = new DescribeBinlogFilesRequest(); + if (StringUtils.isNotEmpty(url)){ + try { + URI uri = new URI(url); + request.setEndPoint(uri.getHost()); + } catch (URISyntaxException e) { + logger.error("resolve url host failed, will use default rds endpoint!"); + } + } + request.setStartDate(startTime); + request.setEndDate(endTime); + request.setPageNumber(1); + request.setPageSize(100); + request.setRdsInstanceId(dbInstanceId); + request.setAccessKeyId(ak); + request.setAccessKeySecret(sk); + DescribeBinlogFileResult result = null; + int retryTime = 3; + while (true){ + try{ + result = request.doAction(); + break; + }catch (Exception e){ + if(retryTime-- <= 0){ + throw new RuntimeException(e); + } + try { + Thread.sleep(100L); + } catch (InterruptedException e1) { + } + } + } + if (result == null){ + return Collections.EMPTY_LIST; + } + RdsItem rdsItem = result.getItems(); + if (rdsItem != null){ + return rdsItem.getBinLogFile(); + } + return Collections.EMPTY_LIST; + } + + public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, Date endTime, File destDir) throws Throwable { int pageSize = 100; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java index 68be38e5..40cb3b13 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java @@ -2,14 +2,23 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds; import java.io.File; import java.util.Date; +import java.util.List; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.math.NumberUtils; import org.springframework.util.Assert; import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.exception.CanalParseException; +import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; +import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException; +import com.alibaba.otter.canal.parse.inbound.ErosaConnection; +import com.alibaba.otter.canal.parse.inbound.ParserExceptionHandler; +import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinLogConnection; import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; import com.alibaba.otter.canal.protocol.position.EntryPosition; +import com.alibaba.otter.canal.protocol.position.LogPosition; /** * 基于rds binlog备份文件的复制 @@ -17,47 +26,102 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition; * @author agapple 2017年10月15日 下午1:27:36 * @since 1.0.25 */ -public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser { +public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser, LocalBinLogConnection.FileParserListener { private String url = "https://rds.aliyuncs.com/"; // openapi地址 - private String accesskey; // 云账号的ak - private String secretkey; // 云账号sk - private String instanceId; // rds实例id - private Long startTime; - private Long endTime; + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id + private Long startTime; + private Long endTime; + private BinlogDownloadQueue binlogDownloadQueue; + private ParseFinishListener finishListener; + private int batchSize; public RdsLocalBinlogEventParser(){ } public void start() throws CanalParseException { try { - Assert.notNull(startTime); Assert.notNull(accesskey); Assert.notNull(secretkey); Assert.notNull(instanceId); Assert.notNull(url); + Assert.notNull(directory); + if (endTime == null) { endTime = System.currentTimeMillis(); } - RdsBinlogOpenApi.downloadBinlogFiles(url, - accesskey, + EntryPosition entryPosition = findStartPosition(null); + if (entryPosition == null) { + throw new PositionNotFoundException("position not found!"); + } + long startTimeInMill = entryPosition.getTimestamp(); + startTime = startTimeInMill; + List binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, accesskey, secretkey, instanceId, new Date(startTime), - new Date(endTime), - new File(directory)); - - // 更新一下时间戳 - masterPosition = new EntryPosition(startTime); + new Date(endTime)); + binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchSize, directory); + binlogDownloadQueue.silenceDownload(); + needWait = true; + parallel = false; + // try to download one file,use to test server id + binlogDownloadQueue.tryOne(); } catch (Throwable e) { logger.error("download binlog failed", e); throw new CanalParseException(e); } + setParserExceptionHandler(new ParserExceptionHandler() { + @Override + public void handle(Throwable e) { + handleMysqlParserException(e); + } + }); super.start(); } + private void handleMysqlParserException(Throwable throwable) { + if (throwable instanceof ServerIdNotMatchException) { + logger.error("server id not match, try download another rds binlog!"); + binlogDownloadQueue.notifyNotMatch(); + try { + binlogDownloadQueue.cleanDir(); + binlogDownloadQueue.prepare(); + } catch (Exception e) { + throw new RuntimeException(e); + } + try { + binlogDownloadQueue.execute(new Runnable() { + + @Override + public void run() { + RdsLocalBinlogEventParser.super.stop(); + RdsLocalBinlogEventParser.super.start(); + } + }); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + } + } + + @Override + protected ErosaConnection buildErosaConnection() { + ErosaConnection connection = super.buildErosaConnection(); + if (connection instanceof LocalBinLogConnection) { + LocalBinLogConnection localBinLogConnection = (LocalBinLogConnection) connection; + localBinLogConnection.setNeedWait(true); + localBinLogConnection.setServerId(serverId); + localBinLogConnection.setParserListener(this); + } + return connection; + } + public String getUrl() { return url; } @@ -68,44 +132,81 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements } } - public String getAccesskey() { - return accesskey; - } public void setAccesskey(String accesskey) { this.accesskey = accesskey; } - public String getSecretkey() { - return secretkey; - } public void setSecretkey(String secretkey) { this.secretkey = secretkey; } - public String getInstanceId() { - return instanceId; - } public void setInstanceId(String instanceId) { this.instanceId = instanceId; } - public Long getStartTime() { - return startTime; - } public void setStartTime(Long startTime) { this.startTime = startTime; } - public Long getEndTime() { - return endTime; - } public void setEndTime(Long endTime) { this.endTime = endTime; } + @Override + public void onFinish(String fileName) { + try { + binlogDownloadQueue.downOne(); + File needDeleteFile = new File(directory + File.separator + fileName); + if (needDeleteFile.exists()){ + needDeleteFile.delete(); + } + // 处理下logManager位点问题 + LogPosition logPosition = logPositionManager.getLatestIndexBy(destination); + EntryPosition position = logPosition.getPostion(); + if (position != null){ + LogPosition newLogPosition = new LogPosition(); + String journalName = position.getJournalName(); + int sepIdx = journalName.indexOf("."); + String fileIndex = journalName.substring(sepIdx+1); + int index = NumberUtils.toInt(fileIndex) + 1; + String newJournalName = journalName.substring(0, sepIdx) + "." + StringUtils.leftPad(String.valueOf(index), fileIndex.length(), "0"); + newLogPosition.setPostion(new EntryPosition(newJournalName, 4L, position.getTimestamp(), position.getServerId())); + newLogPosition.setIdentity(logPosition.getIdentity()); + logPositionManager.persistLogPosition(destination, newLogPosition); + } + + if (binlogDownloadQueue.isLastFile(fileName)) { + logger.info("all file parse complete, switch to mysql parser!"); + finishListener.onFinish(); + return; + } + binlogDownloadQueue.prepare(); + } catch (Exception e) { + logger.error("prepare download binlog file failed!", e); + throw new RuntimeException(e); + } + } + + @Override + public void stop() { + this.binlogDownloadQueue.release(); + super.stop(); + } + + public void setFinishListener(ParseFinishListener finishListener) { + this.finishListener = finishListener; + } + + public interface ParseFinishListener{ + void onFinish(); + } + + public void setBatchSize(int batchSize) { + this.batchSize = batchSize; + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java new file mode 100644 index 00000000..e6b354d7 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java @@ -0,0 +1,72 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; + +/** + * @author chengjin.lyf on 2018/8/7 下午2:26 + * @since 1.0.25 + */ +public class BinlogFile { + + private Long FileSize; + private String LogBeginTime; + private String LogEndTime; + private String DownloadLink; + private String HostInstanceID; + private String LinkExpiredTime; + private String fileName; + + public Long getFileSize() { + return FileSize; + } + + public void setFileSize(Long fileSize) { + FileSize = fileSize; + } + + public String getLogBeginTime() { + return LogBeginTime; + } + + public void setLogBeginTime(String logBeginTime) { + LogBeginTime = logBeginTime; + } + + public String getLogEndTime() { + return LogEndTime; + } + + public void setLogEndTime(String logEndTime) { + LogEndTime = logEndTime; + } + + public String getDownloadLink() { + return DownloadLink; + } + + public void setDownloadLink(String downloadLink) { + DownloadLink = downloadLink; + } + + public String getHostInstanceID() { + return HostInstanceID; + } + + public void setHostInstanceID(String hostInstanceID) { + HostInstanceID = hostInstanceID; + } + + public String getLinkExpiredTime() { + return LinkExpiredTime; + } + + public void setLinkExpiredTime(String linkExpiredTime) { + LinkExpiredTime = linkExpiredTime; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java new file mode 100644 index 00000000..d8d54e44 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java @@ -0,0 +1,62 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; + +/** + * @author chengjin.lyf on 2018/8/7 下午2:26 + * @since 1.0.25 + */ +public class DescribeBinlogFileResult { + private RdsItem Items; + private long PageNumber; + private long TotalRecordCount; + private long TotalFileSize; + private String RequestId; + private long PageRecordCount; + + public RdsItem getItems() { + return Items; + } + + public void setItems(RdsItem items) { + Items = items; + } + + public long getPageNumber() { + return PageNumber; + } + + public void setPageNumber(long pageNumber) { + PageNumber = pageNumber; + } + + public long getTotalRecordCount() { + return TotalRecordCount; + } + + public void setTotalRecordCount(long totalRecordCount) { + TotalRecordCount = totalRecordCount; + } + + public long getTotalFileSize() { + return TotalFileSize; + } + + public void setTotalFileSize(long totalFileSize) { + TotalFileSize = totalFileSize; + } + + public String getRequestId() { + return RequestId; + } + + public void setRequestId(String requestId) { + RequestId = requestId; + } + + public long getPageRecordCount() { + return PageRecordCount; + } + + public void setPageRecordCount(long pageRecordCount) { + PageRecordCount = pageRecordCount; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java new file mode 100644 index 00000000..b6ca7c53 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java @@ -0,0 +1,69 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; + +/** + * @author chengjin.lyf on 2018/8/7 下午2:26 + * @since 1.0.25 + */ +public class RdsBackupPolicy { + + /** + * 数据备份保留天数(7到730天)。 + */ + private String BackupRetentionPeriod; + /** + * 数据备份时间,格式:HH:mmZ- HH:mm Z。 + */ + private String PreferredBackupTime; + /** + * 数据备份周期。Monday:周一;Tuesday:周二;Wednesday:周三;Thursday:周四;Friday:周五;Saturday:周六;Sunday:周日。 + */ + private String PreferredBackupPeriod; + /** + * 日志备份状态。Enable:开启;Disabled:关闭。 + */ + private boolean BackupLog; + /** + * 日志备份保留天数(7到730天)。 + */ + private int LogBackupRetentionPeriod; + + public String getBackupRetentionPeriod() { + return BackupRetentionPeriod; + } + + public void setBackupRetentionPeriod(String backupRetentionPeriod) { + BackupRetentionPeriod = backupRetentionPeriod; + } + + public String getPreferredBackupTime() { + return PreferredBackupTime; + } + + public void setPreferredBackupTime(String preferredBackupTime) { + PreferredBackupTime = preferredBackupTime; + } + + public String getPreferredBackupPeriod() { + return PreferredBackupPeriod; + } + + public void setPreferredBackupPeriod(String preferredBackupPeriod) { + PreferredBackupPeriod = preferredBackupPeriod; + } + + public boolean isBackupLog() { + return BackupLog; + } + + public void setBackupLog(boolean backupLog) { + BackupLog = backupLog; + } + + public int getLogBackupRetentionPeriod() { + return LogBackupRetentionPeriod; + } + + public void setLogBackupRetentionPeriod(int logBackupRetentionPeriod) { + LogBackupRetentionPeriod = logBackupRetentionPeriod; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java new file mode 100644 index 00000000..a2cfaffe --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java @@ -0,0 +1,19 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; + +import java.util.List; + +/** + * @author chengjin.lyf on 2018/8/7 下午2:26 + * @since 1.0.25 + */ +public class RdsItem { + private List BinLogFile; + + public List getBinLogFile() { + return BinLogFile; + } + + public void setBinLogFile(List binLogFile) { + BinLogFile = binLogFile; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java new file mode 100644 index 00000000..976968f6 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java @@ -0,0 +1,250 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.TimeUnit; + +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.SSLContext; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContexts; +import org.apache.http.util.EntityUtils; + +import io.netty.handler.codec.http.HttpResponseStatus; + +/** + * @author chengjin.lyf on 2018/8/7 下午2:26 + * @since 1.0.25 + */ +public abstract class AbstractRequest { + + /** + * 要求的编码格式 + */ + private static final String ENCODING = "UTF-8"; + /** + * 要求的sign签名算法 + */ + private static final String MAC_NAME = "HmacSHA1"; + + private String accessKeyId; + + private String accessKeySecret; + + /** + * api 版本 + * + */ + private String version; + + private String endPoint = "rds.aliyuncs.com"; + + private String protocol = "http"; + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + private int timeout = (int) TimeUnit.MINUTES.toMillis(1); + + + private Map treeMap = new TreeMap(); + + public void putQueryString(String name, String value){ + if (StringUtils.isBlank(name) || StringUtils.isBlank(value)){ + return; + } + treeMap.put(name, value); + } + + + public void setVersion(String version) { + this.version = version; + } + + + public void setEndPoint(String endPoint) { + this.endPoint = endPoint; + } + + public void setAccessKeyId(String accessKeyId) { + this.accessKeyId = accessKeyId; + } + + public void setAccessKeySecret(String accessKeySecret) { + this.accessKeySecret = accessKeySecret; + } + + /** + * 使用 HMAC-SHA1 签名方法对对encryptText进行签名 + * + * @param encryptText 被签名的字符串 + * @param encryptKey 密钥 + * @return + * @throws Exception + */ + private byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { + byte[] data = encryptKey.getBytes(ENCODING); + // 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称 + SecretKey secretKey = new SecretKeySpec(data, MAC_NAME); + // 生成一个指定 Mac 算法 的 Mac 对象 + Mac mac = Mac.getInstance(MAC_NAME); + // 用给定密钥初始化 Mac 对象 + mac.init(secretKey); + + byte[] text = encryptText.getBytes(ENCODING); + // 完成 Mac 操作 + return mac.doFinal(text); + } + + private String base64(byte input[]) throws UnsupportedEncodingException { + return new String(Base64.encodeBase64(input), ENCODING); + } + + private String concatQueryString(Map parameters) throws UnsupportedEncodingException { + if (null == parameters) { + return null; + } + StringBuilder urlBuilder = new StringBuilder(""); + for (Map.Entry entry : parameters.entrySet()) { + String key = entry.getKey(); + String val = entry.getValue(); + urlBuilder.append(encode(key)); + if (val != null) { + urlBuilder.append("=").append(encode(val)); + } + urlBuilder.append("&"); + } + int strIndex = urlBuilder.length(); + if (parameters.size() > 0) { + urlBuilder.deleteCharAt(strIndex - 1); + } + return urlBuilder.toString(); + } + + private String encode(String value) throws UnsupportedEncodingException { + return URLEncoder.encode(value, "UTF-8"); + } + + private String makeSignature(TreeMap paramMap) throws Exception { + String cqs = concatQueryString(paramMap); + cqs = encode(cqs); + cqs = cqs.replaceAll("\\+", "%20"); + cqs = cqs.replaceAll("\\*", "%2A"); + cqs = cqs.replaceAll("%7E", "~"); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("GET").append("&").append(encode("/")).append("&").append(cqs); + return base64(HmacSHA1Encrypt(stringBuilder.toString(), accessKeySecret + "&")); + } + + public final String formatUTCTZ(Date date) { + SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss'Z'"); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(date); + } + + private void fillCommonParam(Map p) { + p.put("Format", "JSON"); + p.put("Version", version); + p.put("AccessKeyId", accessKeyId); + p.put("SignatureMethod", "HMAC-SHA1"); //此处不能用变量 MAC_NAME + p.put("Timestamp", formatUTCTZ(new Date())); + p.put("SignatureVersion", "1.0"); + p.put("SignatureNonce", UUID.randomUUID().toString()); + } + + private String makeRequestString(Map param) throws Exception { + fillCommonParam(param); + String sign = makeSignature(new TreeMap(param)); + StringBuilder builder = new StringBuilder(); + for (Map.Entry entry : param.entrySet()) { + builder.append(encode(entry.getKey())).append("=").append(encode(entry.getValue())).append("&"); + } + builder.append("Signature").append("=").append(sign); + return builder.toString(); + } + + /** + * 执行http请求 + * + * @param getMethod + * @return + * @throws IOException + */ + private final HttpResponse executeHttpRequest(HttpGet getMethod, String host) throws Exception { + SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() { + + @Override + public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + return true; + } + }).build(); + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, + new String[] { "TLSv1" }, + null, + SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + Registry registry = RegistryBuilder.create() + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", sslsf) + .build(); + HttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry); + CloseableHttpClient httpClient = HttpClientBuilder.create() + .setMaxConnPerRoute(50) + .setMaxConnTotal(100) + .setConnectionManager(httpClientConnectionManager) + .build(); + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(timeout) + .setConnectionRequestTimeout(timeout) + .setSocketTimeout(timeout) + .build(); + getMethod.setConfig(requestConfig); + HttpResponse response = httpClient.execute(getMethod); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpResponseStatus.OK.code() && statusCode != HttpResponseStatus.PARTIAL_CONTENT.code()) { + String result = EntityUtils.toString(response.getEntity()); + throw new RuntimeException("return error !" + response.getStatusLine().getReasonPhrase() + ", " + result); + } + return response; + } + + protected abstract T processResult(HttpResponse response) throws Exception; + + protected void processBefore(){ + + } + + public final T doAction() throws Exception { + processBefore(); + String requestStr = makeRequestString(treeMap); + HttpGet httpGet = new HttpGet(protocol + "://" +endPoint + "?" + requestStr); + HttpResponse response = executeHttpRequest(httpGet, endPoint); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + String result = EntityUtils.toString(response.getEntity()); + throw new RuntimeException("http request failed! " + result); + } + return processResult(response); + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java new file mode 100644 index 00000000..0fbdfc47 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java @@ -0,0 +1,41 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; + +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; + +/** + * rds 备份策略查询 + * @author chengjin.lyf on 2018/8/7 下午3:41 + * @since 1.0.25 + */ +public class DescribeBackupPolicyRequest extends AbstractRequest { + + + public DescribeBackupPolicyRequest() { + setVersion("2014-08-15"); + putQueryString("Action", "DescribeBackupPolicy"); + + } + + + public void setRdsInstanceId(String rdsInstanceId) { + putQueryString("DBInstanceId", rdsInstanceId); + } + + @Override + protected RdsBackupPolicy processResult(HttpResponse response) throws Exception { + String result = EntityUtils.toString(response.getEntity()); + JSONObject jsonObj = JSON.parseObject(result); + RdsBackupPolicy policy = new RdsBackupPolicy(); + policy.setBackupRetentionPeriod(jsonObj.getString("BackupRetentionPeriod")); + policy.setBackupLog(jsonObj.getString("BackupLog").equalsIgnoreCase("Enable")); + policy.setLogBackupRetentionPeriod(jsonObj.getIntValue("LogBackupRetentionPeriod")); + policy.setPreferredBackupPeriod(jsonObj.getString("PreferredBackupPeriod")); + policy.setPreferredBackupTime(jsonObj.getString("PreferredBackupTime")); + return policy; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java new file mode 100644 index 00000000..1c91c255 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java @@ -0,0 +1,56 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; + +import java.util.Date; + +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; +import org.apache.http.HttpResponse; +import org.apache.http.util.EntityUtils; + +import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.TypeReference; + +/** + * @author chengjin.lyf on 2018/8/7 下午3:41 + * @since 1.0.25 + */ +public class DescribeBinlogFilesRequest extends AbstractRequest { + + + public DescribeBinlogFilesRequest() { + setVersion("2014-08-15"); + putQueryString("Action", "DescribeBinlogFiles"); + + } + + public void setRdsInstanceId(String rdsInstanceId) { + putQueryString("DBInstanceId", rdsInstanceId); + } + + public void setPageSize(int pageSize) { + putQueryString("PageSize", String.valueOf(pageSize)); + } + + public void setPageNumber(int pageNumber) { + putQueryString("PageNumber", String.valueOf(pageNumber)); + } + + public void setStartDate(Date startDate) { + putQueryString("StartTime" , formatUTCTZ(startDate)); + } + + public void setEndDate(Date endDate) { + putQueryString("EndTime" , formatUTCTZ(endDate)); + } + + public void setResourceOwnerId(Long resourceOwnerId) { + putQueryString("ResourceOwnerId", String.valueOf(resourceOwnerId)); + } + + @Override + protected DescribeBinlogFileResult processResult(HttpResponse response) throws Exception { + String result = EntityUtils.toString(response.getEntity()); + DescribeBinlogFileResult describeBinlogFileResult = JSONObject.parseObject(result, new TypeReference() { + }); + return describeBinlogFileResult; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java new file mode 100644 index 00000000..0964627d --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java @@ -0,0 +1,191 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.CacheConnectionNull; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; + +import java.io.IOException; +import java.util.*; + +public class HistoryTableMetaCache { + private TableMetaStorage tableMetaStorage; + private MysqlConnection metaConnection; + private LoadingCache> cache; // 第一层:数据库名.表名,第二层时间戳,TableMeta + + public HistoryTableMetaCache() { + cache = CacheBuilder.newBuilder().build(new CacheLoader>() { + @Override + public Map load(String tableName) throws Exception { + Long timestamp = new Date().getTime(); + String[] strs = tableName.split("\\."); + String schema = strs[0]; + if (tableMetaStorage != null) { + init(tableMetaStorage.fetchByTableName(tableName)); // 从存储中读取表的历史ddl + } + ResultSetPacket resultSetPacket = connectionQuery("show create table " + tableName); // 获取当前ddl + String currentDdl = resultSetPacket.getFieldValues().get(1); + if (cache.asMap().containsKey(tableName)) { + Map tableMetaMap = cache.getUnchecked(tableName); + if (tableMetaMap.isEmpty()) { + put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema,取时间为当前时间-1s + } else { // 如果table存在历史 + Iterator iterator = tableMetaMap.keySet().iterator(); + Long firstTimestamp = iterator.next(); + TableMeta first = tableMetaMap.get(firstTimestamp); // 拿第一条ddl + if (!first.getDdl().equalsIgnoreCase(currentDdl)) { // 当前ddl与历史第一条不一致,放入当前ddl + put(schema, tableName, currentDdl, calculateNewTimestamp(firstTimestamp)); // 计算放入的timestamp,设为第一条时间+1s + } + } + } else { + put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema + } + return cache.get(tableName); + } + }); + } + + public void init(List entries) throws IOException { + if (entries == null) { + return; + } + for (TableMetaEntry entry : entries) { + try { + put(entry.getSchema(), entry.getTable(), entry.getDdl(), entry.getTimestamp()); + } catch (CacheConnectionNull cacheConnectionNull) { + cacheConnectionNull.printStackTrace(); + } + } + } + + public TableMeta put(String schema, String table, String ddl, Long timestamp) throws CacheConnectionNull, IOException { + ResultSetPacket resultSetPacket; + if (!(ddl.contains("CREATE TABLE") || ddl.contains("create table"))) { // 尝试直接从数据库拉取CREATE TABLE的DDL + resultSetPacket = connectionQuery("show create table " + table); + ddl = resultSetPacket.getFieldValues().get(1); + } else { // CREATE TABLE 的 DDL + resultSetPacket = new ResultSetPacket(); + List fields = new ArrayList(); + String[] strings = table.split("\\."); + String shortTable = table; + if (strings.length > 1) { + shortTable = strings[1]; + } + fields.add(0, shortTable); + fields.add(1, ddl); + resultSetPacket.setFieldValues(fields); + if (metaConnection != null) { + resultSetPacket.setSourceAddress(metaConnection.getAddress()); + } + } + Map tableMetaMap; + if (!cache.asMap().containsKey(table)) { + tableMetaMap = new TreeMap(new Comparator() { + @Override + public int compare(Long o1, Long o2) { + return o2.compareTo(o1); + } + }); + cache.put(table, tableMetaMap); + } else { + tableMetaMap = cache.getUnchecked(table); + } + eliminate(tableMetaMap); // 淘汰旧的TableMeta + TableMeta tableMeta = new TableMeta(schema, table, TableMetaCache.parseTableMeta(schema, table, resultSetPacket)); + if (tableMeta.getDdl() == null) { // 生成的TableMeta有时DDL为null + tableMeta.setDdl(ddl); + } + tableMetaMap.put(timestamp, tableMeta); + return tableMeta; + } + + public TableMeta get(String schema, String table, Long timestamp) throws NoHistoryException, CacheConnectionNull { + Map tableMetaMap = cache.getUnchecked(table); + Iterator iterator = tableMetaMap.keySet().iterator(); + Long selected = null; + while(iterator.hasNext()) { + Long temp = iterator.next(); + if (timestamp > temp) { + selected = temp; + break; + } + } + + if (selected == null) { + iterator = tableMetaMap.keySet().iterator(); + if (iterator.hasNext()) { + selected = iterator.next(); + } else { + throw new NoHistoryException(schema, table); + } + } + + return tableMetaMap.get(selected); + } + + public void clearTableMeta() { + cache.invalidateAll(); + } + + public void clearTableMetaWithSchemaName(String schema) { + for (String tableName : cache.asMap().keySet()) { + String[] strs = tableName.split("\\."); + if (schema.equalsIgnoreCase(strs[0])) { + cache.invalidate(tableName); + } + } + } + + public void clearTableMeta(String schema, String table) { + if (!table.contains(".")) { + table = schema+"."+table; + } + cache.invalidate(table); + } + + // eliminate older table meta in cache + private void eliminate(Map tableMetaMap) { + int MAX_CAPABILITY = 20; + if (tableMetaMap.keySet().size() < MAX_CAPABILITY) { + return; + } + Iterator iterator = tableMetaMap.keySet().iterator(); + while(iterator.hasNext()) { + iterator.next(); + } + iterator.remove(); + } + + private Long calculateNewTimestamp(Long oldTimestamp) { + return oldTimestamp + 1000; + } + + private ResultSetPacket connectionQuery(String query) throws CacheConnectionNull, IOException { + if (metaConnection == null) { + throw new CacheConnectionNull(); + } + try { + return metaConnection.query(query); + } catch (IOException e) { + try { + metaConnection.reconnect(); + return metaConnection.query(query); + } catch (IOException e1) { + throw e1; + } + } + } + + public void setMetaConnection(MysqlConnection metaConnection) { + this.metaConnection = metaConnection; + } + + public void setTableMetaStorage(TableMetaStorage tableMetaStorage) { + this.tableMetaStorage = tableMetaStorage; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java new file mode 100644 index 00000000..5196ee08 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java @@ -0,0 +1,20 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.protocol.position.EntryPosition; + +public interface TableMetaCacheInterface { + + TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position); + + void clearTableMeta(); + + void clearTableMetaWithSchemaName(String schema); + + void clearTableMeta(String schema, String table); + + boolean apply(EntryPosition position, String schema, String ddl, String extra); + + boolean isOnRDS(); + +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java new file mode 100644 index 00000000..eb43158b --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java @@ -0,0 +1,105 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; + +public class TableMetaCacheWithStorage implements TableMetaCacheInterface { + + private static Logger logger = LoggerFactory.getLogger(TableMetaCacheWithStorage.class); + private TableMetaStorage tableMetaStorage; // TableMeta存储 + private HistoryTableMetaCache cache = new HistoryTableMetaCache(); // cache + + public TableMetaCacheWithStorage(MysqlConnection con, TableMetaStorage tableMetaStorage) { + this.tableMetaStorage = tableMetaStorage; + InetSocketAddress address = con.getAddress(); + this.tableMetaStorage.setDbAddress(address.getHostName()+":"+address.getPort()); + cache.setMetaConnection(con); + cache.setTableMetaStorage(tableMetaStorage); + if (tableMetaStorage != null) { + try { + cache.init(tableMetaStorage.fetch()); // 初始化,从存储拉取TableMeta + } catch (IOException e) { + logger.error(e.getMessage()); + } + } + } + + @Override + public boolean apply(EntryPosition position, String fullTableName, String ddl, String extra) { + String[] strs = fullTableName.split("\\."); + String schema = strs[0]; + if (schema.equalsIgnoreCase("null")) { // ddl schema为null,放弃处理 + return false; + } + try { + TableMeta tableMeta = cache.get(schema, fullTableName, position.getTimestamp()); + if (!compare(tableMeta, ddl)) { // 获取最近的TableMeta,进行比对 + TableMeta result = cache.put(schema, fullTableName, ddl, calTimestamp(position.getTimestamp())); + if (tableMetaStorage != null && result != null) { // 储存 + tableMetaStorage.store(schema, fullTableName, result.getDdl(), calTimestamp(position.getTimestamp())); + } + } + return true; + } catch (Exception e) { + logger.error(e.toString()); + } + + return false; + } + + @Override + public boolean isOnRDS() { + return false; + } + + /*** + * + * @param schema dbname + * @param table tablename + * @param useCache unused + * @param position timestamp + * @return + */ + @Override + public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) { + String fulltbName = schema + "." + table; + try { + return cache.get(schema, fulltbName, position.getTimestamp()); + } catch (Exception e) { + logger.error(e.toString()); + } + return null; + } + + @Override + public void clearTableMeta() { + cache.clearTableMeta(); + } + + @Override + public void clearTableMetaWithSchemaName(String schema) { + cache.clearTableMetaWithSchemaName(schema); + } + + @Override + public void clearTableMeta(String schema, String table) { + cache.clearTableMeta(schema, table); + } + + private boolean compare(TableMeta tableMeta, String ddl) { + if (tableMeta == null) { + return false; + } + return tableMeta.getDdl().equalsIgnoreCase(ddl); + } + + private Long calTimestamp(Long timestamp) { + return timestamp; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java new file mode 100644 index 00000000..03e63ed7 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java @@ -0,0 +1,55 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import java.io.Serializable; + +public class TableMetaEntry implements Serializable { + + private static final long serialVersionUID = -1350200637109107904L; + + private String dbAddress; + private String schema; + private String table; + private String ddl; + private Long timestamp; + + + public String getSchema() { + return schema; + } + + public void setSchema(String schema) { + this.schema = schema; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public String getDdl() { + return ddl; + } + + public void setDdl(String ddl) { + this.ddl = ddl; + } + + public Long getTimestamp() { + return timestamp; + } + + public void setTimestamp(Long timestamp) { + this.timestamp = timestamp; + } + + public String getDbAddress() { + return dbAddress; + } + + public void setDbAddress(String dbAddress) { + this.dbAddress = dbAddress; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java new file mode 100644 index 00000000..ac9d7a0a --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java @@ -0,0 +1,18 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import java.util.List; + +public interface TableMetaStorage { + + void store(String schema, String table, String ddl, Long timestamp); + + List fetch(); + + List fetchByTableName(String tableName); + + String getDbName(); + + String getDbAddress(); + + void setDbAddress(String address); +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java new file mode 100644 index 00000000..0e532ebb --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java @@ -0,0 +1,9 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +public interface TableMetaStorageFactory { + + TableMetaStorage getTableMetaStorage(); + + String getDbName(); + +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java new file mode 100644 index 00000000..1dadd78a --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java @@ -0,0 +1,9 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; + +public class CacheConnectionNull extends Exception{ + + @Override + public String toString() { + return "CacheConnectionNull"; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java new file mode 100644 index 00000000..d8f1d16b --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java @@ -0,0 +1,21 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; + +public class NoHistoryException extends Exception{ + + private String dbName; + private String tbName; + + public NoHistoryException(String dbName, String tbName) { + this.dbName = dbName; + this.tbName = tbName; + } + + public void printTableName() { + System.out.println(dbName+"."+tbName); + } + + @Override + public String toString() { + return "NioHistoryException: " + dbName + " " + tbName; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java new file mode 100644 index 00000000..661986a1 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java @@ -0,0 +1,14 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; + +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; + +import java.util.List; + +public interface MySqlTableMetaCallback { + + void save(String dbAddress, String schema, String table,String ddl, Long timestamp); + + List fetch(String dbAddress, String dbName); + + List fetch(String dbAddress, String dbName, String tableName); +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java new file mode 100644 index 00000000..f120ef80 --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java @@ -0,0 +1,48 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; + +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; + +import java.util.List; + +public class MySqlTableMetaStorage implements TableMetaStorage { + private MySqlTableMetaCallback mySqlTableMetaCallback; + private String dbName; + private String dbAddress; + + MySqlTableMetaStorage(MySqlTableMetaCallback callback, String dbName) { + mySqlTableMetaCallback = callback; + this.dbName = dbName; + } + + + @Override + public void store(String schema, String table, String ddl, Long timestamp) { + mySqlTableMetaCallback.save(dbAddress, schema, table, ddl, timestamp); + } + + @Override + public List fetch() { + return mySqlTableMetaCallback.fetch(dbAddress, dbName); + } + + @Override + public List fetchByTableName(String tableName) { + return mySqlTableMetaCallback.fetch(dbAddress, dbName, tableName); + } + + @Override + public String getDbName() { + return dbName; + } + + @Override + public String getDbAddress() { + return dbAddress; + } + + @Override + public void setDbAddress(String address) { + this.dbAddress = address; + } +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java new file mode 100644 index 00000000..0eee235d --- /dev/null +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java @@ -0,0 +1,26 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; + +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; + +public class MySqlTableMetaStorageFactory implements TableMetaStorageFactory { + + private MySqlTableMetaCallback mySQLTableMetaCallback; + private String dbName; + + public MySqlTableMetaStorageFactory(MySqlTableMetaCallback callback, String dbName) { + mySQLTableMetaCallback = callback; + this.dbName = dbName; + } + + @Override + public TableMetaStorage getTableMetaStorage() { + return new MySqlTableMetaStorage(mySQLTableMetaCallback, dbName); + } + + @Override + public String getDbName() { + return dbName; + } + +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java new file mode 100644 index 00000000..8ca9b044 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java @@ -0,0 +1,89 @@ +package com.alibaba.otter.canal.parse; + +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; +import com.alibaba.otter.canal.parse.exception.CanalParseException; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; +import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager; +import com.alibaba.otter.canal.parse.support.AuthenticationInfo; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import com.alibaba.otter.canal.protocol.position.LogPosition; +import com.alibaba.otter.canal.sink.CanalEventSink; +import com.alibaba.otter.canal.sink.exception.CanalSinkException; + +public class MysqlBinlogDumpPerformanceTest { + + public static void main(String args[]) { + final MysqlEventParser controller = new MysqlEventParser(); + final EntryPosition startPosition = new EntryPosition("mysql-bin.001699", 120L, 100L); + controller.setConnectionCharset(Charset.forName("UTF-8")); + controller.setSlaveId(3344L); + controller.setDetectingEnable(false); + controller.setFilterQueryDml(true); + controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3328), "root", "hello")); + controller.setMasterPosition(startPosition); + controller.setEnableTsdb(false); + controller.setDestination("example"); + controller.setTsdbSpringXml("classpath:spring/tsdb/h2-tsdb.xml"); + // controller.setEventFilter(new AviaterRegexFilter("test\\..*")); + // controller.setEventBlackFilter(new + // AviaterRegexFilter("canal_tsdb\\..*")); + controller.setParallel(true); + controller.setParallelBufferSize(256); + controller.setParallelThreadSize(16); + controller.setIsGTIDMode(false); + final AtomicLong sum = new AtomicLong(0); + final AtomicLong last = new AtomicLong(0); + final AtomicLong start = new AtomicLong(System.currentTimeMillis()); + final AtomicLong end = new AtomicLong(0); + controller.setEventSink(new AbstractCanalEventSinkTest>() { + + public boolean sink(List entrys, InetSocketAddress remoteAddress, String destination) + throws CanalSinkException, + InterruptedException { + + sum.addAndGet(entrys.size()); + long current = sum.get(); + if (current - last.get() >= 100000) { + end.set(System.currentTimeMillis()); + long tps = ((current - last.get()) * 1000) / (end.get() - start.get()); + System.out.println(" total : " + sum + " , cost : " + (end.get() - start.get()) + " , tps : " + tps); + last.set(current); + start.set(end.get()); + } + return true; + } + + }); + controller.setLogPositionManager(new AbstractLogPositionManager() { + + @Override + public LogPosition getLatestIndexBy(String destination) { + return null; + } + + @Override + public void persistLogPosition(String destination, LogPosition logPosition) throws CanalParseException { + } + }); + + controller.start(); + + try { + Thread.sleep(100 * 1000 * 1000L); + } catch (InterruptedException e) { + } + controller.stop(); + } + + public static abstract class AbstractCanalEventSinkTest extends AbstractCanalLifeCycle implements CanalEventSink { + + public void interrupt() { + } + } +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java new file mode 100644 index 00000000..e92fecb5 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogEventPerformanceTest.java @@ -0,0 +1,82 @@ +package com.alibaba.otter.canal.parse; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicLong; + +import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector; +import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor; +import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket; +import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket; +import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager; +import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher; +import com.taobao.tddl.dbsync.binlog.LogContext; +import com.taobao.tddl.dbsync.binlog.LogDecoder; +import com.taobao.tddl.dbsync.binlog.LogEvent; + +public class MysqlBinlogEventPerformanceTest { + + protected static Charset charset = Charset.forName("utf-8"); + + public static void main(String args[]) { + DirectLogFetcher fetcher = new DirectLogFetcher(); + try { + MysqlConnector connector = new MysqlConnector(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + connector.connect(); + updateSettings(connector); + sendBinlogDump(connector, "mysql-bin.000006", 120L, 3); + fetcher.start(connector.getChannel()); + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogContext context = new LogContext(); + AtomicLong sum = new AtomicLong(0); + long start = System.currentTimeMillis(); + long last = 0; + long end = 0; + while (fetcher.fetch()) { + decoder.decode(fetcher, context); + sum.incrementAndGet(); + long current = sum.get(); + if (current - last >= 100000) { + end = System.currentTimeMillis(); + long tps = ((current - last) * 1000) / (end - start); + System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps); + last = current; + start = end; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + fetcher.close(); + } catch (IOException e) { + } + } + } + + private static void sendBinlogDump(MysqlConnector connector, String binlogfilename, Long binlogPosition, int slaveId) + throws IOException { + BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket(); + binlogDumpCmd.binlogFileName = binlogfilename; + binlogDumpCmd.binlogPosition = binlogPosition; + binlogDumpCmd.slaveServerId = slaveId; + byte[] cmdBody = binlogDumpCmd.toBytes(); + + HeaderPacket binlogDumpHeader = new HeaderPacket(); + binlogDumpHeader.setPacketBodyLength(cmdBody.length); + binlogDumpHeader.setPacketSequenceNumber((byte) 0x00); + PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody); + } + + private static void updateSettings(MysqlConnector connector) throws IOException { + update("set @master_binlog_checksum= '@@global.binlog_checksum'", connector); + update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'", connector); + } + + public static void update(String cmd, MysqlConnector connector) throws IOException { + MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector); + exector.update(cmd); + } + +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java new file mode 100644 index 00000000..79b88375 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java @@ -0,0 +1,192 @@ +package com.alibaba.otter.canal.parse; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.nio.charset.Charset; +import java.util.BitSet; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.atomic.AtomicLong; + +import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector; +import com.alibaba.otter.canal.parse.driver.mysql.MysqlUpdateExecutor; +import com.alibaba.otter.canal.parse.driver.mysql.packets.HeaderPacket; +import com.alibaba.otter.canal.parse.driver.mysql.packets.client.BinlogDumpCommandPacket; +import com.alibaba.otter.canal.parse.driver.mysql.utils.PacketManager; +import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher; +import com.taobao.tddl.dbsync.binlog.LogBuffer; +import com.taobao.tddl.dbsync.binlog.LogContext; +import com.taobao.tddl.dbsync.binlog.LogDecoder; +import com.taobao.tddl.dbsync.binlog.LogEvent; +import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer; +import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent.ColumnInfo; +import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; + +public class MysqlBinlogParsePerformanceTest { + + protected static Charset charset = Charset.forName("utf-8"); + + public static void main(String args[]) { + DirectLogFetcher fetcher = new DirectLogFetcher(); + try { + MysqlConnector connector = new MysqlConnector(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + connector.connect(); + updateSettings(connector); + sendBinlogDump(connector, "mysql-bin.000006", 120L, 3); + fetcher.start(connector.getChannel()); + final BlockingQueue buffer = new ArrayBlockingQueue(1024); + Thread thread = new Thread(new Runnable() { + + @Override + public void run() { + try { + consumer(buffer); + } catch (IOException e) { + e.printStackTrace(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + thread.start(); + + while (fetcher.fetch()) { + buffer.put(fetcher.duplicate()); + fetcher.consume(fetcher.limit()); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + fetcher.close(); + } catch (IOException e) { + } + } + } + + public static void consumer(BlockingQueue buffer) throws IOException, InterruptedException { + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogContext context = new LogContext(); + + AtomicLong sum = new AtomicLong(0); + long start = System.currentTimeMillis(); + long last = 0; + long end = 0; + while (true) { + LogEvent event = null; + event = decoder.decode(buffer.take(), context); + int eventType = event.getHeader().getType(); + switch (eventType) { + case LogEvent.ROTATE_EVENT: + break; + case LogEvent.WRITE_ROWS_EVENT_V1: + case LogEvent.WRITE_ROWS_EVENT: + parseRowsEvent((WriteRowsLogEvent) event, sum); + break; + case LogEvent.UPDATE_ROWS_EVENT_V1: + case LogEvent.UPDATE_ROWS_EVENT: + parseRowsEvent((UpdateRowsLogEvent) event, sum); + break; + case LogEvent.DELETE_ROWS_EVENT_V1: + case LogEvent.DELETE_ROWS_EVENT: + parseRowsEvent((DeleteRowsLogEvent) event, sum); + break; + case LogEvent.XID_EVENT: + sum.incrementAndGet(); + break; + case LogEvent.QUERY_EVENT: + sum.incrementAndGet(); + break; + default: + break; + } + + long current = sum.get(); + if (current - last >= 100000) { + end = System.currentTimeMillis(); + long tps = ((current - last) * 1000) / (end - start); + System.out.println(" total : " + sum + " , cost : " + (end - start) + " , tps : " + tps); + last = current; + start = end; + } + } + } + + private static void sendBinlogDump(MysqlConnector connector, String binlogfilename, Long binlogPosition, int slaveId) + throws IOException { + BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket(); + binlogDumpCmd.binlogFileName = binlogfilename; + binlogDumpCmd.binlogPosition = binlogPosition; + binlogDumpCmd.slaveServerId = slaveId; + byte[] cmdBody = binlogDumpCmd.toBytes(); + + HeaderPacket binlogDumpHeader = new HeaderPacket(); + binlogDumpHeader.setPacketBodyLength(cmdBody.length); + binlogDumpHeader.setPacketSequenceNumber((byte) 0x00); + PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody); + } + + private static void updateSettings(MysqlConnector connector) throws IOException { + update("set @master_binlog_checksum= '@@global.binlog_checksum'", connector); + update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'", connector); + } + + public static void update(String cmd, MysqlConnector connector) throws IOException { + MysqlUpdateExecutor exector = new MysqlUpdateExecutor(connector); + exector.update(cmd); + } + + public static void parseRowsEvent(RowsLogEvent event, AtomicLong sum) { + try { + RowsLogBuffer buffer = event.getRowsBuf(charset.name()); + BitSet columns = event.getColumns(); + BitSet changeColumns = event.getChangeColumns(); + while (buffer.nextOneRow(columns)) { + int type = event.getHeader().getType(); + if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) { + parseOneRow(event, buffer, columns, true); + } else if (LogEvent.DELETE_ROWS_EVENT_V1 == type || LogEvent.DELETE_ROWS_EVENT == type) { + parseOneRow(event, buffer, columns, false); + } else { + parseOneRow(event, buffer, columns, false); + if (!buffer.nextOneRow(changeColumns)) { + break; + } + parseOneRow(event, buffer, changeColumns, true); + } + + sum.incrementAndGet(); + } + } catch (Exception e) { + throw new RuntimeException("parse row data failed.", e); + } + } + + public static void parseOneRow(RowsLogEvent event, RowsLogBuffer buffer, BitSet cols, boolean isAfter) + throws UnsupportedEncodingException { + TableMapLogEvent map = event.getTable(); + if (map == null) { + throw new RuntimeException("not found TableMap with tid=" + event.getTableId()); + } + + final int columnCnt = map.getColumnCnt(); + final ColumnInfo[] columnInfo = map.getColumnInfo(); + for (int i = 0; i < columnCnt; i++) { + if (!cols.get(i)) { + continue; + } + + ColumnInfo info = columnInfo[i]; + buffer.nextValue(info.type, info.meta); + if (buffer.isNull()) { + } else { + buffer.getValue(); + } + } + } +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java index 6849c5b3..6c11c637 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java @@ -41,6 +41,9 @@ public class MysqlDumpTest { controller.setTsdbSpringXml("classpath:tsdb/h2-tsdb.xml"); controller.setEventFilter(new AviaterRegexFilter("test\\..*")); controller.setEventBlackFilter(new AviaterRegexFilter("canal_tsdb\\..*")); + controller.setParallel(true); + controller.setParallelBufferSize(256); + controller.setParallelThreadSize(2); controller.setIsGTIDMode(false); controller.setEventSink(new AbstractCanalEventSinkTest>() { diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java new file mode 100644 index 00000000..18a217b8 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java @@ -0,0 +1,153 @@ +package com.alibaba.otter.canal.parse.inbound.mysql; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.junit.Assert; +import org.junit.Test; + +import com.alibaba.otter.canal.parse.helper.TimeoutChecker; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaCallback; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaStorageFactory; +import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager; +import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest; +import com.alibaba.otter.canal.parse.support.AuthenticationInfo; +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import com.alibaba.otter.canal.protocol.position.LogPosition; +import com.alibaba.otter.canal.sink.exception.CanalSinkException; + +/** + * @author chengjin.lyf on 2018/7/21 下午5:24 + * @since 1.0.25 + */ +public class RdsBinlogEventParserProxyTest { + + private static final String DETECTING_SQL = "insert into retl.xdual values(1,now()) on duplicate key update x=now()"; + private static final String MYSQL_ADDRESS = ""; + private static final String USERNAME = ""; + private static final String PASSWORD = ""; + public static final String DBNAME = ""; + public static final String TBNAME = ""; + public static final String DDL = ""; + + + @Test + public void test_timestamp() throws InterruptedException { + final TimeoutChecker timeoutChecker = new TimeoutChecker(3000 * 1000); + final AtomicLong entryCount = new AtomicLong(0); + final EntryPosition entryPosition = new EntryPosition(); + + final RdsBinlogEventParserProxy controller = new RdsBinlogEventParserProxy(); + Calendar calendar = Calendar.getInstance(); + calendar.add(Calendar.DAY_OF_YEAR, -1); + final EntryPosition defaultPosition = buildPosition(null, null, calendar.getTimeInMillis()); + controller.setSlaveId(3344L); + controller.setDetectingEnable(false); + controller.setDetectingSQL(DETECTING_SQL); + controller.setMasterInfo(buildAuthentication()); + controller.setMasterPosition(defaultPosition); + controller.setInstanceId(""); + controller.setAccesskey(""); + controller.setSecretkey(""); + controller.setBatchSize(4); +// controller.setRdsOpenApiUrl("https://rds.aliyuncs.com/"); + controller.setTableMetaStorageFactory(new MySqlTableMetaStorageFactory(new MySqlTableMetaCallback() { + @Override + public void save(String dbAddress, String schema, String table, String ddl, Long timestamp) { + + } + + @Override + public List fetch(String dbAddress, String dbName) { + TableMetaEntry tableMeta = new TableMetaEntry(); + tableMeta.setSchema(DBNAME); + tableMeta.setTable(TBNAME); + tableMeta.setDdl(DDL); + tableMeta.setTimestamp(new Date().getTime()); + List entries = new ArrayList(); + entries.add(tableMeta); + return entries; + } + + @Override + public List fetch(String dbAddress, String dbName, String tableName) { + TableMetaEntry tableMeta = new TableMetaEntry(); + tableMeta.setSchema(DBNAME); + tableMeta.setTable(TBNAME); + tableMeta.setDdl(DDL); + tableMeta.setTimestamp(new Date().getTime()); + List entries = new ArrayList(); + entries.add(tableMeta); + return entries; + } + }, DBNAME)); + controller.setEventSink(new AbstractCanalEventSinkTest>() { + + @Override + public boolean sink(List entrys, InetSocketAddress remoteAddress, String destination) + throws CanalSinkException { + for (CanalEntry.Entry entry : entrys) { + if (entry.getEntryType() != CanalEntry.EntryType.HEARTBEAT) { + entryCount.incrementAndGet(); + + String logfilename = entry.getHeader().getLogfileName(); + long logfileoffset = entry.getHeader().getLogfileOffset(); + long executeTime = entry.getHeader().getExecuteTime(); + + entryPosition.setJournalName(logfilename); + entryPosition.setPosition(logfileoffset); + entryPosition.setTimestamp(executeTime); + break; + } + } + return true; + } + }); + + controller.setLogPositionManager(new AbstractLogPositionManager() { + + private LogPosition logPosition; + public void persistLogPosition(String destination, LogPosition logPosition) { + System.out.println(logPosition); + this.logPosition = logPosition; + } + + public LogPosition getLatestIndexBy(String destination) { + return logPosition; + } + }); + + controller.start(); + timeoutChecker.waitForIdle(); + + if (controller.isStart()) { + controller.stop(); + } + + // check + Assert.assertTrue(entryCount.get() > 0); + + // 对比第一条数据和起始的position相同 + Assert.assertEquals(entryPosition.getJournalName(), "mysql-bin.000001"); + Assert.assertTrue(entryPosition.getPosition() <= 6163L); + Assert.assertTrue(entryPosition.getTimestamp() <= defaultPosition.getTimestamp()); + } + + + // ======================== helper method ======================= + + private EntryPosition buildPosition(String binlogFile, Long offest, Long timestamp) { + return new EntryPosition(binlogFile, offest, timestamp); + } + + private AuthenticationInfo buildAuthentication() { + return new AuthenticationInfo(new InetSocketAddress(MYSQL_ADDRESS, 3306), USERNAME, PASSWORD); + } +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java new file mode 100644 index 00000000..04fd95ea --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java @@ -0,0 +1,38 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.util.Date; + +public class NoStorageTest { + final String DBNAME = "testdb"; + final String TBNAME = "testtb"; + final String DDL = "CREATE TABLE `testtb` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `name` varchar(2048) DEFAULT NULL,\n" + + " `datachange_lasttime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最晚更新时间',\n" + + " `otter_testcol` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol1` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol2` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol3` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol4` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol5` varchar(45) DEFAULT NULL,\n" + + " PRIMARY KEY (`id`)\n" + + " ) ENGINE=InnoDB AUTO_INCREMENT=58333898 DEFAULT CHARSET=utf8mb4"; + @Test + public void nostorage() { + MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + TableMetaCacheWithStorage tableMetaCacheWithStorage = new TableMetaCacheWithStorage(connection, null); + EntryPosition entryPosition = new EntryPosition(); + entryPosition.setTimestamp(new Date().getTime()); + String fullTableName = DBNAME + "." + TBNAME; + tableMetaCacheWithStorage.apply(entryPosition, fullTableName, DDL, null); + entryPosition.setTimestamp(new Date().getTime() + 1000L); + TableMeta result = tableMetaCacheWithStorage.getTableMeta(DBNAME, TBNAME, false, entryPosition); + assert result.getDdl().equalsIgnoreCase(DDL); + } +} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java new file mode 100644 index 00000000..0ec16607 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java @@ -0,0 +1,77 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; + +import com.alibaba.otter.canal.parse.inbound.TableMeta; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaCallback; +import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaStorageFactory; +import com.alibaba.otter.canal.protocol.position.EntryPosition; +import com.alibaba.otter.canal.protocol.position.Position; +import org.junit.Test; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class StorageTest { + + final String DBNAME = "testdb"; + final String TBNAME = "testtb"; + final String DDL = "CREATE TABLE `testtb` (\n" + + " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + + " `name` varchar(2048) DEFAULT NULL,\n" + + " `datachange_lasttime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最晚更新时间',\n" + + " `otter_testcol` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol1` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol2` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol3` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol4` varchar(45) DEFAULT NULL,\n" + + " `otter_testcol5` varchar(45) DEFAULT NULL,\n" + + " PRIMARY KEY (`id`)\n" + + " ) ENGINE=InnoDB AUTO_INCREMENT=58333898 DEFAULT CHARSET=utf8mb4"; + + @Test + public void storage() { + + MySqlTableMetaStorageFactory factory = new MySqlTableMetaStorageFactory(new MySqlTableMetaCallback() { + @Override + public void save(String dbAddress, String schema, String table, String ddl, Long timestamp) { + + } + + @Override + public List fetch(String dbAddress, String dbName) { + TableMetaEntry tableMeta = new TableMetaEntry(); + tableMeta.setSchema(DBNAME); + tableMeta.setTable(TBNAME); + tableMeta.setDdl(DDL); + tableMeta.setTimestamp(new Date().getTime()); + List entries = new ArrayList(); + entries.add(tableMeta); + return entries; + } + + @Override + public List fetch(String dbAddress, String dbName, String tableName) { + TableMetaEntry tableMeta = new TableMetaEntry(); + tableMeta.setSchema(DBNAME); + tableMeta.setTable(TBNAME); + tableMeta.setDdl(DDL); + tableMeta.setTimestamp(new Date().getTime()); + List entries = new ArrayList(); + entries.add(tableMeta); + return entries; + } + }, DBNAME); + MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); + TableMetaCacheWithStorage tableMetaCacheWithStorage = new TableMetaCacheWithStorage(connection, factory.getTableMetaStorage()); + EntryPosition entryPosition = new EntryPosition(); + entryPosition.setTimestamp(new Date().getTime()); + String fullTableName = DBNAME + "." + TBNAME; + tableMetaCacheWithStorage.apply(entryPosition, fullTableName, DDL, null); + + entryPosition.setTimestamp(new Date().getTime() + 1000L); + TableMeta result = tableMetaCacheWithStorage.getTableMeta(DBNAME, TBNAME, false, entryPosition); + assert result.getDdl().equalsIgnoreCase(DDL); + } +} diff --git a/pom.xml b/pom.xml index 1e8123ef..56ac821a 100644 --- a/pom.xml +++ b/pom.xml @@ -66,16 +66,6 @@ false - - alibaba - http://code.alibabatech.com/mvn/releases/ - - true - - - false - - sonatype sonatype @@ -106,8 +96,8 @@ true true - 1.6 - 1.6 + 1.7 + 1.7 UTF-8 3.2.9.RELEASE @@ -129,6 +119,7 @@ example kafka kafka-client + prometheus @@ -256,7 +247,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_520 + 2.0.0_preview_540 com.alibaba @@ -341,7 +332,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.2 + 3.8.0 ${java_source_version} ${java_target_version} diff --git a/prometheus/pom.xml b/prometheus/pom.xml new file mode 100644 index 00000000..16cf8acf --- /dev/null +++ b/prometheus/pom.xml @@ -0,0 +1,67 @@ + + + + canal + com.alibaba.otter + 1.0.26-SNAPSHOT + + 4.0.0 + + com.alibaba.otter + canal.prometheus + 1.0.26-SNAPSHOT + canal prometheus module for otter ${project.version} + + + + org.aspectj + aspectjrt + 1.8.9 + + + org.aspectj + aspectjweaver + 1.8.9 + + + org.jctools + jctools-core + 2.1.2 + + + io.prometheus + simpleclient + 0.4.0 + + + io.prometheus + simpleclient_hotspot + 0.4.0 + + + io.prometheus + simpleclient_httpserver + 0.4.0 + + + io.prometheus + simpleclient_pushgateway + 0.4.0 + + + com.alibaba.otter + canal.instance.core + ${project.version} + provided + + + com.alibaba.otter + canal.server + ${project.version} + provided + + + + diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java new file mode 100644 index 00000000..8acf40b8 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java @@ -0,0 +1,98 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.prometheus.impl.InstanceMetaCollector; +import com.alibaba.otter.canal.prometheus.impl.MemoryStoreCollector; +import com.alibaba.otter.canal.prometheus.impl.PrometheusCanalEventDownStreamHandler; +import com.alibaba.otter.canal.sink.CanalEventSink; +import com.alibaba.otter.canal.sink.entry.EntryEventSink; +import com.alibaba.otter.canal.store.CanalStoreException; +import io.prometheus.client.Collector; +import io.prometheus.client.CollectorRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; + +/** + * @author Chuanyi Li + */ +public class CanalInstanceExports { + + private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class); + + public static final String[] labels = {"destination"}; + + public static final List labelList = Collections.singletonList(labels[0]); + + private final String destination; + + private Collector storeCollector; + + private Collector delayCollector; + + private Collector metaCollector; + + private CanalInstanceExports(CanalInstance instance) { + this.destination = instance.getDestination(); + initDelayGauge(instance); + initStoreCollector(instance); + initMetaCollector(instance); + } + + + + static CanalInstanceExports forInstance(CanalInstance instance) { + return new CanalInstanceExports(instance); + } + + void register() { + if (delayCollector != null) { + delayCollector.register(); + } + if (storeCollector != null) { + storeCollector.register(); + } + if (metaCollector != null) { + metaCollector.register(); + } + } + + void unregister() { + if (delayCollector != null) { + CollectorRegistry.defaultRegistry.unregister(delayCollector); + } + if (storeCollector != null) { + CollectorRegistry.defaultRegistry.unregister(storeCollector); + } + if (metaCollector != null) { + CollectorRegistry.defaultRegistry.unregister(metaCollector); + } + } + + private void initDelayGauge(CanalInstance instance) { + CanalEventSink sink = instance.getEventSink(); + if (sink instanceof EntryEventSink) { + EntryEventSink entryEventSink = (EntryEventSink) sink; + // TODO ensure not to add handler again + PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(destination); + entryEventSink.addHandler(handler); + delayCollector = handler.getCollector(); + } else { + logger.warn("This impl register metrics for only EntryEventSink, skip."); + } + } + + private void initStoreCollector(CanalInstance instance) { + try { + storeCollector = new MemoryStoreCollector(instance.getEventStore(), destination); + } catch (CanalStoreException cse) { + logger.warn("Failed to register metrics for destination {}.", destination, cse); + } + } + + private void initMetaCollector(CanalInstance instance) { + metaCollector = new InstanceMetaCollector(instance); + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java new file mode 100644 index 00000000..8eef79ba --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java @@ -0,0 +1,21 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.prometheus.impl.InboundThroughputAspect; +import com.alibaba.otter.canal.prometheus.impl.OutboundThroughputAspect; + +/** + * @author Chuanyi Li + */ +public class CanalServerExports { + + private static boolean initialized = false; + + public static synchronized void initialize() { + if (!initialized) { + InboundThroughputAspect.getCollector().register(); + OutboundThroughputAspect.getCollector().register(); + initialized = true; + } + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java new file mode 100644 index 00000000..12b8675f --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusProvider.java @@ -0,0 +1,15 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.spi.CanalMetricsProvider; +import com.alibaba.otter.canal.spi.CanalMetricsService; + +/** + * @author Chuanyi Li + */ +public class PrometheusProvider implements CanalMetricsProvider { + + @Override + public CanalMetricsService getService() { + return PrometheusService.getInstance(); + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java new file mode 100644 index 00000000..c7942235 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -0,0 +1,110 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.spi.CanalMetricsService; +import io.prometheus.client.exporter.HTTPServer; +import io.prometheus.client.hotspot.DefaultExports; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Chuanyi Li + */ +public class PrometheusService implements CanalMetricsService { + + private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); + + private final Map exports = new ConcurrentHashMap(); + + private volatile boolean running = false; + + private HTTPServer server; + + private PrometheusService() { + } + + private static class SingletonHolder { + private static final PrometheusService SINGLETON = new PrometheusService(); + } + + public static PrometheusService getInstance() { + return SingletonHolder.SINGLETON; + } + + @Override + public void initialize() { + try { + //TODO 1.Configurable port + //TODO 2.Https + server = new HTTPServer(11112); + } catch (IOException e) { + logger.warn("Unable to start prometheus HTTPServer.", e); + return; + } + try { + // JVM exports + DefaultExports.initialize(); + // Canal server level exports + CanalServerExports.initialize(); + } catch (Throwable t) { + logger.warn("Unable to initialize server exports.", t); + } + + running = true; + } + + @Override + public void terminate() { + running = false; + // Normally, service should be terminated at canal shutdown. + // No need to unregister instance exports explicitly. + // But for the sake of safety, unregister them. + for (CanalInstanceExports ie : exports.values()) { + ie.unregister(); + } + if (server != null) { + server.stop(); + } + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public void register(CanalInstance instance) { + if (instance.isStart()) { + logger.warn("Cannot register metrics for destination {} that is running.", instance.getDestination()); + return; + } + try { + CanalInstanceExports export = CanalInstanceExports.forInstance(instance); + export.register(); + exports.put(instance.getDestination(), export); + } catch (Throwable t) { + logger.warn("Unable to register instance exports for {}.", instance.getDestination(), t); + } + logger.info("Register metrics for destination {}.", instance.getDestination()); + } + + @Override + public void unregister(CanalInstance instance) { + if (instance.isStart()) { + logger.warn("Try unregister metrics after destination {} is stopped.", instance.getDestination()); + } + try { + CanalInstanceExports export = exports.remove(instance.getDestination()); + if (export != null) { + export.unregister(); + } + } catch (Throwable t) { + logger.warn("Unable to unregister instance exports for {}.", instance.getDestination(), t); + } + logger.info("Unregister metrics for destination {}.", instance.getDestination()); + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java new file mode 100644 index 00000000..eabc256c --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java @@ -0,0 +1,79 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.jctools.maps.ConcurrentAutoTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Chuanyi Li + */ +@Aspect +public class InboundThroughputAspect { + + private static final Logger logger = LoggerFactory.getLogger(InboundThroughputAspect.class); + + /** + * Support highly scalable counters + * @see ConcurrentAutoTable + */ + private static final ConcurrentAutoTable total = new ConcurrentAutoTable(); + + private static final Collector collector = new InboundThroughputCollector(); + + public static Collector getCollector() { + return collector; + } + + @Pointcut("call(byte[] com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))") + public void read() {} + + @Pointcut("call(void com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..)) ") + public void readBytes() {} + + //nested read, just eliminate them. + @Pointcut("withincode(* com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))") + public void nestedCall() {} + + @After("read() && !nestedCall() && args(len, ..)") + public void recordRead(int len) { + accumulateBytes(len); + } + + @After("readBytes() && !nestedCall() && args(.., len, timeout)") + public void recordReadBytes(int len, int timeout) { + accumulateBytes(len); + } + + private void accumulateBytes(int count) { + try { + total.add(count); + } catch (Throwable t) { + //Catch every Throwable, rather than break the business logic. + logger.warn("Error while accumulate inbound bytes.", t); + } + } + + public static class InboundThroughputCollector extends Collector { + + private InboundThroughputCollector() {} + + @Override + public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily bytes = new CounterMetricFamily("canal_net_inbound_bytes", + "Total socket inbound bytes of canal server.", + total.get()); + mfs.add(bytes); + return mfs; + } + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java new file mode 100644 index 00000000..5562b8fd --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java @@ -0,0 +1,64 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring; +import com.alibaba.otter.canal.meta.CanalMetaManager; +import com.alibaba.otter.canal.prometheus.CanalInstanceExports; +import com.alibaba.otter.canal.protocol.ClientIdentity; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Chuanyi Li + */ +public class InstanceMetaCollector extends Collector { + + private static final List InfoLabel = Arrays.asList("destination", "mode"); + + private CanalMetaManager metaManager; + + private final String destination; + + private final String mode; + + private final String subsHelp; + + public InstanceMetaCollector(CanalInstance instance) { + if (instance == null) { + throw new IllegalArgumentException("CanalInstance must not be null."); + } + if (instance instanceof CanalInstanceWithSpring) { + mode = "spring"; + } else { + mode = "manager"; + } + this.metaManager = instance.getMetaManager(); + this.destination = instance.getDestination(); + this.subsHelp = "Subscriptions of canal instance " + destination; + } + + @Override + public List collect() { + List mfs = new ArrayList(); + GaugeMetricFamily instanceInfo = new GaugeMetricFamily( + "canal_instance", + "Canal instance", + InfoLabel); + instanceInfo.addMetric(Arrays.asList(destination, mode), 1); + mfs.add(instanceInfo); + if (metaManager.isStart()) { + // client id = hardcode 1001, 目前没有意义 + List subs = metaManager.listAllSubscribeInfo(destination); + GaugeMetricFamily subscriptions = new GaugeMetricFamily( + "canal_instance_subscription", + subsHelp, CanalInstanceExports.labelList); + subscriptions.addMetric(Arrays.asList(destination), subs.size()); + mfs.add(subscriptions); + } + return mfs; + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java new file mode 100644 index 00000000..79fdca4a --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java @@ -0,0 +1,75 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.prometheus.CanalInstanceExports; +import com.alibaba.otter.canal.store.CanalEventStore; +import com.alibaba.otter.canal.store.CanalStoreException; +import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer; +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +/** + * @author Chuanyi Li + */ +public class MemoryStoreCollector extends Collector { + + private static final Class clazz = MemoryEventStoreWithBuffer.class; + + private final String destination; + + private final AtomicLong putSequence; + + private final AtomicLong ackSequence; + + private final String putHelp; + + private final String ackHelp; + + public MemoryStoreCollector(CanalEventStore store, String destination) { + this.destination = destination; + if (!(store instanceof MemoryEventStoreWithBuffer)) { + throw new IllegalArgumentException("EventStore must be MemoryEventStoreWithBuffer"); + } + MemoryEventStoreWithBuffer ms = (MemoryEventStoreWithBuffer) store; + putSequence = getDeclaredValue(ms, "putSequence"); + ackSequence = getDeclaredValue(ms, "ackSequence"); + putHelp = "Produced sequence of canal instance " + destination; + ackHelp = "Consumed sequence of canal instance " + destination; + } + + @Override + public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily put = new CounterMetricFamily("canal_instance_store_produce_seq", + putHelp, Arrays.asList(CanalInstanceExports.labels)); + put.addMetric(Collections.singletonList(destination), putSequence.doubleValue()); + mfs.add(put); + CounterMetricFamily ack = new CounterMetricFamily("canal_instance_store_consume_seq", + ackHelp, Arrays.asList(CanalInstanceExports.labels)); + ack.addMetric(Collections.singletonList(destination), ackSequence.doubleValue()); + mfs.add(ack); + return mfs; + } + + @SuppressWarnings("unchecked") + private static T getDeclaredValue(MemoryEventStoreWithBuffer store, String name) { + T value; + try { + Field putField = clazz.getDeclaredField(name); + putField.setAccessible(true); + value = (T) putField.get(store); + } catch (NoSuchFieldException e) { + throw new CanalStoreException(e); + } catch (IllegalAccessException e) { + throw new CanalStoreException(e); + } + return value; + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java new file mode 100644 index 00000000..60dd808d --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java @@ -0,0 +1,80 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; +import org.aspectj.lang.annotation.After; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.jboss.netty.channel.Channel; +import org.jctools.maps.ConcurrentAutoTable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.otter.canal.server.netty.NettyUtils.HEADER_LENGTH; + +/** + * @author Chuanyi Li + */ +@Aspect +public class OutboundThroughputAspect { + private static final Logger logger = LoggerFactory.getLogger(OutboundThroughputAspect.class); + + /** + * Support highly scalable counters + * @see ConcurrentAutoTable + */ + private static final ConcurrentAutoTable total = new ConcurrentAutoTable(); + + private static final Collector collector = new OutboundThroughputCollector(); + + public static Collector getCollector() { + return collector; + } + + @Pointcut("call(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))") + public void write() {} + + //nested read, just eliminate them. + @Pointcut("withincode(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))") + public void nestedCall() {} + + @After("write() && !nestedCall() && args(ch, bytes, ..)") + public void recordWriteBytes(Channel ch, byte[] bytes) { + if (bytes != null) { + accumulateBytes(HEADER_LENGTH + bytes.length); + } + } + + @After("write() && !nestedCall() && args(ch, buf, ..)") + public void recordWriteBuffer(Channel ch, ByteBuffer buf) { + if (buf != null) { + total.add(HEADER_LENGTH + buf.limit()); + } + } + private void accumulateBytes(int count) { + try { + total.add(count); + } catch (Throwable t) { + //Catch every Throwable, rather than break the business logic. + logger.warn("Error while accumulate inbound bytes.", t); + } + } + + public static class OutboundThroughputCollector extends Collector { + + private OutboundThroughputCollector() {} + + @Override public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily bytes = new CounterMetricFamily("canal_net_outbound_bytes", + "Total socket outbound bytes of canal server.", + total.get()); + mfs.add(bytes); + return mfs; + } + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java new file mode 100644 index 00000000..c55a7eac --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -0,0 +1,73 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.prometheus.CanalInstanceExports; +import com.alibaba.otter.canal.sink.AbstractCanalEventDownStreamHandler; +import com.alibaba.otter.canal.store.model.Event; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * @author Chuanyi Li + */ +public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler> { + + private final Collector collector; + + private long latestExecuteTime = 0L; + + private static final String DELAY_NAME = "canal_instance_traffic_delay"; + + private final String delayHelpName; + + private final List labelValues; + + public PrometheusCanalEventDownStreamHandler(final String destination) { + this.delayHelpName = "Traffic delay of canal instance " + destination + " in seconds."; + this.labelValues = Collections.singletonList(destination); + collector = new Collector() { + @Override + public List collect() { + List mfs = new ArrayList(); + long now = System.currentTimeMillis(); + GaugeMetricFamily delay = new GaugeMetricFamily( + DELAY_NAME, + delayHelpName, + CanalInstanceExports.labelList); + double d = 0.0; + if (latestExecuteTime > 0) { + d = now - latestExecuteTime; + } + d = d > 0.0 ? (d / 1000) : 0.0; + delay.addMetric(labelValues, d); + mfs.add(delay); + return mfs; + } + }; + } + + @Override + public List before(List events) { + // TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming + // see: https://dev.mysql.com/worklog/task/?id=342 + // heartbeats are sent by the master only if there is no + // more unsent events in the actual binlog file for a period longer that + // master_heartbeat_period. + if (events != null && !events.isEmpty()) { + Event last = events.get(events.size() - 1); + long ts = last.getExecuteTime(); + if (ts > latestExecuteTime) { + latestExecuteTime = ts; + } + } + return events; + } + + public Collector getCollector() { + return this.collector; + } + +} diff --git a/prometheus/src/main/resources/META-INF/aop.xml b/prometheus/src/main/resources/META-INF/aop.xml new file mode 100644 index 00000000..8bd31d0c --- /dev/null +++ b/prometheus/src/main/resources/META-INF/aop.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider b/prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider new file mode 100644 index 00000000..9adbf8d1 --- /dev/null +++ b/prometheus/src/main/resources/META-INF/services/com.alibaba.otter.canal.spi.CanalMetricsProvider @@ -0,0 +1 @@ +com.alibaba.otter.canal.prometheus.PrometheusProvider diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index 722bb2bc..ccf21020 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -1,11 +1,11 @@ package com.alibaba.otter.canal.server.embedded; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.TimeUnit; +import com.alibaba.otter.canal.spi.CanalMetricsProvider; +import com.alibaba.otter.canal.spi.CanalMetricsService; +import com.alibaba.otter.canal.spi.NopCanalMetricsService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; @@ -33,17 +33,18 @@ import com.google.protobuf.ByteString; /** * 嵌入式版本实现 - * + * * @author jianghang 2012-7-12 下午01:34:00 * @author zebin.xuzb * @version 1.0.0 */ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements CanalServer, CanalService { - private static final Logger logger = LoggerFactory.getLogger(CanalServerWithEmbedded.class); + private static final Logger logger = LoggerFactory.getLogger(CanalServerWithEmbedded.class); private Map canalInstances; // private Map lastRollbackPostions; private CanalInstanceGenerator canalInstanceGenerator; + private CanalMetricsService metrics = NopCanalMetricsService.NOP; private static class SingletonHolder { @@ -61,7 +62,9 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C public void start() { if (!isStart()) { super.start(); - + // 如果存在provider,则启动metrics service + loadCanalMetrics(); + metrics.initialize(); canalInstances = MigrateMap.makeComputingMap(new Function() { public CanalInstance apply(String destination) { @@ -92,6 +95,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C logger.error(String.format("stop CanalInstance[%s] has an error", entry.getKey()), e); } } + metrics.terminate(); } public void start(final String destination) { @@ -99,6 +103,9 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C if (!canalInstance.isStart()) { try { MDC.put("destination", destination); + if (metrics.isRunning()) { + metrics.register(canalInstance); + } canalInstance.start(); logger.info("start CanalInstances[{}] successfully", destination); } finally { @@ -114,6 +121,9 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C try { MDC.put("destination", destination); canalInstance.stop(); + if (metrics.isRunning()) { + metrics.unregister(canalInstance); + } logger.info("stop CanalInstances[{}] successfully", destination); } finally { MDC.remove("destination"); @@ -176,7 +186,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 获取数据 - * + * *

            * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
            * 
      @@ -188,14 +198,14 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 获取数据,可以指定超时时间. - * + * *
            * 几种case:
            * a. 如果timeout为null,则采用tryGet方式,即时获取
            * b. 如果timeout不为null
            *    1. timeout为0,则采用get阻塞方式,获取数据,不设置超时,直到有足够的batchSize数据才返回
            *    2. timeout不为0,则采用get+timeout方式,获取数据,超时还没有batchSize足够的数据,有多少返回多少
      -     * 
      +     *
            * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
            * 
      */ @@ -251,7 +261,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 不指定 position 获取事件。canal 会记住此 client 最新的 position。
      * 如果是第一次 fetch,则会从 canal 中保存的最老一条数据开始输出。 - * + * *
            * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
            * 
      @@ -264,14 +274,14 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 不指定 position 获取事件。canal 会记住此 client 最新的 position。
      * 如果是第一次 fetch,则会从 canal 中保存的最老一条数据开始输出。 - * + * *
            * 几种case:
            * a. 如果timeout为null,则采用tryGet方式,即时获取
            * b. 如果timeout不为null
            *    1. timeout为0,则采用get阻塞方式,获取数据,不设置超时,直到有足够的batchSize数据才返回
            *    2. timeout不为0,则采用get+timeout方式,获取数据,超时还没有batchSize足够的数据,有多少返回多少
      -     *    
      +     *
            * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题)
            * 
      */ @@ -342,7 +352,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C /** * 进行 batch id 的确认。确认之后,小于等于此 batchId 的 Message 都会被确认。 - * + * *
            * 注意:进行反馈时必须按照batchId的顺序进行ack(需有客户端保证)
            * 
      @@ -492,6 +502,27 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C } } + private void loadCanalMetrics() { + ServiceLoader providers = ServiceLoader.load(CanalMetricsProvider.class); + List list = new ArrayList(); + for (CanalMetricsProvider provider : providers) { + list.add(provider); + } + if (!list.isEmpty()) { + // 发现provider, 进行初始化 + if (list.size() > 1) { + logger.warn("Found more than one CanalMetricsProvider, use the first one."); + //报告冲突 + for (CanalMetricsProvider p : list) { + logger.warn("Found CanalMetricsProvider: {}.", p.getClass().getName()); + } + } + //默认使用第一个 + CanalMetricsProvider provider = list.get(0); + this.metrics = provider.getService(); + } + } + // ========= setter ========== public void setCanalInstanceGenerator(CanalInstanceGenerator canalInstanceGenerator) { diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java index 531c817f..20532400 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java @@ -2,8 +2,12 @@ package com.alibaba.otter.canal.server.netty; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; +import org.jboss.netty.buffer.ChannelBuffer; import org.jboss.netty.buffer.ChannelBuffers; +import org.jboss.netty.buffer.CompositeChannelBuffer; import org.jboss.netty.channel.Channel; import org.jboss.netty.channel.ChannelFutureListener; import org.jboss.netty.channel.Channels; @@ -19,9 +23,23 @@ import com.alibaba.otter.canal.protocol.CanalPacket.Packet; public class NettyUtils { private static final Logger logger = LoggerFactory.getLogger(NettyUtils.class); - private static int HEADER_LENGTH = 4; + public static int HEADER_LENGTH = 4; public static Timer hashedWheelTimer = new HashedWheelTimer(); + public static void write(Channel channel, ByteBuffer body, ChannelFutureListener channelFutureListner) { + byte[] header = ByteBuffer.allocate(HEADER_LENGTH).order(ByteOrder.BIG_ENDIAN).putInt(body.limit()).array(); + List components = new ArrayList(2); + components.add(ChannelBuffers.wrappedBuffer(ByteOrder.BIG_ENDIAN, header)); + components.add(ChannelBuffers.wrappedBuffer(body)); + + if (channelFutureListner == null) { + Channels.write(channel, new CompositeChannelBuffer(ByteOrder.BIG_ENDIAN, components)); + } else { + Channels.write(channel, new CompositeChannelBuffer(ByteOrder.BIG_ENDIAN, components)) + .addListener(channelFutureListner); + } + } + public static void write(Channel channel, byte[] body, ChannelFutureListener channelFutureListner) { byte[] header = ByteBuffer.allocate(HEADER_LENGTH).order(ByteOrder.BIG_ENDIAN).putInt(body.length).array(); if (channelFutureListner == null) { diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index 8f7b4278..11bba043 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -33,13 +33,10 @@ import com.alibaba.otter.canal.protocol.ClientIdentity; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; import com.alibaba.otter.canal.server.netty.NettyUtils; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; -/** - * 处理具体的客户端请求 - * - * @author jianghang 2012-10-24 下午02:21:13 - * @version 1.0.0 - */ public class SessionHandler extends SimpleChannelHandler { private static final Logger logger = LoggerFactory.getLogger(SessionHandler.class); @@ -76,7 +73,7 @@ public class SessionHandler extends SimpleChannelHandler { } embeddedServer.subscribe(clientIdentity); - ctx.setAttachment(clientIdentity);// 设置状态数据 + // ctx.setAttachment(clientIdentity);// 设置状态数据 NettyUtils.ack(ctx.getChannel(), null); } else { NettyUtils.error(401, @@ -130,27 +127,74 @@ public class SessionHandler extends SimpleChannelHandler { } // } - Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); - packetBuilder.setType(PacketType.MESSAGES); + if (message.getId() != -1 && message.isRaw()) { + List rowEntries = message.getRawEntries(); + // message size + int messageSize = 0; + messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, message.getId()); - Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); - messageBuilder.setBatchId(message.getId()); - if (message.getId() != -1) { - if (message.isRaw()) { - // for performance - if (!CollectionUtils.isEmpty(message.getRawEntries())) { + int dataSize = 0; + for (int i = 0; i < rowEntries.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + } + messageSize += dataSize; + messageSize += 1 * rowEntries.size(); + // packet size + int size = 0; + size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, + PacketType.MESSAGES.getNumber()); + size += com.google.protobuf.CodedOutputStream.computeTagSize(5) + + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + + messageSize; + // recyle bytes + // ByteBuffer byteBuffer = (ByteBuffer) + // ctx.getAttachment(); + // if (byteBuffer != null && size <= + // byteBuffer.capacity()) { + // byteBuffer.clear(); + // } else { + // byteBuffer = + // ByteBuffer.allocate(size).order(ByteOrder.BIG_ENDIAN); + // ctx.setAttachment(byteBuffer); + // } + // CodedOutputStream output = + // CodedOutputStream.newInstance(byteBuffer); + byte[] body = new byte[size]; + CodedOutputStream output = CodedOutputStream.newInstance(body); + output.writeEnum(3, PacketType.MESSAGES.getNumber()); + + output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(messageSize); + // message + output.writeInt64(1, message.getId()); + for (int i = 0; i < rowEntries.size(); i++) { + output.writeBytes(2, rowEntries.get(i)); + } + output.checkNoSpaceLeft(); + NettyUtils.write(ctx.getChannel(), body, null); + + // output.flush(); + // byteBuffer.flip(); + // NettyUtils.write(ctx.getChannel(), byteBuffer, + // null); + } else { + Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); + packetBuilder.setType(PacketType.MESSAGES); + + Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); + messageBuilder.setBatchId(message.getId()); + if (message.getId() != -1) { + if (message.isRaw() && !CollectionUtils.isEmpty(message.getRawEntries())) { messageBuilder.addAllMessages(message.getRawEntries()); - } - } else { - if (!CollectionUtils.isEmpty(message.getEntries())) { + } else if (!CollectionUtils.isEmpty(message.getEntries())) { for (Entry entry : message.getEntries()) { messageBuilder.addMessages(entry.toByteString()); } } } + packetBuilder.setBody(messageBuilder.build().toByteString()); + NettyUtils.write(ctx.getChannel(), packetBuilder.build().toByteArray(), null);// 输出数据 } - packetBuilder.setBody(messageBuilder.build().toByteString()); - NettyUtils.write(ctx.getChannel(), packetBuilder.build().toByteArray(), null);// 输出数据 } else { NettyUtils.error(401, MessageFormatter.format("destination or clientId is null", get.toString()).getMessage(), diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java new file mode 100644 index 00000000..ad0ceddf --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsProvider.java @@ -0,0 +1,24 @@ +package com.alibaba.otter.canal.spi; + +/** + * Use java service provider mechanism to provide {@link CanalMetricsService}. + *
      + * Example:
      + * {@code
      + *     ServiceLoader providers = ServiceLoader.load(CanalMetricsProvider.class);
      + *     List list = new ArrayList();
      + *     for (CanalMetricsProvider provider : providers) {
      + *         list.add(provider);
      + *     }
      + * }
      + * 
      + * @author Chuanyi Li + */ +public interface CanalMetricsProvider { + + /** + * @return Impl of {@link CanalMetricsService} + */ + CanalMetricsService getService(); + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java new file mode 100644 index 00000000..6d823be8 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java @@ -0,0 +1,42 @@ +package com.alibaba.otter.canal.spi; + +import com.alibaba.otter.canal.instance.core.CanalInstance; + +/** + * Canal server/instance metrics for export. + * + * Designed to be created by service provider. + * + * @see CanalMetricsProvider + * @author Chuanyi Li + */ +public interface CanalMetricsService { + + /** + * Initialization on canal server startup. + */ + void initialize(); + + /** + * Clean-up at canal server stop phase. + */ + void terminate(); + + /** + * @return {@code true} if the metrics service is running, otherwise {@code false}. + */ + boolean isRunning(); + + /** + * Register instance level metrics for specified instance. + * @param instance {@link CanalInstance} + */ + void register(CanalInstance instance); + + /** + * Unregister instance level metrics for specified instance. + * @param instance {@link CanalInstance} + */ + void unregister(CanalInstance instance); + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java new file mode 100644 index 00000000..0d6ad088 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java @@ -0,0 +1,38 @@ +package com.alibaba.otter.canal.spi; + +import com.alibaba.otter.canal.instance.core.CanalInstance; + +/** + * @author Chuanyi Li + */ +public class NopCanalMetricsService implements CanalMetricsService { + + public static final NopCanalMetricsService NOP = new NopCanalMetricsService(); + + private NopCanalMetricsService() {} + + @Override + public void initialize() { + + } + + @Override + public void terminate() { + + } + + @Override + public boolean isRunning() { + return false; + } + + @Override + public void register(CanalInstance instance) { + + } + + @Override + public void unregister(CanalInstance instance) { + + } +} diff --git a/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java b/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java new file mode 100644 index 00000000..e986b6ff --- /dev/null +++ b/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithEmbedded_FileModeTest.java @@ -0,0 +1,53 @@ +package com.alibaba.otter.canal.server; + +import java.net.InetSocketAddress; +import java.util.Arrays; + +import com.alibaba.otter.canal.instance.manager.model.Canal; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter; +import com.alibaba.otter.canal.instance.manager.model.CanalParameter.*; + +public class CanalServerWithEmbedded_FileModeTest extends BaseCanalServerWithEmbededTest { + + protected Canal buildCanal() { + Canal canal = new Canal(); + canal.setId(1L); + canal.setName(DESTINATION); + canal.setDesc("my standalone server test "); + + CanalParameter parameter = new CanalParameter(); + + parameter.setMetaMode(MetaMode.LOCAL_FILE); + parameter.setDataDir("./conf"); + parameter.setMetaFileFlushPeriod(1000); + parameter.setHaMode(HAMode.HEARTBEAT); + parameter.setIndexMode(IndexMode.MEMORY_META_FAILBACK); + + parameter.setStorageMode(StorageMode.MEMORY); + parameter.setMemoryStorageBufferSize(32 * 1024); + + parameter.setSourcingType(SourcingType.MYSQL); + parameter.setDbAddresses(Arrays.asList(new InetSocketAddress(MYSQL_ADDRESS, 3306), + new InetSocketAddress(MYSQL_ADDRESS, 3306))); + parameter.setDbUsername(USERNAME); + parameter.setDbPassword(PASSWORD); + parameter.setPositions(Arrays.asList("{\"journalName\":\"mysql-bin.000001\",\"position\":332L,\"timestamp\":\"1505998863000\"}", + "{\"journalName\":\"mysql-bin.000001\",\"position\":332L,\"timestamp\":\"1505998863000\"}")); + + parameter.setSlaveId(1234L); + + parameter.setDefaultConnectionTimeoutInSeconds(30); + parameter.setConnectionCharset("UTF-8"); + parameter.setConnectionCharsetNumber((byte) 33); + parameter.setReceiveBufferSize(8 * 1024); + parameter.setSendBufferSize(8 * 1024); + + parameter.setDetectingEnable(false); + parameter.setDetectingIntervalInSeconds(10); + parameter.setDetectingRetryTimes(3); + parameter.setDetectingSQL(DETECTING_SQL); + + canal.setCanalParameter(parameter); + return canal; + } +} \ No newline at end of file diff --git a/server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java b/server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java new file mode 100644 index 00000000..cb078ddf --- /dev/null +++ b/server/src/test/java/com/alibaba/otter/canal/server/ProtocolTest.java @@ -0,0 +1,92 @@ +package com.alibaba.otter.canal.server; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.alibaba.otter.canal.protocol.CanalEntry.Entry; +import com.alibaba.otter.canal.protocol.CanalEntry.EntryType; +import com.alibaba.otter.canal.protocol.CanalEntry.Header; +import com.alibaba.otter.canal.protocol.CanalPacket.Compression; +import com.alibaba.otter.canal.protocol.CanalPacket.Messages; +import com.alibaba.otter.canal.protocol.CanalPacket.Packet; +import com.alibaba.otter.canal.protocol.CanalPacket.PacketType; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; + +public class ProtocolTest { + + @Test + public void testSimple() throws IOException { + Header.Builder headerBuilder = Header.newBuilder(); + headerBuilder.setLogfileName("mysql-bin.000001"); + headerBuilder.setLogfileOffset(1024); + headerBuilder.setExecuteTime(1024); + Entry.Builder entryBuilder = Entry.newBuilder(); + entryBuilder.setHeader(headerBuilder.build()); + entryBuilder.setEntryType(EntryType.ROWDATA); + Entry entry = entryBuilder.build(); + Message message = new Message(3, true, Arrays.asList(entry.toByteString())); + + byte[] body = buildData(message); + Packet packet = Packet.parseFrom(body); + switch (packet.getType()) { + case MESSAGES: { + if (!packet.getCompression().equals(Compression.NONE)) { + throw new CanalClientException("compression is not supported in this connector"); + } + + Messages messages = Messages.parseFrom(packet.getBody()); + Message result = new Message(messages.getBatchId()); + for (ByteString byteString : messages.getMessagesList()) { + result.addEntry(Entry.parseFrom(byteString)); + } + + System.out.println(result); + break; + } + default: { + throw new CanalClientException("unexpected packet type: " + packet.getType()); + } + } + } + + private byte[] buildData(Message message) throws IOException { + List rowEntries = message.getRawEntries(); + // message size + int messageSize = 0; + messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, message.getId()); + + int dataSize = 0; + for (int i = 0; i < rowEntries.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + } + messageSize += dataSize; + messageSize += 1 * rowEntries.size(); + // packet size + int size = 0; + size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, PacketType.MESSAGES.getNumber()); + size += com.google.protobuf.CodedOutputStream.computeTagSize(5) + + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + messageSize; + // TODO recyle bytes[] + byte[] body = new byte[size]; + CodedOutputStream output = CodedOutputStream.newInstance(body); + output.writeEnum(3, PacketType.MESSAGES.getNumber()); + + output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(messageSize); + // message + output.writeInt64(1, message.getId()); + for (int i = 0; i < rowEntries.size(); i++) { + output.writeBytes(2, rowEntries.get(i)); + } + output.checkNoSpaceLeft(); + + return body; + } +} From 92f24bd02ea1b0aab409b585087ff763161e91dd Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Wed, 8 Aug 2018 20:59:09 +0800 Subject: [PATCH 049/360] Remove dirty test case --- .../mysql/tablemeta/NoStorageTest.java | 38 --------- .../inbound/mysql/tablemeta/StorageTest.java | 77 ------------------- 2 files changed, 115 deletions(-) delete mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java delete mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java deleted file mode 100644 index 04fd95ea..00000000 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/NoStorageTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; -import com.alibaba.otter.canal.protocol.position.EntryPosition; -import org.junit.Test; - -import java.net.InetSocketAddress; -import java.util.Date; - -public class NoStorageTest { - final String DBNAME = "testdb"; - final String TBNAME = "testtb"; - final String DDL = "CREATE TABLE `testtb` (\n" + - " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + - " `name` varchar(2048) DEFAULT NULL,\n" + - " `datachange_lasttime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最晚更新时间',\n" + - " `otter_testcol` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol1` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol2` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol3` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol4` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol5` varchar(45) DEFAULT NULL,\n" + - " PRIMARY KEY (`id`)\n" + - " ) ENGINE=InnoDB AUTO_INCREMENT=58333898 DEFAULT CHARSET=utf8mb4"; - @Test - public void nostorage() { - MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); - TableMetaCacheWithStorage tableMetaCacheWithStorage = new TableMetaCacheWithStorage(connection, null); - EntryPosition entryPosition = new EntryPosition(); - entryPosition.setTimestamp(new Date().getTime()); - String fullTableName = DBNAME + "." + TBNAME; - tableMetaCacheWithStorage.apply(entryPosition, fullTableName, DDL, null); - entryPosition.setTimestamp(new Date().getTime() + 1000L); - TableMeta result = tableMetaCacheWithStorage.getTableMeta(DBNAME, TBNAME, false, entryPosition); - assert result.getDdl().equalsIgnoreCase(DDL); - } -} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java deleted file mode 100644 index 0ec16607..00000000 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/StorageTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaCallback; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaStorageFactory; -import com.alibaba.otter.canal.protocol.position.EntryPosition; -import com.alibaba.otter.canal.protocol.position.Position; -import org.junit.Test; - -import java.net.InetSocketAddress; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -public class StorageTest { - - final String DBNAME = "testdb"; - final String TBNAME = "testtb"; - final String DDL = "CREATE TABLE `testtb` (\n" + - " `id` int(11) NOT NULL AUTO_INCREMENT,\n" + - " `name` varchar(2048) DEFAULT NULL,\n" + - " `datachange_lasttime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最晚更新时间',\n" + - " `otter_testcol` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol1` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol2` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol3` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol4` varchar(45) DEFAULT NULL,\n" + - " `otter_testcol5` varchar(45) DEFAULT NULL,\n" + - " PRIMARY KEY (`id`)\n" + - " ) ENGINE=InnoDB AUTO_INCREMENT=58333898 DEFAULT CHARSET=utf8mb4"; - - @Test - public void storage() { - - MySqlTableMetaStorageFactory factory = new MySqlTableMetaStorageFactory(new MySqlTableMetaCallback() { - @Override - public void save(String dbAddress, String schema, String table, String ddl, Long timestamp) { - - } - - @Override - public List fetch(String dbAddress, String dbName) { - TableMetaEntry tableMeta = new TableMetaEntry(); - tableMeta.setSchema(DBNAME); - tableMeta.setTable(TBNAME); - tableMeta.setDdl(DDL); - tableMeta.setTimestamp(new Date().getTime()); - List entries = new ArrayList(); - entries.add(tableMeta); - return entries; - } - - @Override - public List fetch(String dbAddress, String dbName, String tableName) { - TableMetaEntry tableMeta = new TableMetaEntry(); - tableMeta.setSchema(DBNAME); - tableMeta.setTable(TBNAME); - tableMeta.setDdl(DDL); - tableMeta.setTimestamp(new Date().getTime()); - List entries = new ArrayList(); - entries.add(tableMeta); - return entries; - } - }, DBNAME); - MysqlConnection connection = new MysqlConnection(new InetSocketAddress("127.0.0.1", 3306), "root", "hello"); - TableMetaCacheWithStorage tableMetaCacheWithStorage = new TableMetaCacheWithStorage(connection, factory.getTableMetaStorage()); - EntryPosition entryPosition = new EntryPosition(); - entryPosition.setTimestamp(new Date().getTime()); - String fullTableName = DBNAME + "." + TBNAME; - tableMetaCacheWithStorage.apply(entryPosition, fullTableName, DDL, null); - - entryPosition.setTimestamp(new Date().getTime() + 1000L); - TableMeta result = tableMetaCacheWithStorage.getTableMeta(DBNAME, TBNAME, false, entryPosition); - assert result.getDdl().equalsIgnoreCase(DDL); - } -} From b53131553ed974544f881791302f53a939ed45f6 Mon Sep 17 00:00:00 2001 From: "charles.lin" <848497337@qq.com> Date: Thu, 9 Aug 2018 02:01:44 +0800 Subject: [PATCH 050/360] Commit test case --- .../mysql/RdsBinlogEventParserProxyTest.java | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java index 18a217b8..c552a787 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java @@ -58,36 +58,6 @@ public class RdsBinlogEventParserProxyTest { controller.setSecretkey(""); controller.setBatchSize(4); // controller.setRdsOpenApiUrl("https://rds.aliyuncs.com/"); - controller.setTableMetaStorageFactory(new MySqlTableMetaStorageFactory(new MySqlTableMetaCallback() { - @Override - public void save(String dbAddress, String schema, String table, String ddl, Long timestamp) { - - } - - @Override - public List fetch(String dbAddress, String dbName) { - TableMetaEntry tableMeta = new TableMetaEntry(); - tableMeta.setSchema(DBNAME); - tableMeta.setTable(TBNAME); - tableMeta.setDdl(DDL); - tableMeta.setTimestamp(new Date().getTime()); - List entries = new ArrayList(); - entries.add(tableMeta); - return entries; - } - - @Override - public List fetch(String dbAddress, String dbName, String tableName) { - TableMetaEntry tableMeta = new TableMetaEntry(); - tableMeta.setSchema(DBNAME); - tableMeta.setTable(TBNAME); - tableMeta.setDdl(DDL); - tableMeta.setTimestamp(new Date().getTime()); - List entries = new ArrayList(); - entries.add(tableMeta); - return entries; - } - }, DBNAME)); controller.setEventSink(new AbstractCanalEventSinkTest>() { @Override From 8ce320148cf57c54215e83af32e0244079fc902e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 9 Aug 2018 22:20:54 +0800 Subject: [PATCH 051/360] fixed code --- .../mysql/MysqlMultiStageCoprocessor.java | 11 +- .../inbound/mysql/dbsync/LogEventConvert.java | 11 +- .../inbound/mysql/dbsync/TableMetaCache.java | 7 +- .../mysql/rds/BinlogDownloadQueue.java | 98 ++++----- .../mysql/rds/RdsBinlogEventParserProxy.java | 38 ++-- .../inbound/mysql/rds/RdsBinlogOpenApi.java | 37 ++-- .../mysql/rds/RdsLocalBinlogEventParser.java | 39 ++-- .../tablemeta/HistoryTableMetaCache.java | 191 ------------------ .../tablemeta/TableMetaCacheInterface.java | 20 -- .../tablemeta/TableMetaCacheWithStorage.java | 105 ---------- .../mysql/tablemeta/TableMetaEntry.java | 55 ----- .../mysql/tablemeta/TableMetaStorage.java | 18 -- .../tablemeta/TableMetaStorageFactory.java | 9 - .../exception/CacheConnectionNull.java | 9 - .../exception/NoHistoryException.java | 21 -- .../impl/mysql/MySqlTableMetaCallback.java | 14 -- .../impl/mysql/MySqlTableMetaStorage.java | 48 ----- .../mysql/MySqlTableMetaStorageFactory.java | 26 --- .../mysql/RdsBinlogEventParserProxyTest.java | 5 - 19 files changed, 121 insertions(+), 641 deletions(-) delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java delete mode 100644 parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index e93a1d20..6acc5056 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -203,14 +203,15 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement try { LogBuffer buffer = event.getBuffer(); if (StringUtils.isNotEmpty(event.getBinlogFileName()) - && (context.getLogPosition() == null - || !context.getLogPosition().getFileName().equals(event.getBinlogFileName()))) { + && (context.getLogPosition() == null || !context.getLogPosition() + .getFileName() + .equals(event.getBinlogFileName()))) { // set roate binlog file name - if (context.getLogPosition() == null){ + if (context.getLogPosition() == null) { context.setLogPosition(new LogPosition(event.getBinlogFileName(), 0)); - }else{ + } else { context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition() - .getPosition())); + .getPosition())); } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index f25e80f4..359e10bd 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -10,9 +10,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; @@ -90,8 +87,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private volatile AviaterRegexFilter nameFilter; // 运行时引用可能会有变化,比如规则发生变化时 private volatile AviaterRegexFilter nameBlackFilter; - - private TableMetaCacheInterface tableMetaCache; + private TableMetaCache tableMetaCache; private String binlogFileName = "mysql-bin.000001"; private Charset charset = Charset.defaultCharset(); @@ -268,8 +264,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar if (!isSeek) { // 使用新的表结构元数据管理方式 EntryPosition position = createPosition(event.getHeader()); - String fulltbName = schemaName+"."+tableName; - tableMetaCache.apply(position, fulltbName, queryString, null); + tableMetaCache.apply(position, event.getDbName(), queryString, null); } Header header = createHeader(event.getHeader(), schemaName, tableName, type); @@ -944,7 +939,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar this.nameBlackFilter = nameBlackFilter; } - public void setTableMetaCache(TableMetaCacheInterface tableMetaCache) { + public void setTableMetaCache(TableMetaCache tableMetaCache) { this.tableMetaCache = tableMetaCache; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java index 5c782aa1..4c1cef3c 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java @@ -6,7 +6,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaCacheInterface; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.parse.driver.mysql.packets.server.FieldPacket; @@ -30,7 +29,7 @@ import com.google.common.cache.LoadingCache; * @author jianghang 2013-1-17 下午10:15:16 * @version 1.0.0 */ -public class TableMetaCache implements TableMetaCacheInterface { +public class TableMetaCache { public static final String COLUMN_NAME = "COLUMN_NAME"; public static final String COLUMN_TYPE = "COLUMN_TYPE"; @@ -100,10 +99,6 @@ public class TableMetaCache implements TableMetaCacheInterface { String createDDL = packet.getFieldValues().get(1); MemoryTableMeta memoryTableMeta = new MemoryTableMeta(); memoryTableMeta.apply(DatabaseTableMeta.INIT_POSITION, schema, createDDL, null); - String[] strings = table.split("\\."); - if (strings.length > 1) { - table = strings[1]; - } TableMeta tableMeta = memoryTableMeta.find(schema, table); return tableMeta.getFields(); } else { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java index d95c8af1..d1716e18 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java @@ -1,7 +1,17 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds; -import java.io.*; -import java.util.*; +import io.netty.handler.codec.http.HttpResponseStatus; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -20,29 +30,27 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; -import io.netty.handler.codec.http.HttpResponseStatus; - /** * @author chengjin.lyf on 2018/8/7 下午3:10 * @since 1.0.25 */ public class BinlogDownloadQueue { - private static final Logger logger = LoggerFactory.getLogger(BinlogDownloadQueue.class); - private static final int TIMEOUT = 10000; + private static final Logger logger = LoggerFactory.getLogger(BinlogDownloadQueue.class); + private static final int TIMEOUT = 10000; private LinkedBlockingQueue downloadQueue = new LinkedBlockingQueue(); - private LinkedBlockingQueue taskQueue = new LinkedBlockingQueue(); - private LinkedList binlogList; - private final int batchSize; - private Thread downloadThread; - public boolean running = true; - private final String destDir; - private String hostId; - private int currentSize; - private String lastDownload; + private LinkedBlockingQueue taskQueue = new LinkedBlockingQueue(); + private LinkedList binlogList; + private final int batchSize; + private Thread downloadThread; + public boolean running = true; + private final String destDir; + private String hostId; + private int currentSize; + private String lastDownload; - public BinlogDownloadQueue(List downloadQueue, int batchSize, String destDir) throws IOException { + public BinlogDownloadQueue(List downloadQueue, int batchSize, String destDir) throws IOException{ this.binlogList = new LinkedList(downloadQueue); this.batchSize = batchSize; this.destDir = destDir; @@ -51,12 +59,13 @@ public class BinlogDownloadQueue { cleanDir(); } - private void prepareBinlogList(){ + private void prepareBinlogList() { for (BinlogFile binlog : this.binlogList) { String fileName = StringUtils.substringBetween(binlog.getDownloadLink(), "mysql-bin.", "?"); binlog.setFileName(fileName); } Collections.sort(this.binlogList, new Comparator() { + @Override public int compare(BinlogFile o1, BinlogFile o2) { return o1.getFileName().compareTo(o2.getFileName()); @@ -78,35 +87,34 @@ public class BinlogDownloadQueue { downloadThread.start(); } - public BinlogFile tryOne() throws IOException { BinlogFile binlogFile = binlogList.poll(); download(binlogFile); hostId = binlogFile.getHostInstanceID(); - this.currentSize ++; + this.currentSize++; return binlogFile; } - public void notifyNotMatch(){ - this.currentSize --; + public void notifyNotMatch() { + this.currentSize--; filter(hostId); } - private void filter(String hostInstanceId){ + private void filter(String hostInstanceId) { Iterator it = binlogList.iterator(); - while (it.hasNext()){ + while (it.hasNext()) { BinlogFile bf = it.next(); - if(bf.getHostInstanceID().equalsIgnoreCase(hostInstanceId)){ + if (bf.getHostInstanceID().equalsIgnoreCase(hostInstanceId)) { it.remove(); - }else{ + } else { hostId = bf.getHostInstanceID(); } } } - public boolean isLastFile(String fileName){ + public boolean isLastFile(String fileName) { String needCompareName = lastDownload; - if (StringUtils.isNotEmpty(needCompareName) && StringUtils.endsWith(needCompareName, "tar")){ + if (StringUtils.isNotEmpty(needCompareName) && StringUtils.endsWith(needCompareName, "tar")) { needCompareName = needCompareName.substring(0, needCompareName.indexOf(".")); } return fileName.equalsIgnoreCase(needCompareName) && binlogList.isEmpty(); @@ -115,27 +123,27 @@ public class BinlogDownloadQueue { public void prepare() throws InterruptedException { for (int i = this.currentSize; i < batchSize && !binlogList.isEmpty(); i++) { BinlogFile binlogFile = null; - while (!binlogList.isEmpty()){ + while (!binlogList.isEmpty()) { binlogFile = binlogList.poll(); - if (!binlogFile.getHostInstanceID().equalsIgnoreCase(hostId)){ + if (!binlogFile.getHostInstanceID().equalsIgnoreCase(hostId)) { continue; } break; } - if (binlogFile == null){ + if (binlogFile == null) { break; } this.downloadQueue.put(binlogFile); this.lastDownload = "mysql-bin." + binlogFile.getFileName(); - this.currentSize ++; + this.currentSize++; } } - public void downOne(){ - this.currentSize --; + public void downOne() { + this.currentSize--; } - public void release(){ + public void release() { running = false; this.currentSize = 0; binlogList.clear(); @@ -146,21 +154,17 @@ public class BinlogDownloadQueue { String downloadLink = binlogFile.getDownloadLink(); String fileName = binlogFile.getFileName(); HttpGet httpGet = new HttpGet(downloadLink); - CloseableHttpClient httpClient = HttpClientBuilder.create() - .setMaxConnPerRoute(50) - .setMaxConnTotal(100) - .build(); + CloseableHttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(50).setMaxConnTotal(100).build(); RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(TIMEOUT) - .setConnectionRequestTimeout(TIMEOUT) - .setSocketTimeout(TIMEOUT) - .build(); + .setConnectTimeout(TIMEOUT) + .setConnectionRequestTimeout(TIMEOUT) + .setSocketTimeout(TIMEOUT) + .build(); httpGet.setConfig(requestConfig); HttpResponse response = httpClient.execute(httpGet); int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpResponseStatus.OK.code()) { - throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:" - + statusCode); + throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:" + statusCode); } saveFile(new File(destDir), "mysql-bin." + fileName, response); } @@ -168,7 +172,7 @@ public class BinlogDownloadQueue { private static void saveFile(File parentFile, String fileName, HttpResponse response) throws IOException { InputStream is = response.getEntity().getContent(); long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue()); - if(response.getFirstHeader("Content-Disposition")!=null){ + if (response.getFirstHeader("Content-Disposition") != null) { fileName = response.getFirstHeader("Content-Disposition").getValue(); fileName = StringUtils.substringAfter(fileName, "filename="); } @@ -243,11 +247,11 @@ public class BinlogDownloadQueue { while (running) { try { BinlogFile binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS); - if (binlogFile != null){ + if (binlogFile != null) { download(binlogFile); } Runnable runnable = taskQueue.poll(5000, TimeUnit.MILLISECONDS); - if (runnable != null){ + if (runnable != null) { runnable.run(); } } catch (Exception e) { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java index 5bcc8688..88caca53 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java @@ -14,25 +14,26 @@ import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; */ public class RdsBinlogEventParserProxy extends MysqlEventParser { - private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址 - private String accesskey; // 云账号的ak - private String secretkey; // 云账号sk - private String instanceId; // rds实例id - private Long startTime; - private Long endTime; - private String directory; //binlog 目录 - private int batchSize = 4; //最多下载的binlog文件数量 + private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址 + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id + private Long startTime; + private Long endTime; + private String directory; // binlog + // 目录 + private int batchSize = 4; // 最多下载的binlog文件数量 private RdsLocalBinlogEventParser rdsBinlogEventParser = new RdsLocalBinlogEventParser(); - private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { + private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "rds-binlog-daemon-thread"); - t.setDaemon(true); - return t; - } - }); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, "rds-binlog-daemon-thread"); + t.setDaemon(true); + return t; + } + }); @Override public void start() { @@ -62,9 +63,11 @@ public class RdsBinlogEventParserProxy extends MysqlEventParser { rdsBinlogEventParser.setDirectory(directory); rdsBinlogEventParser.setBatchSize(batchSize); rdsBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() { + @Override public void onFinish() { executorService.execute(new Runnable() { + @Override public void run() { rdsBinlogEventParser.stop(); @@ -124,17 +127,14 @@ public class RdsBinlogEventParserProxy extends MysqlEventParser { this.rdsOpenApiUrl = rdsOpenApiUrl; } - public void setAccesskey(String accesskey) { this.accesskey = accesskey; } - public void setSecretkey(String secretkey) { this.secretkey = secretkey; } - public void setInstanceId(String instanceId) { this.instanceId = instanceId; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java index dced6d8d..356a3520 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java @@ -1,9 +1,5 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsItem; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFilesRequest; import io.netty.handler.codec.http.HttpResponseStatus; import java.io.BufferedOutputStream; @@ -17,7 +13,14 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.UUID; import javax.crypto.Mac; import javax.crypto.SecretKey; @@ -40,6 +43,10 @@ import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsItem; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFilesRequest; /** * @author agapple 2017年10月14日 下午1:53:52 @@ -55,11 +62,10 @@ public class RdsBinlogOpenApi { private static final String API_VERSION = "2014-08-15"; private static final String SIGNATURE_VERSION = "1.0"; - - public static List listBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, - Date endTime) { + public static List listBinlogFiles(String url, String ak, String sk, String dbInstanceId, + Date startTime, Date endTime) { DescribeBinlogFilesRequest request = new DescribeBinlogFilesRequest(); - if (StringUtils.isNotEmpty(url)){ + if (StringUtils.isNotEmpty(url)) { try { URI uri = new URI(url); request.setEndPoint(uri.getHost()); @@ -76,12 +82,12 @@ public class RdsBinlogOpenApi { request.setAccessKeySecret(sk); DescribeBinlogFileResult result = null; int retryTime = 3; - while (true){ - try{ + while (true) { + try { result = request.doAction(); break; - }catch (Exception e){ - if(retryTime-- <= 0){ + } catch (Exception e) { + if (retryTime-- <= 0) { throw new RuntimeException(e); } try { @@ -90,17 +96,16 @@ public class RdsBinlogOpenApi { } } } - if (result == null){ + if (result == null) { return Collections.EMPTY_LIST; } RdsItem rdsItem = result.getItems(); - if (rdsItem != null){ + if (rdsItem != null) { return rdsItem.getBinLogFile(); } return Collections.EMPTY_LIST; } - public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, Date endTime, File destDir) throws Throwable { int pageSize = 100; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java index 40cb3b13..05a1935e 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java @@ -28,15 +28,15 @@ import com.alibaba.otter.canal.protocol.position.LogPosition; */ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser, LocalBinLogConnection.FileParserListener { - private String url = "https://rds.aliyuncs.com/"; // openapi地址 - private String accesskey; // 云账号的ak - private String secretkey; // 云账号sk - private String instanceId; // rds实例id - private Long startTime; - private Long endTime; + private String url = "https://rds.aliyuncs.com/"; // openapi地址 + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id + private Long startTime; + private Long endTime; private BinlogDownloadQueue binlogDownloadQueue; private ParseFinishListener finishListener; - private int batchSize; + private int batchSize; public RdsLocalBinlogEventParser(){ } @@ -59,7 +59,8 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements } long startTimeInMill = entryPosition.getTimestamp(); startTime = startTimeInMill; - List binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, accesskey, + List binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, + accesskey, secretkey, instanceId, new Date(startTime), @@ -132,27 +133,22 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements } } - public void setAccesskey(String accesskey) { this.accesskey = accesskey; } - public void setSecretkey(String secretkey) { this.secretkey = secretkey; } - public void setInstanceId(String instanceId) { this.instanceId = instanceId; } - public void setStartTime(Long startTime) { this.startTime = startTime; } - public void setEndTime(Long endTime) { this.endTime = endTime; } @@ -162,20 +158,24 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements try { binlogDownloadQueue.downOne(); File needDeleteFile = new File(directory + File.separator + fileName); - if (needDeleteFile.exists()){ + if (needDeleteFile.exists()) { needDeleteFile.delete(); } // 处理下logManager位点问题 LogPosition logPosition = logPositionManager.getLatestIndexBy(destination); EntryPosition position = logPosition.getPostion(); - if (position != null){ + if (position != null) { LogPosition newLogPosition = new LogPosition(); String journalName = position.getJournalName(); int sepIdx = journalName.indexOf("."); - String fileIndex = journalName.substring(sepIdx+1); + String fileIndex = journalName.substring(sepIdx + 1); int index = NumberUtils.toInt(fileIndex) + 1; - String newJournalName = journalName.substring(0, sepIdx) + "." + StringUtils.leftPad(String.valueOf(index), fileIndex.length(), "0"); - newLogPosition.setPostion(new EntryPosition(newJournalName, 4L, position.getTimestamp(), position.getServerId())); + String newJournalName = journalName.substring(0, sepIdx) + "." + + StringUtils.leftPad(String.valueOf(index), fileIndex.length(), "0"); + newLogPosition.setPostion(new EntryPosition(newJournalName, + 4L, + position.getTimestamp(), + position.getServerId())); newLogPosition.setIdentity(logPosition.getIdentity()); logPositionManager.persistLogPosition(destination, newLogPosition); } @@ -202,7 +202,8 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements this.finishListener = finishListener; } - public interface ParseFinishListener{ + public interface ParseFinishListener { + void onFinish(); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java deleted file mode 100644 index 0964627d..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/HistoryTableMetaCache.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.driver.mysql.packets.server.ResultSetPacket; -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; -import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.TableMetaCache; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.CacheConnectionNull; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception.NoHistoryException; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; - -import java.io.IOException; -import java.util.*; - -public class HistoryTableMetaCache { - private TableMetaStorage tableMetaStorage; - private MysqlConnection metaConnection; - private LoadingCache> cache; // 第一层:数据库名.表名,第二层时间戳,TableMeta - - public HistoryTableMetaCache() { - cache = CacheBuilder.newBuilder().build(new CacheLoader>() { - @Override - public Map load(String tableName) throws Exception { - Long timestamp = new Date().getTime(); - String[] strs = tableName.split("\\."); - String schema = strs[0]; - if (tableMetaStorage != null) { - init(tableMetaStorage.fetchByTableName(tableName)); // 从存储中读取表的历史ddl - } - ResultSetPacket resultSetPacket = connectionQuery("show create table " + tableName); // 获取当前ddl - String currentDdl = resultSetPacket.getFieldValues().get(1); - if (cache.asMap().containsKey(tableName)) { - Map tableMetaMap = cache.getUnchecked(tableName); - if (tableMetaMap.isEmpty()) { - put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema,取时间为当前时间-1s - } else { // 如果table存在历史 - Iterator iterator = tableMetaMap.keySet().iterator(); - Long firstTimestamp = iterator.next(); - TableMeta first = tableMetaMap.get(firstTimestamp); // 拿第一条ddl - if (!first.getDdl().equalsIgnoreCase(currentDdl)) { // 当前ddl与历史第一条不一致,放入当前ddl - put(schema, tableName, currentDdl, calculateNewTimestamp(firstTimestamp)); // 计算放入的timestamp,设为第一条时间+1s - } - } - } else { - put(schema, tableName, currentDdl, timestamp - 1000L); // 放入当前schema - } - return cache.get(tableName); - } - }); - } - - public void init(List entries) throws IOException { - if (entries == null) { - return; - } - for (TableMetaEntry entry : entries) { - try { - put(entry.getSchema(), entry.getTable(), entry.getDdl(), entry.getTimestamp()); - } catch (CacheConnectionNull cacheConnectionNull) { - cacheConnectionNull.printStackTrace(); - } - } - } - - public TableMeta put(String schema, String table, String ddl, Long timestamp) throws CacheConnectionNull, IOException { - ResultSetPacket resultSetPacket; - if (!(ddl.contains("CREATE TABLE") || ddl.contains("create table"))) { // 尝试直接从数据库拉取CREATE TABLE的DDL - resultSetPacket = connectionQuery("show create table " + table); - ddl = resultSetPacket.getFieldValues().get(1); - } else { // CREATE TABLE 的 DDL - resultSetPacket = new ResultSetPacket(); - List fields = new ArrayList(); - String[] strings = table.split("\\."); - String shortTable = table; - if (strings.length > 1) { - shortTable = strings[1]; - } - fields.add(0, shortTable); - fields.add(1, ddl); - resultSetPacket.setFieldValues(fields); - if (metaConnection != null) { - resultSetPacket.setSourceAddress(metaConnection.getAddress()); - } - } - Map tableMetaMap; - if (!cache.asMap().containsKey(table)) { - tableMetaMap = new TreeMap(new Comparator() { - @Override - public int compare(Long o1, Long o2) { - return o2.compareTo(o1); - } - }); - cache.put(table, tableMetaMap); - } else { - tableMetaMap = cache.getUnchecked(table); - } - eliminate(tableMetaMap); // 淘汰旧的TableMeta - TableMeta tableMeta = new TableMeta(schema, table, TableMetaCache.parseTableMeta(schema, table, resultSetPacket)); - if (tableMeta.getDdl() == null) { // 生成的TableMeta有时DDL为null - tableMeta.setDdl(ddl); - } - tableMetaMap.put(timestamp, tableMeta); - return tableMeta; - } - - public TableMeta get(String schema, String table, Long timestamp) throws NoHistoryException, CacheConnectionNull { - Map tableMetaMap = cache.getUnchecked(table); - Iterator iterator = tableMetaMap.keySet().iterator(); - Long selected = null; - while(iterator.hasNext()) { - Long temp = iterator.next(); - if (timestamp > temp) { - selected = temp; - break; - } - } - - if (selected == null) { - iterator = tableMetaMap.keySet().iterator(); - if (iterator.hasNext()) { - selected = iterator.next(); - } else { - throw new NoHistoryException(schema, table); - } - } - - return tableMetaMap.get(selected); - } - - public void clearTableMeta() { - cache.invalidateAll(); - } - - public void clearTableMetaWithSchemaName(String schema) { - for (String tableName : cache.asMap().keySet()) { - String[] strs = tableName.split("\\."); - if (schema.equalsIgnoreCase(strs[0])) { - cache.invalidate(tableName); - } - } - } - - public void clearTableMeta(String schema, String table) { - if (!table.contains(".")) { - table = schema+"."+table; - } - cache.invalidate(table); - } - - // eliminate older table meta in cache - private void eliminate(Map tableMetaMap) { - int MAX_CAPABILITY = 20; - if (tableMetaMap.keySet().size() < MAX_CAPABILITY) { - return; - } - Iterator iterator = tableMetaMap.keySet().iterator(); - while(iterator.hasNext()) { - iterator.next(); - } - iterator.remove(); - } - - private Long calculateNewTimestamp(Long oldTimestamp) { - return oldTimestamp + 1000; - } - - private ResultSetPacket connectionQuery(String query) throws CacheConnectionNull, IOException { - if (metaConnection == null) { - throw new CacheConnectionNull(); - } - try { - return metaConnection.query(query); - } catch (IOException e) { - try { - metaConnection.reconnect(); - return metaConnection.query(query); - } catch (IOException e1) { - throw e1; - } - } - } - - public void setMetaConnection(MysqlConnection metaConnection) { - this.metaConnection = metaConnection; - } - - public void setTableMetaStorage(TableMetaStorage tableMetaStorage) { - this.tableMetaStorage = tableMetaStorage; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java deleted file mode 100644 index 5196ee08..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheInterface.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.protocol.position.EntryPosition; - -public interface TableMetaCacheInterface { - - TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position); - - void clearTableMeta(); - - void clearTableMetaWithSchemaName(String schema); - - void clearTableMeta(String schema, String table); - - boolean apply(EntryPosition position, String schema, String ddl, String extra); - - boolean isOnRDS(); - -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java deleted file mode 100644 index eb43158b..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaCacheWithStorage.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import com.alibaba.otter.canal.parse.inbound.TableMeta; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlConnection; -import com.alibaba.otter.canal.protocol.position.EntryPosition; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.InetSocketAddress; - -public class TableMetaCacheWithStorage implements TableMetaCacheInterface { - - private static Logger logger = LoggerFactory.getLogger(TableMetaCacheWithStorage.class); - private TableMetaStorage tableMetaStorage; // TableMeta存储 - private HistoryTableMetaCache cache = new HistoryTableMetaCache(); // cache - - public TableMetaCacheWithStorage(MysqlConnection con, TableMetaStorage tableMetaStorage) { - this.tableMetaStorage = tableMetaStorage; - InetSocketAddress address = con.getAddress(); - this.tableMetaStorage.setDbAddress(address.getHostName()+":"+address.getPort()); - cache.setMetaConnection(con); - cache.setTableMetaStorage(tableMetaStorage); - if (tableMetaStorage != null) { - try { - cache.init(tableMetaStorage.fetch()); // 初始化,从存储拉取TableMeta - } catch (IOException e) { - logger.error(e.getMessage()); - } - } - } - - @Override - public boolean apply(EntryPosition position, String fullTableName, String ddl, String extra) { - String[] strs = fullTableName.split("\\."); - String schema = strs[0]; - if (schema.equalsIgnoreCase("null")) { // ddl schema为null,放弃处理 - return false; - } - try { - TableMeta tableMeta = cache.get(schema, fullTableName, position.getTimestamp()); - if (!compare(tableMeta, ddl)) { // 获取最近的TableMeta,进行比对 - TableMeta result = cache.put(schema, fullTableName, ddl, calTimestamp(position.getTimestamp())); - if (tableMetaStorage != null && result != null) { // 储存 - tableMetaStorage.store(schema, fullTableName, result.getDdl(), calTimestamp(position.getTimestamp())); - } - } - return true; - } catch (Exception e) { - logger.error(e.toString()); - } - - return false; - } - - @Override - public boolean isOnRDS() { - return false; - } - - /*** - * - * @param schema dbname - * @param table tablename - * @param useCache unused - * @param position timestamp - * @return - */ - @Override - public TableMeta getTableMeta(String schema, String table, boolean useCache, EntryPosition position) { - String fulltbName = schema + "." + table; - try { - return cache.get(schema, fulltbName, position.getTimestamp()); - } catch (Exception e) { - logger.error(e.toString()); - } - return null; - } - - @Override - public void clearTableMeta() { - cache.clearTableMeta(); - } - - @Override - public void clearTableMetaWithSchemaName(String schema) { - cache.clearTableMetaWithSchemaName(schema); - } - - @Override - public void clearTableMeta(String schema, String table) { - cache.clearTableMeta(schema, table); - } - - private boolean compare(TableMeta tableMeta, String ddl) { - if (tableMeta == null) { - return false; - } - return tableMeta.getDdl().equalsIgnoreCase(ddl); - } - - private Long calTimestamp(Long timestamp) { - return timestamp; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java deleted file mode 100644 index 03e63ed7..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaEntry.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import java.io.Serializable; - -public class TableMetaEntry implements Serializable { - - private static final long serialVersionUID = -1350200637109107904L; - - private String dbAddress; - private String schema; - private String table; - private String ddl; - private Long timestamp; - - - public String getSchema() { - return schema; - } - - public void setSchema(String schema) { - this.schema = schema; - } - - public String getTable() { - return table; - } - - public void setTable(String table) { - this.table = table; - } - - public String getDdl() { - return ddl; - } - - public void setDdl(String ddl) { - this.ddl = ddl; - } - - public Long getTimestamp() { - return timestamp; - } - - public void setTimestamp(Long timestamp) { - this.timestamp = timestamp; - } - - public String getDbAddress() { - return dbAddress; - } - - public void setDbAddress(String dbAddress) { - this.dbAddress = dbAddress; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java deleted file mode 100644 index ac9d7a0a..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorage.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -import java.util.List; - -public interface TableMetaStorage { - - void store(String schema, String table, String ddl, Long timestamp); - - List fetch(); - - List fetchByTableName(String tableName); - - String getDbName(); - - String getDbAddress(); - - void setDbAddress(String address); -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java deleted file mode 100644 index 0e532ebb..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/TableMetaStorageFactory.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta; - -public interface TableMetaStorageFactory { - - TableMetaStorage getTableMetaStorage(); - - String getDbName(); - -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java deleted file mode 100644 index 1dadd78a..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/CacheConnectionNull.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; - -public class CacheConnectionNull extends Exception{ - - @Override - public String toString() { - return "CacheConnectionNull"; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java deleted file mode 100644 index d8f1d16b..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/exception/NoHistoryException.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.exception; - -public class NoHistoryException extends Exception{ - - private String dbName; - private String tbName; - - public NoHistoryException(String dbName, String tbName) { - this.dbName = dbName; - this.tbName = tbName; - } - - public void printTableName() { - System.out.println(dbName+"."+tbName); - } - - @Override - public String toString() { - return "NioHistoryException: " + dbName + " " + tbName; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java deleted file mode 100644 index 661986a1..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaCallback.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; - -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; - -import java.util.List; - -public interface MySqlTableMetaCallback { - - void save(String dbAddress, String schema, String table,String ddl, Long timestamp); - - List fetch(String dbAddress, String dbName); - - List fetch(String dbAddress, String dbName, String tableName); -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java deleted file mode 100644 index f120ef80..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorage.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; - -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; - -import java.util.List; - -public class MySqlTableMetaStorage implements TableMetaStorage { - private MySqlTableMetaCallback mySqlTableMetaCallback; - private String dbName; - private String dbAddress; - - MySqlTableMetaStorage(MySqlTableMetaCallback callback, String dbName) { - mySqlTableMetaCallback = callback; - this.dbName = dbName; - } - - - @Override - public void store(String schema, String table, String ddl, Long timestamp) { - mySqlTableMetaCallback.save(dbAddress, schema, table, ddl, timestamp); - } - - @Override - public List fetch() { - return mySqlTableMetaCallback.fetch(dbAddress, dbName); - } - - @Override - public List fetchByTableName(String tableName) { - return mySqlTableMetaCallback.fetch(dbAddress, dbName, tableName); - } - - @Override - public String getDbName() { - return dbName; - } - - @Override - public String getDbAddress() { - return dbAddress; - } - - @Override - public void setDbAddress(String address) { - this.dbAddress = address; - } -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java deleted file mode 100644 index 0eee235d..00000000 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tablemeta/impl/mysql/MySqlTableMetaStorageFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql; - -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorage; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaStorageFactory; - -public class MySqlTableMetaStorageFactory implements TableMetaStorageFactory { - - private MySqlTableMetaCallback mySQLTableMetaCallback; - private String dbName; - - public MySqlTableMetaStorageFactory(MySqlTableMetaCallback callback, String dbName) { - mySQLTableMetaCallback = callback; - this.dbName = dbName; - } - - @Override - public TableMetaStorage getTableMetaStorage() { - return new MySqlTableMetaStorage(mySQLTableMetaCallback, dbName); - } - - @Override - public String getDbName() { - return dbName; - } - -} diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java index c552a787..986d4d6a 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java @@ -1,9 +1,7 @@ package com.alibaba.otter.canal.parse.inbound.mysql; import java.net.InetSocketAddress; -import java.util.ArrayList; import java.util.Calendar; -import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -12,9 +10,6 @@ import org.junit.Test; import com.alibaba.otter.canal.parse.helper.TimeoutChecker; import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.TableMetaEntry; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaCallback; -import com.alibaba.otter.canal.parse.inbound.mysql.tablemeta.impl.mysql.MySqlTableMetaStorageFactory; import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager; import com.alibaba.otter.canal.parse.stub.AbstractCanalEventSinkTest; import com.alibaba.otter.canal.parse.support.AuthenticationInfo; From d7f48f534bd70b041c8971f19ec01f2ed928fd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 9 Aug 2018 22:23:12 +0800 Subject: [PATCH 052/360] fixed encoding example --- .../canal/example/AbstractCanalClientTest.java | 14 ++++++++++++-- .../inbound/mysql/dbsync/LogEventConvert.java | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java index d1ddd88e..599b5da7 100644 --- a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java +++ b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java @@ -1,9 +1,9 @@ package com.alibaba.otter.canal.example; +import java.io.UnsupportedEncodingException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; -import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; @@ -254,7 +254,17 @@ public class AbstractCanalClientTest { protected void printColumn(List columns) { for (Column column : columns) { StringBuilder builder = new StringBuilder(); - builder.append(column.getName() + " : " + column.getValue()); + try { + if (StringUtils.containsIgnoreCase(column.getMysqlType(), "BLOB") + || StringUtils.containsIgnoreCase(column.getMysqlType(), "BINARY")) { + // get value bytes + builder.append(column.getName() + " : " + + new String(column.getValue().getBytes("ISO-8859-1"), "UTF-8")); + } else { + builder.append(column.getName() + " : " + column.getValue()); + } + } catch (UnsupportedEncodingException e) { + } builder.append(" type=" + column.getMysqlType()); if (column.getUpdated()) { builder.append(" update=" + column.getUpdated()); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 359e10bd..2f9b5304 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -88,8 +88,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private volatile AviaterRegexFilter nameBlackFilter; private TableMetaCache tableMetaCache; - private String binlogFileName = "mysql-bin.000001"; - private Charset charset = Charset.defaultCharset(); private boolean filterQueryDcl = false; private boolean filterQueryDml = false; @@ -761,6 +759,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar } else { // byte数组,直接使用iso-8859-1保留对应编码,浪费内存 columnBuilder.setValue(new String((byte[]) value, ISO_8859_1)); + // columnBuilder.setValueBytes(ByteString.copyFrom((byte[]) + // value)); javaType = Types.BLOB; } break; From 1c245280c7fad1c924e99a3d895b17518adbf591 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Wed, 1 Aug 2018 08:17:55 +0800 Subject: [PATCH 053/360] netty3 future listener for metrics. --- .../listener/ChannelFutureAggregator.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java new file mode 100644 index 00000000..aa9b0da3 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -0,0 +1,42 @@ +package com.alibaba.otter.canal.server.netty.listener; + +import com.alibaba.otter.canal.protocol.CanalPacket; +import org.jboss.netty.channel.ChannelFuture; +import org.jboss.netty.channel.ChannelFutureListener; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author Chuanyi Li + */ +public class ChannelFutureAggregator implements ChannelFutureListener { + + private String dest; + + private CanalPacket.PacketType type; + + private int amount; + + private int errorCode; + + public ChannelFutureAggregator(String dest, CanalPacket.PacketType type, int amount) { + this(dest, type, amount, 0); + } + + public ChannelFutureAggregator(String dest, CanalPacket.PacketType type, int amount, int errorCode) { + this.dest = dest; + this.type = type; + this.amount = amount; + this.errorCode = errorCode; + } + + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + ConcurrentHashMap map = new ConcurrentHashMap(); + map.putIfAbsent("", ""); + } else { + Throwable t = future.getCause(); + } + } +} From bd14848600fa40729922b988041f5673994ae240 Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Thu, 2 Aug 2018 17:36:22 +0800 Subject: [PATCH 054/360] Builder of Aggregator. --- .../listener/ChannelFutureAggregator.java | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index aa9b0da3..e3f60dca 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -4,6 +4,7 @@ import com.alibaba.otter.canal.protocol.CanalPacket; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; /** @@ -39,4 +40,64 @@ public class ChannelFutureAggregator implements ChannelFutureListener { Throwable t = future.getCause(); } } + + public static class ClientRequestResult { + + private String dest; + + private CanalPacket.PacketType type; + + private int amount; + + private int errorCode; + + private ClientRequestResult() {} + + private ClientRequestResult(Builder builder) { + this.dest = builder.dest; + this.type = builder.type; + this.amount = builder.amount; + this.errorCode = builder.errorCode; + } + + private static class Builder { + + private String dest; + private CanalPacket.PacketType type; + private int amount; + private int errorCode; + + public Builder dest(String dest) { + this.dest = dest; + return this; + } + + public Builder type(CanalPacket.PacketType type) { + this.type = type; + return this; + } + + public Builder amount(int amount) { + this.amount = amount; + return this; + } + + public Builder errorCode(int errorCode) { + this.errorCode = errorCode; + return this; + } + + public Builder fromPrototype(ClientRequestResult prototype) { + dest = prototype.dest; + type = prototype.type; + amount = prototype.amount; + errorCode = prototype.errorCode; + return this; + } + + public ClientRequestResult build() { + return new ClientRequestResult(this); + } + } + } } From 782ef7fe14ca75752b821fe846fecd73058c6a5a Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Fri, 3 Aug 2018 17:32:48 +0800 Subject: [PATCH 055/360] Netty server/client profiler skeleton. --- .../netty/CanalServerWithNettyProfiler.java | 92 +++++++++++++ .../server/netty/ClientInstanceProfiler.java | 14 ++ .../netty/ClientInstanceProfilerFactory.java | 10 ++ .../server/netty/handler/SessionHandler.java | 1 + .../listener/ChannelFutureAggregator.java | 124 +++++++++++++----- 5 files changed, 206 insertions(+), 35 deletions(-) create mode 100644 server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java create mode 100644 server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java create mode 100644 server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java new file mode 100644 index 00000000..5b629909 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java @@ -0,0 +1,92 @@ +package com.alibaba.otter.canal.server.netty; + +import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; +import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator.ClientRequestResult; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Chuanyi Li + */ +public class CanalServerWithNettyProfiler { + + private static final ClientInstanceProfilerFactory DISABLED = new DefaultClientInstanceProfilerFactory(); + + private volatile ClientInstanceProfilerFactory factory; + + private final ConcurrentMap cliPfs; + + private final CanalServerWithEmbedded server; + + private static class SingletonHolder { + private static CanalServerWithNettyProfiler SINGLETON = new CanalServerWithNettyProfiler(); + } + + private CanalServerWithNettyProfiler() { + this.factory = DISABLED; + this.cliPfs = new ConcurrentHashMap(); + this.server = CanalServerWithEmbedded.instance(); + } + + public static CanalServerWithNettyProfiler profiler() { + return SingletonHolder.SINGLETON; + } + + public void profiling(String dest, ClientRequestResult result) { + if (isDisabled()) { + return; + } + ClientInstanceProfiler profiler = tryGet(dest); + if (profiler != null) { + profiler.profiling(result); + } + } + + /** + * Remove instance profiler for specified instance. + * Only accepted while instance is not running. + * @param dest canal instance destination + */ + public void remove(String dest) throws IllegalStateException { + if (isDisabled()) { + return; + } + synchronized (cliPfs) { + if (server.isStart(dest)) { + throw new IllegalStateException("Instance profiler should not be removed while running."); + } + cliPfs.remove(dest); + } + } + + public void setFactory(ClientInstanceProfilerFactory factory) { + this.factory = factory; + } + + private boolean isDisabled() { + return factory == DISABLED || factory == null; + } + + private ClientInstanceProfiler tryGet(String dest) { + //try fast get + ClientInstanceProfiler profiler = cliPfs.get(dest); + if (profiler == null) { + synchronized (cliPfs) { + if (server.isStart(dest)) { + // avoid overwriting + cliPfs.putIfAbsent(dest, factory.create()); + profiler = cliPfs.get(dest); + } + } + } + return profiler; + } + + private static class DefaultClientInstanceProfilerFactory implements ClientInstanceProfilerFactory { + @Override + public ClientInstanceProfiler create() { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java new file mode 100644 index 00000000..6c9ffc8b --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java @@ -0,0 +1,14 @@ +package com.alibaba.otter.canal.server.netty; + +import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator; + +/** + * @author Chuanyi Li + */ +public interface ClientInstanceProfiler { + + String getDestination(); + + void profiling(ChannelFutureAggregator.ClientRequestResult result); + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java new file mode 100644 index 00000000..30075ba1 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java @@ -0,0 +1,10 @@ +package com.alibaba.otter.canal.server.netty; + +/** + * @author Chuanyi Li + */ +public interface ClientInstanceProfilerFactory { + + ClientInstanceProfiler create(); + +} diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index 11bba043..7ea861d4 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -51,6 +51,7 @@ public class SessionHandler extends SimpleChannelHandler { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { logger.info("message receives in session handler..."); + long start = System.currentTimeMillis(); ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); Packet packet = Packet.parseFrom(buffer.readBytes(buffer.readableBytes()).array()); ClientIdentity clientIdentity = null; diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index e3f60dca..37ee88e2 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -1,103 +1,157 @@ package com.alibaba.otter.canal.server.netty.listener; import com.alibaba.otter.canal.protocol.CanalPacket; +import com.google.common.base.Preconditions; +import com.google.protobuf.GeneratedMessage; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; +import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler; /** * @author Chuanyi Li */ public class ChannelFutureAggregator implements ChannelFutureListener { - private String dest; + private ClientRequestResult result; - private CanalPacket.PacketType type; - - private int amount; - - private int errorCode; - - public ChannelFutureAggregator(String dest, CanalPacket.PacketType type, int amount) { - this(dest, type, amount, 0); + public ChannelFutureAggregator(String dest, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency) { + this(dest, request, type, amount, latency, 0); } - public ChannelFutureAggregator(String dest, CanalPacket.PacketType type, int amount, int errorCode) { - this.dest = dest; - this.type = type; - this.amount = amount; - this.errorCode = errorCode; + private ChannelFutureAggregator(String dest, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, int errorCode) { + this.result = new ClientRequestResult.Builder() + .dest(dest) + .type(type) + .request(request) + .amount(amount) + .latency(latency) + .errorCode(errorCode) + .build(); } @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - ConcurrentHashMap map = new ConcurrentHashMap(); - map.putIfAbsent("", ""); - } else { - Throwable t = future.getCause(); + public void operationComplete(ChannelFuture future) { + // profiling after I/O operation + if (future.getCause() != null) { + result.channelError = future.getCause(); } + profiler().profiling(result.dest, result); } + /** + * Client request result pojo + */ public static class ClientRequestResult { private String dest; - private CanalPacket.PacketType type; - - private int amount; - - private int errorCode; + private GeneratedMessage request; + private int amount; + private long latency; + private int errorCode; + private Throwable channelError; private ClientRequestResult() {} private ClientRequestResult(Builder builder) { - this.dest = builder.dest; - this.type = builder.type; + this.dest = Preconditions.checkNotNull(builder.dest); + this.type = Preconditions.checkNotNull(builder.type); + this.request = Preconditions.checkNotNull(builder.request); this.amount = builder.amount; + this.latency = builder.latency; this.errorCode = builder.errorCode; + this.channelError = builder.channelError; } - private static class Builder { + // auto-generated + public static class Builder { private String dest; private CanalPacket.PacketType type; + private GeneratedMessage request; private int amount; + private long latency; private int errorCode; + private Throwable channelError; - public Builder dest(String dest) { + Builder dest(String dest) { this.dest = dest; return this; } - public Builder type(CanalPacket.PacketType type) { + Builder type(CanalPacket.PacketType type) { this.type = type; return this; } - public Builder amount(int amount) { + Builder request(GeneratedMessage request) { + this.request = request; + return this; + } + + Builder amount(int amount) { this.amount = amount; return this; } - public Builder errorCode(int errorCode) { + Builder latency(long latency) { + this.latency = latency; + return this; + } + + Builder errorCode(int errorCode) { this.errorCode = errorCode; return this; } + public Builder channelError(Throwable channelError) { + this.channelError = channelError; + return this; + } + public Builder fromPrototype(ClientRequestResult prototype) { dest = prototype.dest; type = prototype.type; + request = prototype.request; amount = prototype.amount; + latency = prototype.latency; errorCode = prototype.errorCode; + channelError = prototype.channelError; return this; } - public ClientRequestResult build() { + ClientRequestResult build() { return new ClientRequestResult(this); } } + // getters + public String getDest() { + return dest; + } + + public CanalPacket.PacketType getType() { + return type; + } + + public GeneratedMessage getRequest() { + return request; + } + + public int getAmount() { + return amount; + } + + public long getLatency() { + return latency; + } + + public int getErrorCode() { + return errorCode; + } + + public Throwable getChannelError() { + return channelError; + } } } From 01e74c73e2b2c6572cdbfb7650aa254e68bcc2cb Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Fri, 3 Aug 2018 17:42:57 +0800 Subject: [PATCH 056/360] Reformat code. --- .../canal/server/netty/CanalServerWithNettyProfiler.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java index 5b629909..9e9d9a39 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java @@ -10,12 +10,9 @@ import java.util.concurrent.ConcurrentMap; */ public class CanalServerWithNettyProfiler { - private static final ClientInstanceProfilerFactory DISABLED = new DefaultClientInstanceProfilerFactory(); - + private static final ClientInstanceProfilerFactory DISABLED = new DefaultClientInstanceProfilerFactory(); private volatile ClientInstanceProfilerFactory factory; - private final ConcurrentMap cliPfs; - private final CanalServerWithEmbedded server; private static class SingletonHolder { From da5fb30191879fb3d5960b84722583e6deec8f2d Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Mon, 6 Aug 2018 08:12:59 +0800 Subject: [PATCH 057/360] canal server metrics refactor. --- .../common/utils/SerializedLongAdder.java | 31 ++++ .../parse/inbound/AbstractEventParser.java | 3 + .../mysql/AbstractMysqlEventParser.java | 18 ++- .../parse/inbound/mysql/MysqlConnection.java | 38 +++-- .../parse/inbound/mysql/MysqlEventParser.java | 15 +- .../mysql/MysqlMultiStageCoprocessor.java | 30 +++- .../prometheus/CanalInstanceExports.java | 49 +++++-- .../canal/prometheus/CanalServerExports.java | 4 - .../canal/prometheus/PrometheusService.java | 7 + .../prometheus/impl/EntrySinkCollector.java | 45 ++++++ .../impl/InboundThroughputAspect.java | 79 ----------- .../impl/InstanceMetaCollector.java | 2 +- .../prometheus/impl/MemoryStoreCollector.java | 9 +- .../prometheus/impl/MysqlParserCollector.java | 74 ++++++++++ .../impl/OutboundThroughputAspect.java | 80 ----------- ...PrometheusCanalEventDownStreamHandler.java | 2 +- .../PrometheusClientInstanceProfiler.java | 134 ++++++++++++++++++ ...ometheusClientInstanceProfilerFactory.java | 15 ++ .../src/main/resources/META-INF/aop.xml | 11 -- .../netty/CanalServerWithNettyProfiler.java | 58 +++++--- .../server/netty/ClientInstanceProfiler.java | 3 +- .../netty/ClientInstanceProfilerFactory.java | 2 +- .../server/netty/handler/SessionHandler.java | 1 + .../listener/ChannelFutureAggregator.java | 62 +++++--- .../canal/sink/entry/EntryEventSink.java | 13 +- 25 files changed, 526 insertions(+), 259 deletions(-) create mode 100644 common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java delete mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java delete mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java delete mode 100644 prometheus/src/main/resources/META-INF/aop.xml diff --git a/common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java b/common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java new file mode 100644 index 00000000..4d86a99e --- /dev/null +++ b/common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java @@ -0,0 +1,31 @@ +package com.alibaba.otter.canal.common.utils; + +/** + * 用于dump记录counter + * 这种场景下, + * 写操作>>读操作 + * dump线程为串行,读取counter时,并不严格要求最新值可见,使用普通long即可。 + * + * @author Chuanyi Li + */ +public class SerializedLongAdder { + + private long value = 0; + + public SerializedLongAdder(long initial) { + this.value = initial; + } + + public void add(long x) { + value += x; + } + + public void increment() { + value ++; + } + + public long get() { + return value; + } + +} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index 0034ba2b..62096b6a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -9,6 +9,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; +import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.math.RandomUtils; @@ -100,6 +101,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle + protected abstract BinlogParser buildParser(); protected abstract ErosaConnection buildErosaConnection(); @@ -643,4 +645,5 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle public void setServerId(long serverId) { this.serverId = serverId; } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index 17eb08f4..5209b178 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.parse.inbound.mysql; import java.nio.charset.Charset; +import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,6 +39,7 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { protected boolean filterRows = false; protected boolean filterTableError = false; protected boolean useDruidDdlFilter = true; + private final SerializedLongAdder eventsPublishBlockingTime = new SerializedLongAdder(0L); protected BinlogParser buildParser() { LogEventConvert convert = new LogEventConvert(); @@ -131,11 +133,13 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { } protected MultiStageCoprocessor buildMultiStageCoprocessor() { - return new MysqlMultiStageCoprocessor(parallelBufferSize, - parallelThreadSize, - (LogEventConvert) binlogParser, - transactionBuffer, - destination); + MysqlMultiStageCoprocessor mysqlMultiStageCoprocessor = new MysqlMultiStageCoprocessor(parallelBufferSize, + parallelThreadSize, + (LogEventConvert) binlogParser, + transactionBuffer, + destination); + mysqlMultiStageCoprocessor.setEventsPublishBlockingTime(eventsPublishBlockingTime); + return mysqlMultiStageCoprocessor; } // ============================ setter / getter ========================= @@ -203,5 +207,9 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { public void setTableMetaTSDBFactory(TableMetaTSDBFactory tableMetaTSDBFactory) { this.tableMetaTSDBFactory = tableMetaTSDBFactory; } + + public SerializedLongAdder getEventsPublishBlockingTime() { + return this.eventsPublishBlockingTime; + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index 5f681a54..c0c658fb 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -8,6 +8,7 @@ import java.nio.charset.Charset; import java.util.List; import java.util.concurrent.TimeUnit; +import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.slf4j.Logger; @@ -39,18 +40,19 @@ import com.taobao.tddl.dbsync.binlog.LogEvent; public class MysqlConnection implements ErosaConnection { - private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class); + 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; + 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秒 - protected int soTimeout = 60 * 60 * 1000; // 1小时 + private AuthenticationInfo authInfo; + protected int connTimeout = 5 * 1000; // 5秒 + protected int soTimeout = 60 * 60 * 1000; // 1小时 + private SerializedLongAdder receivedBinlogBytes; public MysqlConnection(){ } @@ -126,6 +128,7 @@ public class MysqlConnection implements ErosaConnection { decoder.handle(LogEvent.XID_EVENT); LogContext context = new LogContext(); while (fetcher.fetch()) { + accumulateReceivedBytes(fetcher.limit()); LogEvent event = null; event = decoder.decode(fetcher, context); @@ -148,6 +151,7 @@ public class MysqlConnection implements ErosaConnection { LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); LogContext context = new LogContext(); while (fetcher.fetch()) { + accumulateReceivedBytes(fetcher.limit()); LogEvent event = null; event = decoder.decode(fetcher, context); @@ -176,6 +180,7 @@ public class MysqlConnection implements ErosaConnection { LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); LogContext context = new LogContext(); while (fetcher.fetch()) { + accumulateReceivedBytes(fetcher.limit()); LogEvent event = null; event = decoder.decode(fetcher, context); @@ -206,6 +211,7 @@ public class MysqlConnection implements ErosaConnection { try { fetcher.start(connector.getChannel()); while (fetcher.fetch()) { + accumulateReceivedBytes(fetcher.limit()); LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); if (!coprocessor.publish(buffer)) { @@ -232,6 +238,7 @@ public class MysqlConnection implements ErosaConnection { try { fetcher.start(connector.getChannel()); while (fetcher.fetch()) { + accumulateReceivedBytes(fetcher.limit()); LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); if (!coprocessor.publish(buffer)) { @@ -346,7 +353,6 @@ public class MysqlConnection implements ErosaConnection { *
    3. net_read_timeout
    4. * * - * @param channel * @throws IOException */ private void updateSettings() throws IOException { @@ -465,6 +471,14 @@ public class MysqlConnection implements ErosaConnection { } } + private void accumulateReceivedBytes(long x) { + if (receivedBinlogBytes != null) { + receivedBinlogBytes.add(x); + } + } + + + public static enum BinlogFormat { STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED"); @@ -604,4 +618,8 @@ public class MysqlConnection implements ErosaConnection { this.authInfo = authInfo; } + public void setReceivedBinlogBytes(SerializedLongAdder receivedBinlogBytes) { + this.receivedBinlogBytes = receivedBinlogBytes; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index 8faaaa61..2b1e65e2 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicLong; +import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; @@ -68,8 +69,11 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 // update by yishun.chen,特殊异常处理参数 - private int dumpErrorCount = 0; // binlogDump失败异常计数 - private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 + private int dumpErrorCount = 0; // binlogDump失败异常计数 + private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 + + // instance received binlog bytes + private final SerializedLongAdder receivedBinlogBytes = new SerializedLongAdder(0L); protected ErosaConnection buildErosaConnection() { return buildMysqlConnection(this.runningInfo); @@ -313,6 +317,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE connection.getConnector().setSendBufferSize(sendBufferSize); connection.getConnector().setSoTimeout(defaultConnectionTimeoutInSeconds * 1000); connection.setCharset(connectionCharset); + connection.setReceivedBinlogBytes(receivedBinlogBytes); // 随机生成slaveId if (this.slaveId <= 0) { this.slaveId = generateUniqueServerId(); @@ -910,4 +915,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE this.dumpErrorCountThreshold = dumpErrorCountThreshold; } + + + public SerializedLongAdder getReceivedBinlogBytes() { + return this.receivedBinlogBytes; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 6acc5056..e348c386 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -4,6 +4,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.LockSupport; +import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; @@ -52,6 +53,7 @@ import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; */ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor { + private static final int maxFullTimes = 10; private LogEventConvert logEventConvert; private EventTransactionBuffer transactionBuffer; private ErosaConnection connection; @@ -63,6 +65,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement private ExecutorService stageExecutor; private String destination; private volatile CanalParseException exception; + private SerializedLongAdder eventsPublishBlockingTime; public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, EventTransactionBuffer transactionBuffer, String destination){ @@ -161,6 +164,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement throw exception; } boolean interupted = false; + long blockingStart = 0L; + int fullTimes = 0; do { try { long next = disruptorMsgBuffer.tryNext(); @@ -170,16 +175,34 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement event.setBinlogFileName(binlogFileName); } disruptorMsgBuffer.publish(next); + if (fullTimes > 0) { + eventsPublishBlockingTime.add(System.nanoTime() - blockingStart); + } break; } catch (InsufficientCapacityException e) { + if (fullTimes == 0) { + blockingStart = System.nanoTime(); + } // park - LockSupport.parkNanos(1L); + //LockSupport.parkNanos(1L); + applyWait(++fullTimes); interupted = Thread.interrupted(); } } while (!interupted && isStart()); return isStart(); } + // 处理无数据的情况,避免空循环挂死 + private void applyWait(int fullTimes) { + int newFullTimes = fullTimes > maxFullTimes ? maxFullTimes : fullTimes; + if (fullTimes <= 3) { // 3次以内 + Thread.yield(); + } else { // 超过3次,最多只sleep 1ms + LockSupport.parkNanos(100 * 1000L * newFullTimes); + } + + } + @Override public void reset() { if (isStart()) { @@ -189,6 +212,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement start(); } + private class SimpleParserStage implements EventHandler, LifecycleAware { private LogDecoder decoder; @@ -433,4 +457,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement this.connection = connection; } + public void setEventsPublishBlockingTime(SerializedLongAdder eventsPublishBlockingTime) { + this.eventsPublishBlockingTime = eventsPublishBlockingTime; + } + } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java index 8acf40b8..19759154 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java @@ -1,9 +1,7 @@ package com.alibaba.otter.canal.prometheus; import com.alibaba.otter.canal.instance.core.CanalInstance; -import com.alibaba.otter.canal.prometheus.impl.InstanceMetaCollector; -import com.alibaba.otter.canal.prometheus.impl.MemoryStoreCollector; -import com.alibaba.otter.canal.prometheus.impl.PrometheusCanalEventDownStreamHandler; +import com.alibaba.otter.canal.prometheus.impl.*; import com.alibaba.otter.canal.sink.CanalEventSink; import com.alibaba.otter.canal.sink.entry.EntryEventSink; import com.alibaba.otter.canal.store.CanalStoreException; @@ -15,30 +13,31 @@ import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.List; +import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler; + /** * @author Chuanyi Li */ public class CanalInstanceExports { - private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class); - - public static final String[] labels = {"destination"}; - - public static final List labelList = Collections.singletonList(labels[0]); - + private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class); + public static final String DESTINATION = "destination"; + public static final String[] DEST_LABELS = {DESTINATION}; + public static final List DEST_LABEL_LIST = Collections.singletonList(DESTINATION); private final String destination; - private Collector storeCollector; - private Collector delayCollector; - private Collector metaCollector; + private Collector sinkCollector; + private Collector parserCollector; private CanalInstanceExports(CanalInstance instance) { this.destination = instance.getDestination(); - initDelayGauge(instance); + initEventsMetrics(instance); initStoreCollector(instance); initMetaCollector(instance); + initSinkCollector(instance); + initParserCollector(instance); } @@ -48,6 +47,7 @@ public class CanalInstanceExports { } void register() { + profiler().start(destination); if (delayCollector != null) { delayCollector.register(); } @@ -57,9 +57,16 @@ public class CanalInstanceExports { if (metaCollector != null) { metaCollector.register(); } + if (sinkCollector != null) { + sinkCollector.register(); + } + if (parserCollector != null) { + parserCollector.register(); + } } void unregister() { + profiler().stop(destination); if (delayCollector != null) { CollectorRegistry.defaultRegistry.unregister(delayCollector); } @@ -69,9 +76,15 @@ public class CanalInstanceExports { if (metaCollector != null) { CollectorRegistry.defaultRegistry.unregister(metaCollector); } + if (sinkCollector != null) { + CollectorRegistry.defaultRegistry.unregister(sinkCollector); + } + if (parserCollector != null) { + CollectorRegistry.defaultRegistry.unregister(parserCollector); + } } - private void initDelayGauge(CanalInstance instance) { + private void initEventsMetrics(CanalInstance instance) { CanalEventSink sink = instance.getEventSink(); if (sink instanceof EntryEventSink) { EntryEventSink entryEventSink = (EntryEventSink) sink; @@ -95,4 +108,12 @@ public class CanalInstanceExports { private void initMetaCollector(CanalInstance instance) { metaCollector = new InstanceMetaCollector(instance); } + + private void initSinkCollector(CanalInstance instance) { + sinkCollector = new EntrySinkCollector(instance.getEventSink(), instance.getDestination()); + } + + private void initParserCollector(CanalInstance instance) { + parserCollector = new MysqlParserCollector(instance.getEventParser(), instance.getDestination()); + } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java index 8eef79ba..ded44fb8 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java @@ -1,7 +1,5 @@ package com.alibaba.otter.canal.prometheus; -import com.alibaba.otter.canal.prometheus.impl.InboundThroughputAspect; -import com.alibaba.otter.canal.prometheus.impl.OutboundThroughputAspect; /** * @author Chuanyi Li @@ -12,8 +10,6 @@ public class CanalServerExports { public static synchronized void initialize() { if (!initialized) { - InboundThroughputAspect.getCollector().register(); - OutboundThroughputAspect.getCollector().register(); initialized = true; } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index c7942235..2aa7dcd0 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -1,6 +1,8 @@ package com.alibaba.otter.canal.prometheus; import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.prometheus.impl.PrometheusClientInstanceProfilerFactory; +import com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler; import com.alibaba.otter.canal.spi.CanalMetricsService; import io.prometheus.client.exporter.HTTPServer; import io.prometheus.client.hotspot.DefaultExports; @@ -11,6 +13,9 @@ import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.DISABLED; +import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler; + /** * @author Chuanyi Li */ @@ -50,6 +55,7 @@ public class PrometheusService implements CanalMetricsService { DefaultExports.initialize(); // Canal server level exports CanalServerExports.initialize(); + profiler().setInstanceProfilerFactory(new PrometheusClientInstanceProfilerFactory()); } catch (Throwable t) { logger.warn("Unable to initialize server exports.", t); } @@ -66,6 +72,7 @@ public class PrometheusService implements CanalMetricsService { for (CanalInstanceExports ie : exports.values()) { ie.unregister(); } + profiler().setInstanceProfilerFactory(DISABLED); if (server != null) { server.stop(); } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java new file mode 100644 index 00000000..54775842 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java @@ -0,0 +1,45 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.sink.CanalEventSink; +import com.alibaba.otter.canal.sink.entry.EntryEventSink; +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; + +/** + * @author Chuanyi Li + */ +public class EntrySinkCollector extends Collector { + + private static final String SINK_BLOCKING_TIME = "canal_instance_sink_blocking_time"; + private final String sinkBlockTimeHelp; + private final List destLabelValues; + private final AtomicLong eventsSinkBlockingTime; + + + public EntrySinkCollector(CanalEventSink sink, String destination) { + if (!(sink instanceof EntryEventSink)) { + throw new IllegalArgumentException("CanalEventSink must be EntryEventSink"); + } + this.destLabelValues = Collections.singletonList(destination); + EntryEventSink entrySink = (EntryEventSink) sink; + this.sinkBlockTimeHelp = "Sink blocking time of instance " + destination; + eventsSinkBlockingTime = entrySink.getEventsSinkBlockingTime(); + } + + @Override public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily blockingCounter = new CounterMetricFamily(SINK_BLOCKING_TIME, + sinkBlockTimeHelp, DEST_LABEL_LIST); + blockingCounter.addMetric(destLabelValues, eventsSinkBlockingTime.doubleValue()); + mfs.add(blockingCounter); + return mfs; + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java deleted file mode 100644 index eabc256c..00000000 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InboundThroughputAspect.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.alibaba.otter.canal.prometheus.impl; - -import io.prometheus.client.Collector; -import io.prometheus.client.CounterMetricFamily; -import org.aspectj.lang.annotation.After; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.jctools.maps.ConcurrentAutoTable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; - -/** - * @author Chuanyi Li - */ -@Aspect -public class InboundThroughputAspect { - - private static final Logger logger = LoggerFactory.getLogger(InboundThroughputAspect.class); - - /** - * Support highly scalable counters - * @see ConcurrentAutoTable - */ - private static final ConcurrentAutoTable total = new ConcurrentAutoTable(); - - private static final Collector collector = new InboundThroughputCollector(); - - public static Collector getCollector() { - return collector; - } - - @Pointcut("call(byte[] com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))") - public void read() {} - - @Pointcut("call(void com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..)) ") - public void readBytes() {} - - //nested read, just eliminate them. - @Pointcut("withincode(* com.alibaba.otter.canal.parse.driver.mysql.socket.SocketChannel.read(..))") - public void nestedCall() {} - - @After("read() && !nestedCall() && args(len, ..)") - public void recordRead(int len) { - accumulateBytes(len); - } - - @After("readBytes() && !nestedCall() && args(.., len, timeout)") - public void recordReadBytes(int len, int timeout) { - accumulateBytes(len); - } - - private void accumulateBytes(int count) { - try { - total.add(count); - } catch (Throwable t) { - //Catch every Throwable, rather than break the business logic. - logger.warn("Error while accumulate inbound bytes.", t); - } - } - - public static class InboundThroughputCollector extends Collector { - - private InboundThroughputCollector() {} - - @Override - public List collect() { - List mfs = new ArrayList(); - CounterMetricFamily bytes = new CounterMetricFamily("canal_net_inbound_bytes", - "Total socket inbound bytes of canal server.", - total.get()); - mfs.add(bytes); - return mfs; - } - } - -} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java index 5562b8fd..2e8b7fc4 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java @@ -55,7 +55,7 @@ public class InstanceMetaCollector extends Collector { List subs = metaManager.listAllSubscribeInfo(destination); GaugeMetricFamily subscriptions = new GaugeMetricFamily( "canal_instance_subscription", - subsHelp, CanalInstanceExports.labelList); + subsHelp, CanalInstanceExports.DEST_LABEL_LIST); subscriptions.addMetric(Arrays.asList(destination), subs.size()); mfs.add(subscriptions); } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java index 79fdca4a..4c212c4c 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java @@ -20,15 +20,10 @@ import java.util.concurrent.atomic.AtomicLong; public class MemoryStoreCollector extends Collector { private static final Class clazz = MemoryEventStoreWithBuffer.class; - private final String destination; - private final AtomicLong putSequence; - private final AtomicLong ackSequence; - private final String putHelp; - private final String ackHelp; public MemoryStoreCollector(CanalEventStore store, String destination) { @@ -47,11 +42,11 @@ public class MemoryStoreCollector extends Collector { public List collect() { List mfs = new ArrayList(); CounterMetricFamily put = new CounterMetricFamily("canal_instance_store_produce_seq", - putHelp, Arrays.asList(CanalInstanceExports.labels)); + putHelp, Arrays.asList(CanalInstanceExports.DEST_LABELS)); put.addMetric(Collections.singletonList(destination), putSequence.doubleValue()); mfs.add(put); CounterMetricFamily ack = new CounterMetricFamily("canal_instance_store_consume_seq", - ackHelp, Arrays.asList(CanalInstanceExports.labels)); + ackHelp, Arrays.asList(CanalInstanceExports.DEST_LABELS)); ack.addMetric(Collections.singletonList(destination), ackSequence.doubleValue()); mfs.add(ack); return mfs; diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java new file mode 100644 index 00000000..02d35209 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java @@ -0,0 +1,74 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.common.utils.SerializedLongAdder; +import com.alibaba.otter.canal.parse.CanalEventParser; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; +import io.prometheus.client.GaugeMetricFamily; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DESTINATION; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; + +/** + * @author Chuanyi Li + */ +public class MysqlParserCollector extends Collector { + + private static final String PUBLISH_BLOCKING_TIME = "canal_instance_publish_blocking_time"; + private static final String RECEIVED_BINLOG_BYTES = "canal_instance_received_binlog_bytes"; + private static final String PARSER_MODE = "canal_instance_parser_mode"; + private static final String MODE_LABEL = "parallel"; + private final List destLabelValues; + private final List modeLabelValues; + private final SerializedLongAdder receivedBinlogBytes; + private final SerializedLongAdder eventsPublishBlockingTime; + private final String publishBlockingTimeHelp; + private final String receivedBinlogBytesHelp; + private final String modeHelp; + private final List modeLabels; + private final Boolean isParallel; + + public MysqlParserCollector(CanalEventParser parser, String destination) { + if (!(parser instanceof MysqlEventParser)) { + throw new IllegalArgumentException("CanalEventParser must be MysqlEventParser"); + } + this.destLabelValues = Collections.singletonList(destination); + this.eventsPublishBlockingTime = ((MysqlEventParser)parser).getEventsPublishBlockingTime(); + this.publishBlockingTimeHelp = "Blocking time of publishing of instance " + destination; + this.receivedBinlogBytes = ((MysqlEventParser)parser).getReceivedBinlogBytes(); + this.receivedBinlogBytesHelp = "Received binlog bytes of instance" + destination; + this.isParallel = ((MysqlEventParser)parser).isParallel(); + this.modeHelp = "Parser mode of instance" + destination; + this.modeLabels = Arrays.asList(DESTINATION, MODE_LABEL); + this.modeLabelValues = Arrays.asList(destination, isParallel.toString()); + } + + @Override public List collect() { + List mfs = new ArrayList(); + // canal_instance_received_binlog_bytes + CounterMetricFamily bytesCounter = new CounterMetricFamily(RECEIVED_BINLOG_BYTES, + receivedBinlogBytesHelp, DEST_LABEL_LIST); + bytesCounter.addMetric(destLabelValues, receivedBinlogBytes.get()); + mfs.add(bytesCounter); + // canal_instance_parser_mode + GaugeMetricFamily modeGauge = new GaugeMetricFamily(PARSER_MODE, + modeHelp, + modeLabels); + modeGauge.addMetric(modeLabelValues, 1); + mfs.add(modeGauge); + // canal_instance_publish_blocking_time + if (isParallel) { + CounterMetricFamily blockingCounter = new CounterMetricFamily(PUBLISH_BLOCKING_TIME, + publishBlockingTimeHelp, DEST_LABEL_LIST); + blockingCounter.addMetric(destLabelValues, eventsPublishBlockingTime.get()); + mfs.add(blockingCounter); + } + return mfs; + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java deleted file mode 100644 index 60dd808d..00000000 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/OutboundThroughputAspect.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.alibaba.otter.canal.prometheus.impl; - -import io.prometheus.client.Collector; -import io.prometheus.client.CounterMetricFamily; -import org.aspectj.lang.annotation.After; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.annotation.Pointcut; -import org.jboss.netty.channel.Channel; -import org.jctools.maps.ConcurrentAutoTable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import static com.alibaba.otter.canal.server.netty.NettyUtils.HEADER_LENGTH; - -/** - * @author Chuanyi Li - */ -@Aspect -public class OutboundThroughputAspect { - private static final Logger logger = LoggerFactory.getLogger(OutboundThroughputAspect.class); - - /** - * Support highly scalable counters - * @see ConcurrentAutoTable - */ - private static final ConcurrentAutoTable total = new ConcurrentAutoTable(); - - private static final Collector collector = new OutboundThroughputCollector(); - - public static Collector getCollector() { - return collector; - } - - @Pointcut("call(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))") - public void write() {} - - //nested read, just eliminate them. - @Pointcut("withincode(* com.alibaba.otter.canal.server.netty.NettyUtils.write(..))") - public void nestedCall() {} - - @After("write() && !nestedCall() && args(ch, bytes, ..)") - public void recordWriteBytes(Channel ch, byte[] bytes) { - if (bytes != null) { - accumulateBytes(HEADER_LENGTH + bytes.length); - } - } - - @After("write() && !nestedCall() && args(ch, buf, ..)") - public void recordWriteBuffer(Channel ch, ByteBuffer buf) { - if (buf != null) { - total.add(HEADER_LENGTH + buf.limit()); - } - } - private void accumulateBytes(int count) { - try { - total.add(count); - } catch (Throwable t) { - //Catch every Throwable, rather than break the business logic. - logger.warn("Error while accumulate inbound bytes.", t); - } - } - - public static class OutboundThroughputCollector extends Collector { - - private OutboundThroughputCollector() {} - - @Override public List collect() { - List mfs = new ArrayList(); - CounterMetricFamily bytes = new CounterMetricFamily("canal_net_outbound_bytes", - "Total socket outbound bytes of canal server.", - total.get()); - mfs.add(bytes); - return mfs; - } - } -} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java index c55a7eac..47d4824d 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -36,7 +36,7 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow GaugeMetricFamily delay = new GaugeMetricFamily( DELAY_NAME, delayHelpName, - CanalInstanceExports.labelList); + CanalInstanceExports.DEST_LABEL_LIST); double d = 0.0; if (latestExecuteTime > 0) { d = now - latestExecuteTime; diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java new file mode 100644 index 00000000..d3413455 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java @@ -0,0 +1,134 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.protocol.CanalPacket.PacketType; +import com.alibaba.otter.canal.server.netty.ClientInstanceProfiler; +import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator.ClientRequestResult; +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.Counter; +import io.prometheus.client.Histogram; + +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DESTINATION; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS; + +/** + * @author Chuanyi Li + */ +public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler { + + private static final String PACKET_TYPE = "canal_instance_client_packets"; + private static final String OUTBOUND_BYTES = "canal_instance_client_bytes"; + private static final String EMPTY_BATCHES = "canal_instance_client_empty_batches"; + private static final String ERRORS = "canal_instance_client_request_error"; + private static final String LATENCY = "canal_instance_client_request_latency"; + private volatile boolean running = false; + private final String destination; + private final String[] destLabelValues; + private final Counter outboundCounter; + private final Counter packetsCounter; + private final Counter emptyBatchesCounter; + private final Counter errorsCounter; + private final Histogram responseLatency; + + public PrometheusClientInstanceProfiler(String destination) { + this.destination = destination; + this.destLabelValues = new String[]{destination}; + this.outboundCounter = Counter.build() + .labelNames(DEST_LABELS) + .name(OUTBOUND_BYTES) + .help("Send bytes to client of instance " + destination) + .create(); + this.packetsCounter = Counter.build() + .labelNames(new String[]{DESTINATION, "packetType"}) + .name(PACKET_TYPE) + .help("Send packets to client of instance " + destination) + .create(); + this.emptyBatchesCounter = Counter.build() + .labelNames(DEST_LABELS) + .name(EMPTY_BATCHES) + .help("Send empty batches to client of instance " + destination) + .create(); + this.errorsCounter = Counter.build() + .labelNames(new String[]{DESTINATION, "errorCode"}) + .name(ERRORS) + .help("Client request errors of instance " + destination) + .create(); + this.responseLatency = Histogram.build() + .labelNames(DEST_LABELS) + .name(LATENCY) + .help("Client request latency of instance " + destination) + // buckets in milliseconds + .buckets(1.0, 2.5, 5.0, 10.0, 25.0, 50.0, 100.0) + .create(); + } + + @Override + public String getDestination() { + return this.destination; + } + + @Override + public void profiling(ClientRequestResult result) { + PacketType type = result.getType(); + outboundCounter.labels(destLabelValues).inc(result.getAmount()); + packetsCounter.labels(destination, type.name()).inc(); + short errorCode = result.getErrorCode(); + if (errorCode > 0) { + errorsCounter.labels(destination, Short.toString(errorCode)).inc(); + } + long latency = result.getLatency(); + responseLatency.labels(destLabelValues).observe(latency / 1000000); + switch (type) { + case GET: + boolean empty = result.getEmpty(); + if (empty) { + emptyBatchesCounter.labels(destLabelValues).inc(); + } + break; + // reserve for others + default: + break; + } + } + + @Override public void start() { + if (outboundCounter != null) { + outboundCounter.register(); + } + if (packetsCounter != null) { + packetsCounter.register(); + } + if (emptyBatchesCounter != null) { + emptyBatchesCounter.register(); + } + if (errorsCounter != null) { + errorsCounter.register(); + } + if (responseLatency != null) { + responseLatency.register(); + } + running = true; + } + + @Override public void stop() { + running = false; + if (outboundCounter != null) { + CollectorRegistry.defaultRegistry.unregister(outboundCounter); + } + if (packetsCounter != null) { + CollectorRegistry.defaultRegistry.unregister(packetsCounter); + } + if (emptyBatchesCounter != null) { + CollectorRegistry.defaultRegistry.unregister(emptyBatchesCounter); + } + if (errorsCounter != null) { + CollectorRegistry.defaultRegistry.unregister(errorsCounter); + } + if (responseLatency != null) { + CollectorRegistry.defaultRegistry.unregister(responseLatency); + } + } + + @Override public boolean isStart() { + return running; + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java new file mode 100644 index 00000000..6f80c221 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java @@ -0,0 +1,15 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.server.netty.ClientInstanceProfiler; +import com.alibaba.otter.canal.server.netty.ClientInstanceProfilerFactory; + +/** + * @author Chuanyi Li + */ +public class PrometheusClientInstanceProfilerFactory implements ClientInstanceProfilerFactory { + + @Override + public ClientInstanceProfiler create(String destination) { + return new PrometheusClientInstanceProfiler(destination); + } +} diff --git a/prometheus/src/main/resources/META-INF/aop.xml b/prometheus/src/main/resources/META-INF/aop.xml deleted file mode 100644 index 8bd31d0c..00000000 --- a/prometheus/src/main/resources/META-INF/aop.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java index 9e9d9a39..cfd05ee6 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java @@ -10,7 +10,7 @@ import java.util.concurrent.ConcurrentMap; */ public class CanalServerWithNettyProfiler { - private static final ClientInstanceProfilerFactory DISABLED = new DefaultClientInstanceProfilerFactory(); + public static final ClientInstanceProfilerFactory DISABLED = new DefaultClientInstanceProfilerFactory(); private volatile ClientInstanceProfilerFactory factory; private final ConcurrentMap cliPfs; private final CanalServerWithEmbedded server; @@ -29,34 +29,47 @@ public class CanalServerWithNettyProfiler { return SingletonHolder.SINGLETON; } - public void profiling(String dest, ClientRequestResult result) { + public void profiling(String destination, ClientRequestResult result) { if (isDisabled()) { return; } - ClientInstanceProfiler profiler = tryGet(dest); + ClientInstanceProfiler profiler = cliPfs.get(destination); if (profiler != null) { profiler.profiling(result); } } - /** - * Remove instance profiler for specified instance. - * Only accepted while instance is not running. - * @param dest canal instance destination - */ - public void remove(String dest) throws IllegalStateException { + public void start(String destination) { if (isDisabled()) { return; } - synchronized (cliPfs) { - if (server.isStart(dest)) { - throw new IllegalStateException("Instance profiler should not be removed while running."); - } - cliPfs.remove(dest); + if (server.isStart(destination)) { + throw new IllegalStateException("Instance profiler should not be start while running."); + } + ClientInstanceProfiler profiler = factory.create(destination); + profiler.start(); + cliPfs.put(destination, profiler); + } + + /** + * Remove instance profiler for specified instance. + * Only accepted while instance is not running. + * @param destination canal instance destination + */ + public void stop(String destination) { + if (isDisabled()) { + return; + } + if (server.isStart(destination)) { + throw new IllegalStateException("Instance profiler should not be stop while running."); + } + ClientInstanceProfiler profiler = cliPfs.remove(destination); + if (profiler != null && profiler.isStart()) { + profiler.stop(); } } - public void setFactory(ClientInstanceProfilerFactory factory) { + public void setInstanceProfilerFactory(ClientInstanceProfilerFactory factory) { this.factory = factory; } @@ -64,15 +77,18 @@ public class CanalServerWithNettyProfiler { return factory == DISABLED || factory == null; } - private ClientInstanceProfiler tryGet(String dest) { + private ClientInstanceProfiler tryGet(String destination) { //try fast get - ClientInstanceProfiler profiler = cliPfs.get(dest); + ClientInstanceProfiler profiler = cliPfs.get(destination); if (profiler == null) { synchronized (cliPfs) { - if (server.isStart(dest)) { + if (server.isStart(destination)) { // avoid overwriting - cliPfs.putIfAbsent(dest, factory.create()); - profiler = cliPfs.get(dest); + cliPfs.putIfAbsent(destination, factory.create(destination)); + profiler = cliPfs.get(destination); + if (!profiler.isStart()) { + profiler.start(); + } } } } @@ -81,7 +97,7 @@ public class CanalServerWithNettyProfiler { private static class DefaultClientInstanceProfilerFactory implements ClientInstanceProfilerFactory { @Override - public ClientInstanceProfiler create() { + public ClientInstanceProfiler create(String destination) { throw new UnsupportedOperationException(); } } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java index 6c9ffc8b..bd4e2dbc 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java @@ -1,11 +1,12 @@ package com.alibaba.otter.canal.server.netty; +import com.alibaba.otter.canal.common.CanalLifeCycle; import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator; /** * @author Chuanyi Li */ -public interface ClientInstanceProfiler { +public interface ClientInstanceProfiler extends CanalLifeCycle { String getDestination(); diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java index 30075ba1..12b54bc6 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java @@ -5,6 +5,6 @@ package com.alibaba.otter.canal.server.netty; */ public interface ClientInstanceProfilerFactory { - ClientInstanceProfiler create(); + ClientInstanceProfiler create(String destination); } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index 7ea861d4..2d09f87e 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.server.netty.handler; import java.util.List; import java.util.concurrent.TimeUnit; +import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.jboss.netty.buffer.ChannelBuffer; diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index 37ee88e2..19a33667 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -15,18 +15,27 @@ public class ChannelFutureAggregator implements ChannelFutureListener { private ClientRequestResult result; - public ChannelFutureAggregator(String dest, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency) { - this(dest, request, type, amount, latency, 0); + public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, boolean empty) { + this(destination, request, type, amount, latency, empty, (short) 0); } - private ChannelFutureAggregator(String dest, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, int errorCode) { + public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency) { + this(destination, request, type, amount, latency, false, (short) 0); + } + + public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, short errorCode) { + this(destination, request, type, amount, latency, false, errorCode); + } + + private ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, boolean empty, short errorCode) { this.result = new ClientRequestResult.Builder() - .dest(dest) + .destination(destination) .type(type) .request(request) .amount(amount) .latency(latency) .errorCode(errorCode) + .empty(empty) .build(); } @@ -36,7 +45,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { if (future.getCause() != null) { result.channelError = future.getCause(); } - profiler().profiling(result.dest, result); + profiler().profiling(result.destination, result); } /** @@ -44,39 +53,42 @@ public class ChannelFutureAggregator implements ChannelFutureListener { */ public static class ClientRequestResult { - private String dest; + private String destination; private CanalPacket.PacketType type; private GeneratedMessage request; - private int amount; - private long latency; - private int errorCode; - private Throwable channelError; + private int amount; + private long latency; + private short errorCode; + private boolean empty; + private Throwable channelError; private ClientRequestResult() {} private ClientRequestResult(Builder builder) { - this.dest = Preconditions.checkNotNull(builder.dest); + this.destination = Preconditions.checkNotNull(builder.destination); this.type = Preconditions.checkNotNull(builder.type); this.request = Preconditions.checkNotNull(builder.request); this.amount = builder.amount; this.latency = builder.latency; this.errorCode = builder.errorCode; + this.empty = builder.empty; this.channelError = builder.channelError; } // auto-generated public static class Builder { - private String dest; + private String destination; private CanalPacket.PacketType type; private GeneratedMessage request; private int amount; private long latency; - private int errorCode; + private short errorCode; + private boolean empty; private Throwable channelError; - Builder dest(String dest) { - this.dest = dest; + Builder destination(String destination) { + this.destination = destination; return this; } @@ -100,23 +112,29 @@ public class ChannelFutureAggregator implements ChannelFutureListener { return this; } - Builder errorCode(int errorCode) { + Builder errorCode(short errorCode) { this.errorCode = errorCode; return this; } + Builder empty(boolean empty) { + this.empty = empty; + return this; + } + public Builder channelError(Throwable channelError) { this.channelError = channelError; return this; } public Builder fromPrototype(ClientRequestResult prototype) { - dest = prototype.dest; + destination = prototype.destination; type = prototype.type; request = prototype.request; amount = prototype.amount; latency = prototype.latency; errorCode = prototype.errorCode; + empty = prototype.empty; channelError = prototype.channelError; return this; } @@ -126,8 +144,8 @@ public class ChannelFutureAggregator implements ChannelFutureListener { } } // getters - public String getDest() { - return dest; + public String getDestination() { + return destination; } public CanalPacket.PacketType getType() { @@ -146,10 +164,14 @@ public class ChannelFutureAggregator implements ChannelFutureListener { return latency; } - public int getErrorCode() { + public short getErrorCode() { return errorCode; } + public boolean getEmpty() { + return empty; + } + public Throwable getChannelError() { return channelError; } diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java index 6511e6ba..23c5071b 100644 --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java @@ -39,6 +39,7 @@ public class EntryEventSink extends AbstractCanalEventSink> handler : getHandlers()) { events = handler.before(events); } - + long blockingStart = 0L; int fullTimes = 0; do { if (eventStore.tryPut(events)) { + if (fullTimes > 0) { + eventsSinkBlockingTime.addAndGet(System.nanoTime() - blockingStart); + } for (CanalEventDownStreamHandler> handler : getHandlers()) { events = handler.after(events); } return true; } else { + if (fullTimes == 0) { + blockingStart = System.nanoTime(); + } applyWait(++fullTimes); } @@ -202,4 +209,8 @@ public class EntryEventSink extends AbstractCanalEventSink Date: Fri, 3 Aug 2018 17:48:32 +0800 Subject: [PATCH 058/360] fixed issue #801 , support canal docker --- docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index e44796cb..2867b955 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -64,4 +64,4 @@ EXPOSE 2222 11111 8000 8080 WORKDIR /home/admin ENTRYPOINT [ "/alidata/bin/main.sh" ] -CMD [ "/home/admin/app.sh" ] \ No newline at end of file +CMD [ "/home/admin/app.sh" ] From 07d4d2023a81cd135c03cb203d6dcd18aee390b3 Mon Sep 17 00:00:00 2001 From: qmzhang <578967933@qq.com> Date: Sun, 5 Aug 2018 19:40:14 +0800 Subject: [PATCH 059/360] =?UTF-8?q?instance=E7=94=B1manager=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=97=B6=EF=BC=8CCanalParameter#MetaMode=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=9C=AC=E5=9C=B0=E6=96=87=E4=BB=B6=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E6=9C=AC=E5=9C=B0meta.dat=E5=AD=98=E5=82=A8=E4=BD=8D?= =?UTF-8?q?=E7=82=B9=E4=BF=A1=E6=81=AF=EF=BC=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/instance/manager/CanalInstanceWithManager.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index 63ed2d02..778a40aa 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -6,6 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import com.alibaba.otter.canal.meta.FileMixedMetaManager; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,12 +39,7 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; -import com.alibaba.otter.canal.parse.index.FailbackLogPositionManager; -import com.alibaba.otter.canal.parse.index.MemoryLogPositionManager; -import com.alibaba.otter.canal.parse.index.MetaLogPositionManager; -import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager; -import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager; +import com.alibaba.otter.canal.parse.index.*; import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.sink.entry.EntryEventSink; From 2084b1fc37cdf2deabd4c12209bab9b660cb8731 Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Mon, 6 Aug 2018 17:22:28 +0800 Subject: [PATCH 060/360] Add listener to write. --- .../prometheus/CanalInstanceExports.java | 6 +- .../canal/prometheus/PrometheusService.java | 7 +- .../prometheus/impl/MysqlParserCollector.java | 4 +- .../PrometheusClientInstanceProfiler.java | 6 +- .../netty/CanalServerWithNettyProfiler.java | 12 +-- .../otter/canal/server/netty/NettyUtils.java | 16 ++++ .../handler/ClientAuthenticationHandler.java | 2 +- .../server/netty/handler/SessionHandler.java | 75 +++++++++---------- .../listener/ChannelFutureAggregator.java | 2 +- 9 files changed, 71 insertions(+), 59 deletions(-) diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java index 19759154..388f8e59 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java @@ -21,9 +21,9 @@ import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler. public class CanalInstanceExports { private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class); - public static final String DESTINATION = "destination"; - public static final String[] DEST_LABELS = {DESTINATION}; - public static final List DEST_LABEL_LIST = Collections.singletonList(DESTINATION); + public static final String DEST = "destination"; + public static final String[] DEST_LABELS = {DEST}; + public static final List DEST_LABEL_LIST = Collections.singletonList(DEST); private final String destination; private Collector storeCollector; private Collector delayCollector; diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 2aa7dcd0..5d417883 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -22,11 +22,8 @@ import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler. public class PrometheusService implements CanalMetricsService { private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); - private final Map exports = new ConcurrentHashMap(); - private volatile boolean running = false; - private HTTPServer server; private PrometheusService() { @@ -69,8 +66,8 @@ public class PrometheusService implements CanalMetricsService { // Normally, service should be terminated at canal shutdown. // No need to unregister instance exports explicitly. // But for the sake of safety, unregister them. - for (CanalInstanceExports ie : exports.values()) { - ie.unregister(); + for (CanalInstanceExports cie : exports.values()) { + cie.unregister(); } profiler().setInstanceProfilerFactory(DISABLED); if (server != null) { diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java index 02d35209..c8543f3c 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java @@ -12,7 +12,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DESTINATION; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; /** @@ -45,7 +45,7 @@ public class MysqlParserCollector extends Collector { this.receivedBinlogBytesHelp = "Received binlog bytes of instance" + destination; this.isParallel = ((MysqlEventParser)parser).isParallel(); this.modeHelp = "Parser mode of instance" + destination; - this.modeLabels = Arrays.asList(DESTINATION, MODE_LABEL); + this.modeLabels = Arrays.asList(DEST, MODE_LABEL); this.modeLabelValues = Arrays.asList(destination, isParallel.toString()); } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java index d3413455..4cbbdb89 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java @@ -7,7 +7,7 @@ import io.prometheus.client.CollectorRegistry; import io.prometheus.client.Counter; import io.prometheus.client.Histogram; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DESTINATION; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS; /** @@ -38,7 +38,7 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler .help("Send bytes to client of instance " + destination) .create(); this.packetsCounter = Counter.build() - .labelNames(new String[]{DESTINATION, "packetType"}) + .labelNames(new String[]{DEST, "packetType"}) .name(PACKET_TYPE) .help("Send packets to client of instance " + destination) .create(); @@ -48,7 +48,7 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler .help("Send empty batches to client of instance " + destination) .create(); this.errorsCounter = Counter.build() - .labelNames(new String[]{DESTINATION, "errorCode"}) + .labelNames(new String[]{DEST, "errorCode"}) .name(ERRORS) .help("Client request errors of instance " + destination) .create(); diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java index cfd05ee6..e44476d7 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java @@ -11,7 +11,7 @@ import java.util.concurrent.ConcurrentMap; public class CanalServerWithNettyProfiler { public static final ClientInstanceProfilerFactory DISABLED = new DefaultClientInstanceProfilerFactory(); - private volatile ClientInstanceProfilerFactory factory; + private volatile ClientInstanceProfilerFactory clientInstanceProfilerFactory; private final ConcurrentMap cliPfs; private final CanalServerWithEmbedded server; @@ -20,7 +20,7 @@ public class CanalServerWithNettyProfiler { } private CanalServerWithNettyProfiler() { - this.factory = DISABLED; + this.clientInstanceProfilerFactory = DISABLED; this.cliPfs = new ConcurrentHashMap(); this.server = CanalServerWithEmbedded.instance(); } @@ -46,7 +46,7 @@ public class CanalServerWithNettyProfiler { if (server.isStart(destination)) { throw new IllegalStateException("Instance profiler should not be start while running."); } - ClientInstanceProfiler profiler = factory.create(destination); + ClientInstanceProfiler profiler = clientInstanceProfilerFactory.create(destination); profiler.start(); cliPfs.put(destination, profiler); } @@ -70,11 +70,11 @@ public class CanalServerWithNettyProfiler { } public void setInstanceProfilerFactory(ClientInstanceProfilerFactory factory) { - this.factory = factory; + this.clientInstanceProfilerFactory = factory; } private boolean isDisabled() { - return factory == DISABLED || factory == null; + return clientInstanceProfilerFactory == DISABLED || clientInstanceProfilerFactory == null; } private ClientInstanceProfiler tryGet(String destination) { @@ -84,7 +84,7 @@ public class CanalServerWithNettyProfiler { synchronized (cliPfs) { if (server.isStart(destination)) { // avoid overwriting - cliPfs.putIfAbsent(destination, factory.create(destination)); + cliPfs.putIfAbsent(destination, clientInstanceProfilerFactory.create(destination)); profiler = cliPfs.get(destination); if (!profiler.isStart()) { profiler.start(); diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java index 20532400..43ecbe67 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/NettyUtils.java @@ -74,4 +74,20 @@ public class NettyUtils { .toByteArray(), channelFutureListener); } + + public static byte[] ackPacket() { + return Packet.newBuilder() + .setType(CanalPacket.PacketType.ACK) + .setBody(Ack.newBuilder().build().toByteString()) + .build() + .toByteArray(); + } + + public static byte[] errorPacket(int errorCode, String errorMessage) { + return Packet.newBuilder() + .setType(CanalPacket.PacketType.ACK) + .setBody(Ack.newBuilder().setErrorCode(errorCode).setErrorMessage(errorMessage).build().toByteString()) + .build() + .toByteArray(); + } } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/ClientAuthenticationHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/ClientAuthenticationHandler.java index 60398ea7..597d692a 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/ClientAuthenticationHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/ClientAuthenticationHandler.java @@ -73,7 +73,7 @@ public class ClientAuthenticationHandler extends SimpleChannelHandler { MDC.remove("destination"); } } - + // 鉴权一次性,暂不统计 NettyUtils.ack(ctx.getChannel(), new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index 2d09f87e..a98e7b8b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -52,7 +52,7 @@ public class SessionHandler extends SimpleChannelHandler { public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { logger.info("message receives in session handler..."); - long start = System.currentTimeMillis(); + long start = System.nanoTime(); ChannelBuffer buffer = (ChannelBuffer) e.getMessage(); Packet packet = Packet.parseFrom(buffer.readBytes(buffer.readableBytes()).array()); ClientIdentity clientIdentity = null; @@ -76,12 +76,13 @@ public class SessionHandler extends SimpleChannelHandler { embeddedServer.subscribe(clientIdentity); // ctx.setAttachment(clientIdentity);// 设置状态数据 - NettyUtils.ack(ctx.getChannel(), null); + byte[] ackBytes = NettyUtils.ackPacket(); + NettyUtils.write(ctx.getChannel(), ackBytes, new ChannelFutureAggregator(sub.getDestination(), + sub, packet.getType(), ackBytes.length, System.nanoTime() - start)); } else { - NettyUtils.error(401, - MessageFormatter.format("destination or clientId is null", sub.toString()).getMessage(), - ctx.getChannel(), - null); + byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", sub.toString()).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes ,new ChannelFutureAggregator(sub.getDestination(), + sub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401)); } break; case UNSUBSCRIPTION: @@ -93,12 +94,13 @@ public class SessionHandler extends SimpleChannelHandler { MDC.put("destination", clientIdentity.getDestination()); embeddedServer.unsubscribe(clientIdentity); stopCanalInstanceIfNecessary(clientIdentity);// 尝试关闭 - NettyUtils.ack(ctx.getChannel(), null); + byte[] ackBytes = NettyUtils.ackPacket(); + NettyUtils.write(ctx.getChannel(), ackBytes, new ChannelFutureAggregator(unsub.getDestination(), + unsub, packet.getType(), ackBytes.length, System.nanoTime() - start)); } else { - NettyUtils.error(401, - MessageFormatter.format("destination or clientId is null", unsub.toString()).getMessage(), - ctx.getChannel(), - null); + byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", unsub.toString()).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(unsub.getDestination(), + unsub, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401)); } break; case GET: @@ -173,7 +175,8 @@ public class SessionHandler extends SimpleChannelHandler { output.writeBytes(2, rowEntries.get(i)); } output.checkNoSpaceLeft(); - NettyUtils.write(ctx.getChannel(), body, null); + NettyUtils.write(ctx.getChannel(), body, new ChannelFutureAggregator(get.getDestination(), + get, packet.getType(), body.length, System.nanoTime() - start, message.getId() == -1)); // output.flush(); // byteBuffer.flip(); @@ -194,14 +197,14 @@ public class SessionHandler extends SimpleChannelHandler { } } } - packetBuilder.setBody(messageBuilder.build().toByteString()); - NettyUtils.write(ctx.getChannel(), packetBuilder.build().toByteArray(), null);// 输出数据 + byte[] body = packetBuilder.setBody(messageBuilder.build().toByteString()).build().toByteArray(); + NettyUtils.write(ctx.getChannel(), body, new ChannelFutureAggregator(get.getDestination(), + get, packet.getType(), body.length, System.nanoTime() - start, message.getId() == -1));// 输出数据 } } else { - NettyUtils.error(401, - MessageFormatter.format("destination or clientId is null", get.toString()).getMessage(), - ctx.getChannel(), - null); + byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", get.toString()).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(get.getDestination(), + get, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401)); } break; case CLIENTACK: @@ -209,10 +212,9 @@ public class SessionHandler extends SimpleChannelHandler { MDC.put("destination", ack.getDestination()); if (StringUtils.isNotEmpty(ack.getDestination()) && StringUtils.isNotEmpty(ack.getClientId())) { if (ack.getBatchId() == 0L) { - NettyUtils.error(402, - MessageFormatter.format("batchId should assign value", ack.toString()).getMessage(), - ctx.getChannel(), - null); + byte[] errorBytes = NettyUtils.errorPacket(402, MessageFormatter.format("batchId should assign value", ack.toString()).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(), + ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 402)); } else if (ack.getBatchId() == -1L) { // -1代表上一次get没有数据,直接忽略之 // donothing } else { @@ -220,10 +222,9 @@ public class SessionHandler extends SimpleChannelHandler { embeddedServer.ack(clientIdentity, ack.getBatchId()); } } else { - NettyUtils.error(401, - MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage(), - ctx.getChannel(), - null); + byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ack.getDestination(), + ack, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401)); } break; case CLIENTROLLBACK: @@ -239,25 +240,23 @@ public class SessionHandler extends SimpleChannelHandler { embeddedServer.rollback(clientIdentity, rollback.getBatchId()); // 只回滚单个批次 } } else { - NettyUtils.error(401, - MessageFormatter.format("destination or clientId is null", rollback.toString()) - .getMessage(), - ctx.getChannel(), - null); + byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", rollback.toString()).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(rollback.getDestination(), + rollback, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 401)); } break; default: - NettyUtils.error(400, MessageFormatter.format("packet type={} is NOT supported!", packet.getType()) - .getMessage(), ctx.getChannel(), null); + byte[] errorBytes = NettyUtils.errorPacket(400, MessageFormatter.format("packet type={} is NOT supported!", packet.getType()).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ctx.getChannel().getRemoteAddress().toString(), + null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400)); break; } } catch (Throwable exception) { - NettyUtils.error(400, - MessageFormatter.format("something goes wrong with channel:{}, exception={}", + byte[] errorBytes = NettyUtils.errorPacket(400, MessageFormatter.format("something goes wrong with channel:{}, exception={}", ctx.getChannel(), - ExceptionUtils.getStackTrace(exception)).getMessage(), - ctx.getChannel(), - null); + ExceptionUtils.getStackTrace(exception)).getMessage()); + NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(ctx.getChannel().getRemoteAddress().toString(), + null, packet.getType(), errorBytes.length, System.nanoTime() - start, (short) 400)); } finally { MDC.remove("destination"); } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index 19a33667..89755431 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -67,7 +67,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { private ClientRequestResult(Builder builder) { this.destination = Preconditions.checkNotNull(builder.destination); this.type = Preconditions.checkNotNull(builder.type); - this.request = Preconditions.checkNotNull(builder.request); + this.request = builder.request; this.amount = builder.amount; this.latency = builder.latency; this.errorCode = builder.errorCode; From 75de57c102f753863fa14978c77d0c821a933c83 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Tue, 7 Aug 2018 08:18:01 +0800 Subject: [PATCH 061/360] Prometheus refactor. --- .../prometheus/CanalInstanceExports.java | 54 ++++++------ .../canal/prometheus/InstanceRegistry.java | 14 +++ .../canal/prometheus/PrometheusService.java | 31 +++---- .../canal/prometheus/impl/EntryCollector.java | 76 ++++++++++++++++ .../prometheus/impl/EntrySinkCollector.java | 45 ---------- .../impl/InstanceMetaCollector.java | 64 -------------- .../canal/prometheus/impl/MetaCollector.java | 77 +++++++++++++++++ ...PrometheusCanalEventDownStreamHandler.java | 50 ++--------- .../PrometheusClientInstanceProfiler.java | 47 +++++----- ...ometheusClientInstanceProfilerFactory.java | 15 ---- .../canal/prometheus/impl/SinkCollector.java | 73 ++++++++++++++++ ...toreCollector.java => StoreCollector.java} | 33 +++++-- .../netty/CanalServerWithNettyProfiler.java | 86 +++---------------- .../server/netty/ClientInstanceProfiler.java | 6 +- .../netty/ClientInstanceProfilerFactory.java | 10 --- .../listener/ChannelFutureAggregator.java | 2 +- 16 files changed, 346 insertions(+), 337 deletions(-) create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/InstanceRegistry.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java delete mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java delete mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java delete mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java rename prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/{MemoryStoreCollector.java => StoreCollector.java} (66%) delete mode 100644 server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java index 388f8e59..6908a3bd 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java @@ -13,8 +13,6 @@ import org.slf4j.LoggerFactory; import java.util.Collections; import java.util.List; -import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler; - /** * @author Chuanyi Li */ @@ -24,30 +22,29 @@ public class CanalInstanceExports { public static final String DEST = "destination"; public static final String[] DEST_LABELS = {DEST}; public static final List DEST_LABEL_LIST = Collections.singletonList(DEST); - private final String destination; +// private final String destination; private Collector storeCollector; private Collector delayCollector; private Collector metaCollector; private Collector sinkCollector; private Collector parserCollector; - private CanalInstanceExports(CanalInstance instance) { - this.destination = instance.getDestination(); - initEventsMetrics(instance); - initStoreCollector(instance); - initMetaCollector(instance); - initSinkCollector(instance); - initParserCollector(instance); + public CanalInstanceExports() { +// this.destination = instance.getDestination(); +// initEventsMetrics(instance); +// initStoreCollector(instance); +// initMetaCollector(instance); +// initSinkCollector(instance); +// initParserCollector(instance); } - static CanalInstanceExports forInstance(CanalInstance instance) { - return new CanalInstanceExports(instance); - } +// static CanalInstanceExports forInstance(CanalInstance instance) { +// return new CanalInstanceExports(instance); +// } - void register() { - profiler().start(destination); + void register(CanalInstance instance) { if (delayCollector != null) { delayCollector.register(); } @@ -65,8 +62,7 @@ public class CanalInstanceExports { } } - void unregister() { - profiler().stop(destination); + void unregister(CanalInstance instance) { if (delayCollector != null) { CollectorRegistry.defaultRegistry.unregister(delayCollector); } @@ -89,28 +85,28 @@ public class CanalInstanceExports { if (sink instanceof EntryEventSink) { EntryEventSink entryEventSink = (EntryEventSink) sink; // TODO ensure not to add handler again - PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(destination); - entryEventSink.addHandler(handler); - delayCollector = handler.getCollector(); +// PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(destination); +// entryEventSink.addHandler(handler); +// delayCollector = handler.getCollector(); } else { logger.warn("This impl register metrics for only EntryEventSink, skip."); } } - private void initStoreCollector(CanalInstance instance) { - try { - storeCollector = new MemoryStoreCollector(instance.getEventStore(), destination); - } catch (CanalStoreException cse) { - logger.warn("Failed to register metrics for destination {}.", destination, cse); - } - } +// private void initStoreCollector(CanalInstance instance) { +// try { +// storeCollector = new StoreCollector(instance.getEventStore(), destination); +// } catch (CanalStoreException cse) { +// logger.warn("Failed to register metrics for destination {}.", destination, cse); +// } +// } private void initMetaCollector(CanalInstance instance) { - metaCollector = new InstanceMetaCollector(instance); + metaCollector = new MetaCollector(instance); } private void initSinkCollector(CanalInstance instance) { - sinkCollector = new EntrySinkCollector(instance.getEventSink(), instance.getDestination()); +// sinkCollector = new EntryCollector(instance.getEventSink(), instance.getDestination()); } private void initParserCollector(CanalInstance instance) { diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/InstanceRegistry.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/InstanceRegistry.java new file mode 100644 index 00000000..e320efc4 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/InstanceRegistry.java @@ -0,0 +1,14 @@ +package com.alibaba.otter.canal.prometheus; + +import com.alibaba.otter.canal.instance.core.CanalInstance; + +/** + * @author Chuanyi Li + */ +public interface InstanceRegistry { + + void register(CanalInstance instance); + + void unregister(CanalInstance instance); + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 5d417883..c3a241bd 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -1,8 +1,8 @@ package com.alibaba.otter.canal.prometheus; import com.alibaba.otter.canal.instance.core.CanalInstance; -import com.alibaba.otter.canal.prometheus.impl.PrometheusClientInstanceProfilerFactory; -import com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler; +import com.alibaba.otter.canal.prometheus.impl.PrometheusClientInstanceProfiler; +import com.alibaba.otter.canal.server.netty.ClientInstanceProfiler; import com.alibaba.otter.canal.spi.CanalMetricsService; import io.prometheus.client.exporter.HTTPServer; import io.prometheus.client.hotspot.DefaultExports; @@ -10,10 +10,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.DISABLED; +import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.NOP; import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler; /** @@ -21,10 +19,11 @@ import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler. */ public class PrometheusService implements CanalMetricsService { - private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); - private final Map exports = new ConcurrentHashMap(); - private volatile boolean running = false; + private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); + private final CanalInstanceExports instanceExports = new CanalInstanceExports(); + private volatile boolean running = false; private HTTPServer server; + private ClientInstanceProfiler clientProfiler = new PrometheusClientInstanceProfiler(); private PrometheusService() { } @@ -52,7 +51,10 @@ public class PrometheusService implements CanalMetricsService { DefaultExports.initialize(); // Canal server level exports CanalServerExports.initialize(); - profiler().setInstanceProfilerFactory(new PrometheusClientInstanceProfilerFactory()); + if (!clientProfiler.isStart()) { + clientProfiler.start(); + } + profiler().setInstanceProfiler(clientProfiler); } catch (Throwable t) { logger.warn("Unable to initialize server exports.", t); } @@ -63,13 +65,11 @@ public class PrometheusService implements CanalMetricsService { @Override public void terminate() { running = false; - // Normally, service should be terminated at canal shutdown. - // No need to unregister instance exports explicitly. - // But for the sake of safety, unregister them. - for (CanalInstanceExports cie : exports.values()) { - cie.unregister(); + instanceExports.unregister(); + if (clientProfiler.isStart()) { + clientProfiler.stop(); } - profiler().setInstanceProfilerFactory(DISABLED); + profiler().setInstanceProfiler(NOP); if (server != null) { server.stop(); } @@ -111,4 +111,5 @@ public class PrometheusService implements CanalMetricsService { } logger.info("Unregister metrics for destination {}.", instance.getDestination()); } + } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java new file mode 100644 index 00000000..83e4ef95 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java @@ -0,0 +1,76 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.prometheus.InstanceRegistry; +import com.alibaba.otter.canal.sink.CanalEventSink; +import com.alibaba.otter.canal.sink.entry.EntryEventSink; +import com.google.common.base.Preconditions; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; + +/** + * @author Chuanyi Li + */ +public class EntryCollector extends Collector implements InstanceRegistry { + + private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); + private static final String DELAY_NAME = "canal_instance_traffic_delay"; + private static final String delayHelpName = "Traffic delay of canal instance"; + private final ConcurrentMap instances = new ConcurrentHashMap(); + + @Override + public List collect() { + List mfs = new ArrayList(); + GaugeMetricFamily delay = new GaugeMetricFamily(DELAY_NAME, + delayHelpName, DEST_LABEL_LIST); + for (EntryMetricsHolder emh : instances.values()) { + delay.addMetric(emh.destLabelValues, emh.latestExecTime.doubleValue()); + } + mfs.add(delay); + return mfs; + } + + @Override + public void register(CanalInstance instance) { + final String destination = instance.getDestination(); + EntryMetricsHolder holder = new EntryMetricsHolder(); + holder.destLabelValues = Collections.singletonList(destination); + CanalEventSink sink = instance.getEventSink(); + if (!(sink instanceof EntryEventSink)) { + throw new IllegalArgumentException("CanalEventSink must be EntryEventSink"); + } + EntryEventSink entrySink = (EntryEventSink) sink; + PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(); + holder.latestExecTime = handler.getLatestExecuteTime(); + entrySink.addHandler(handler, 0); + Preconditions.checkNotNull(holder.destLabelValues); + Preconditions.checkNotNull(holder.latestExecTime); + EntryMetricsHolder old = instances.putIfAbsent(destination, holder); + if (old != null) { + logger.warn("Ignore repeated EntryCollector register for instance {}.", destination); + } + } + + @Override + public void unregister(CanalInstance instance) { + final String destination = instance.getDestination(); + instances.remove(destination); + } + + private class EntryMetricsHolder { + private AtomicLong latestExecTime; + private List destLabelValues; + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java deleted file mode 100644 index 54775842..00000000 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntrySinkCollector.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.alibaba.otter.canal.prometheus.impl; - -import com.alibaba.otter.canal.sink.CanalEventSink; -import com.alibaba.otter.canal.sink.entry.EntryEventSink; -import io.prometheus.client.Collector; -import io.prometheus.client.CounterMetricFamily; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.atomic.AtomicLong; - -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; - -/** - * @author Chuanyi Li - */ -public class EntrySinkCollector extends Collector { - - private static final String SINK_BLOCKING_TIME = "canal_instance_sink_blocking_time"; - private final String sinkBlockTimeHelp; - private final List destLabelValues; - private final AtomicLong eventsSinkBlockingTime; - - - public EntrySinkCollector(CanalEventSink sink, String destination) { - if (!(sink instanceof EntryEventSink)) { - throw new IllegalArgumentException("CanalEventSink must be EntryEventSink"); - } - this.destLabelValues = Collections.singletonList(destination); - EntryEventSink entrySink = (EntryEventSink) sink; - this.sinkBlockTimeHelp = "Sink blocking time of instance " + destination; - eventsSinkBlockingTime = entrySink.getEventsSinkBlockingTime(); - } - - @Override public List collect() { - List mfs = new ArrayList(); - CounterMetricFamily blockingCounter = new CounterMetricFamily(SINK_BLOCKING_TIME, - sinkBlockTimeHelp, DEST_LABEL_LIST); - blockingCounter.addMetric(destLabelValues, eventsSinkBlockingTime.doubleValue()); - mfs.add(blockingCounter); - return mfs; - } - -} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java deleted file mode 100644 index 2e8b7fc4..00000000 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/InstanceMetaCollector.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.alibaba.otter.canal.prometheus.impl; - -import com.alibaba.otter.canal.instance.core.CanalInstance; -import com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring; -import com.alibaba.otter.canal.meta.CanalMetaManager; -import com.alibaba.otter.canal.prometheus.CanalInstanceExports; -import com.alibaba.otter.canal.protocol.ClientIdentity; -import io.prometheus.client.Collector; -import io.prometheus.client.GaugeMetricFamily; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * @author Chuanyi Li - */ -public class InstanceMetaCollector extends Collector { - - private static final List InfoLabel = Arrays.asList("destination", "mode"); - - private CanalMetaManager metaManager; - - private final String destination; - - private final String mode; - - private final String subsHelp; - - public InstanceMetaCollector(CanalInstance instance) { - if (instance == null) { - throw new IllegalArgumentException("CanalInstance must not be null."); - } - if (instance instanceof CanalInstanceWithSpring) { - mode = "spring"; - } else { - mode = "manager"; - } - this.metaManager = instance.getMetaManager(); - this.destination = instance.getDestination(); - this.subsHelp = "Subscriptions of canal instance " + destination; - } - - @Override - public List collect() { - List mfs = new ArrayList(); - GaugeMetricFamily instanceInfo = new GaugeMetricFamily( - "canal_instance", - "Canal instance", - InfoLabel); - instanceInfo.addMetric(Arrays.asList(destination, mode), 1); - mfs.add(instanceInfo); - if (metaManager.isStart()) { - // client id = hardcode 1001, 目前没有意义 - List subs = metaManager.listAllSubscribeInfo(destination); - GaugeMetricFamily subscriptions = new GaugeMetricFamily( - "canal_instance_subscription", - subsHelp, CanalInstanceExports.DEST_LABEL_LIST); - subscriptions.addMetric(Arrays.asList(destination), subs.size()); - mfs.add(subscriptions); - } - return mfs; - } -} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java new file mode 100644 index 00000000..eb5a2bde --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java @@ -0,0 +1,77 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.instance.spring.CanalInstanceWithSpring; +import com.alibaba.otter.canal.meta.CanalMetaManager; +import com.alibaba.otter.canal.prometheus.InstanceRegistry; +import com.alibaba.otter.canal.protocol.ClientIdentity; +import com.google.common.base.Preconditions; +import io.prometheus.client.Collector; +import io.prometheus.client.GaugeMetricFamily; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * @author Chuanyi Li + */ +public class MetaCollector extends Collector implements InstanceRegistry { + + private static final List InfoLabel = Arrays.asList("destination", "mode"); + private static final Logger logger = LoggerFactory.getLogger(MetaCollector.class); + private static final String SUBSCRIPTION = "canal_instance"; + private static final String subscriptionHelp = "Canal instance"; + private final ConcurrentMap instances = new ConcurrentHashMap(); + + @Override + public List collect() { + List mfs = new ArrayList(); + GaugeMetricFamily instanceInfo = new GaugeMetricFamily( + SUBSCRIPTION, + subscriptionHelp, + InfoLabel); + for (Map.Entry nme : instances.entrySet()) { + final String destination = nme.getKey(); + final MetaMetricsHolder nmh = nme.getValue(); + List subs = nmh.metaManager.listAllSubscribeInfo(destination); + int count = subs == null ? 0 : subs.size(); + instanceInfo.addMetric(nmh.infoLabelValues, count); + } + mfs.add(instanceInfo); + return mfs; + } + + @Override + public void register(CanalInstance instance) { + final String destination = instance.getDestination(); + MetaMetricsHolder holder = new MetaMetricsHolder(); + String mode = (instance instanceof CanalInstanceWithSpring) ? "spring" : "manager"; + holder.infoLabelValues = Arrays.asList(destination, mode); + holder.metaManager = instance.getMetaManager(); + Preconditions.checkNotNull(holder.infoLabelValues); + Preconditions.checkNotNull(holder.metaManager); + MetaMetricsHolder old = instances.putIfAbsent(destination, holder); + if (old != null) { + logger.warn("Ignore repeated MetaCollector register for instance {}.", destination); + } + } + + @Override + public void unregister(CanalInstance instance) { + final String destination = instance.getDestination(); + instances.remove(destination); + } + + private class MetaMetricsHolder { + private List infoLabelValues; + private CanalMetaManager metaManager; + } + + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java index 47d4824d..763ce46b 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -1,53 +1,16 @@ package com.alibaba.otter.canal.prometheus.impl; -import com.alibaba.otter.canal.prometheus.CanalInstanceExports; import com.alibaba.otter.canal.sink.AbstractCanalEventDownStreamHandler; import com.alibaba.otter.canal.store.model.Event; -import io.prometheus.client.Collector; -import io.prometheus.client.GaugeMetricFamily; - -import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.atomic.AtomicLong; /** * @author Chuanyi Li */ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler> { - private final Collector collector; - - private long latestExecuteTime = 0L; - - private static final String DELAY_NAME = "canal_instance_traffic_delay"; - - private final String delayHelpName; - - private final List labelValues; - - public PrometheusCanalEventDownStreamHandler(final String destination) { - this.delayHelpName = "Traffic delay of canal instance " + destination + " in seconds."; - this.labelValues = Collections.singletonList(destination); - collector = new Collector() { - @Override - public List collect() { - List mfs = new ArrayList(); - long now = System.currentTimeMillis(); - GaugeMetricFamily delay = new GaugeMetricFamily( - DELAY_NAME, - delayHelpName, - CanalInstanceExports.DEST_LABEL_LIST); - double d = 0.0; - if (latestExecuteTime > 0) { - d = now - latestExecuteTime; - } - d = d > 0.0 ? (d / 1000) : 0.0; - delay.addMetric(labelValues, d); - mfs.add(delay); - return mfs; - } - }; - } + private final AtomicLong latestExecuteTime = new AtomicLong(0L); @Override public List before(List events) { @@ -59,15 +22,16 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow if (events != null && !events.isEmpty()) { Event last = events.get(events.size() - 1); long ts = last.getExecuteTime(); - if (ts > latestExecuteTime) { - latestExecuteTime = ts; + long ls = latestExecuteTime.get(); + if (ts > ls) { + latestExecuteTime.lazySet(ts); } } return events; } - public Collector getCollector() { - return this.collector; + public AtomicLong getLatestExecuteTime() { + return latestExecuteTime; } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java index 4cbbdb89..331897ee 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java @@ -20,68 +20,60 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler private static final String EMPTY_BATCHES = "canal_instance_client_empty_batches"; private static final String ERRORS = "canal_instance_client_request_error"; private static final String LATENCY = "canal_instance_client_request_latency"; + private final Counter outboundCounter; + private final Counter packetsCounter; + private final Counter emptyBatchesCounter; + private final Counter errorsCounter; + private final Histogram responseLatency; private volatile boolean running = false; - private final String destination; - private final String[] destLabelValues; - private final Counter outboundCounter; - private final Counter packetsCounter; - private final Counter emptyBatchesCounter; - private final Counter errorsCounter; - private final Histogram responseLatency; - public PrometheusClientInstanceProfiler(String destination) { - this.destination = destination; - this.destLabelValues = new String[]{destination}; + public PrometheusClientInstanceProfiler() { this.outboundCounter = Counter.build() .labelNames(DEST_LABELS) .name(OUTBOUND_BYTES) - .help("Send bytes to client of instance " + destination) + .help("Total bytes sent to client.") .create(); this.packetsCounter = Counter.build() .labelNames(new String[]{DEST, "packetType"}) .name(PACKET_TYPE) - .help("Send packets to client of instance " + destination) + .help("Total packets sent to client.") .create(); this.emptyBatchesCounter = Counter.build() .labelNames(DEST_LABELS) .name(EMPTY_BATCHES) - .help("Send empty batches to client of instance " + destination) + .help("Total empty batches sent to client.") .create(); this.errorsCounter = Counter.build() .labelNames(new String[]{DEST, "errorCode"}) .name(ERRORS) - .help("Client request errors of instance " + destination) + .help("Total client request errors.") .create(); this.responseLatency = Histogram.build() .labelNames(DEST_LABELS) .name(LATENCY) - .help("Client request latency of instance " + destination) + .help("Client request latency.") // buckets in milliseconds .buckets(1.0, 2.5, 5.0, 10.0, 25.0, 50.0, 100.0) .create(); } - @Override - public String getDestination() { - return this.destination; - } - @Override public void profiling(ClientRequestResult result) { + String destination = result.getDestination(); PacketType type = result.getType(); - outboundCounter.labels(destLabelValues).inc(result.getAmount()); + outboundCounter.labels(destination).inc(result.getAmount()); packetsCounter.labels(destination, type.name()).inc(); short errorCode = result.getErrorCode(); if (errorCode > 0) { errorsCounter.labels(destination, Short.toString(errorCode)).inc(); } long latency = result.getLatency(); - responseLatency.labels(destLabelValues).observe(latency / 1000000); + responseLatency.labels(destination).observe(latency / 1000000); switch (type) { case GET: boolean empty = result.getEmpty(); if (empty) { - emptyBatchesCounter.labels(destLabelValues).inc(); + emptyBatchesCounter.labels(destination).inc(); } break; // reserve for others @@ -90,7 +82,8 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler } } - @Override public void start() { + @Override + public void start() { if (outboundCounter != null) { outboundCounter.register(); } @@ -109,7 +102,8 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler running = true; } - @Override public void stop() { + @Override + public void stop() { running = false; if (outboundCounter != null) { CollectorRegistry.defaultRegistry.unregister(outboundCounter); @@ -128,7 +122,8 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler } } - @Override public boolean isStart() { + @Override + public boolean isStart() { return running; } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java deleted file mode 100644 index 6f80c221..00000000 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfilerFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.alibaba.otter.canal.prometheus.impl; - -import com.alibaba.otter.canal.server.netty.ClientInstanceProfiler; -import com.alibaba.otter.canal.server.netty.ClientInstanceProfilerFactory; - -/** - * @author Chuanyi Li - */ -public class PrometheusClientInstanceProfilerFactory implements ClientInstanceProfilerFactory { - - @Override - public ClientInstanceProfiler create(String destination) { - return new PrometheusClientInstanceProfiler(destination); - } -} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java new file mode 100644 index 00000000..11903314 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java @@ -0,0 +1,73 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.prometheus.InstanceRegistry; +import com.alibaba.otter.canal.sink.CanalEventSink; +import com.alibaba.otter.canal.sink.entry.EntryEventSink; +import com.google.common.base.Preconditions; +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; + +/** + * @author Chuanyi Li + */ +public class SinkCollector extends Collector implements InstanceRegistry { + + private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); + private static final String SINK_BLOCKING_TIME = "canal_instance_sink_blocking_time"; + private static final String sinkBlockTimeHelp = "Total sink blocking time"; + private final ConcurrentMap instances = new ConcurrentHashMap(); + + @Override + public List collect() { + List mfs = new ArrayList(); + CounterMetricFamily blockingCounter = new CounterMetricFamily(SINK_BLOCKING_TIME, + sinkBlockTimeHelp, DEST_LABEL_LIST); + for (SinkMetricsHolder smh : instances.values()) { + blockingCounter.addMetric(smh.destLabelValues, smh.eventsSinkBlockingTime.doubleValue()); + } + mfs.add(blockingCounter); + return mfs; + } + + @Override + public void register(CanalInstance instance) { + final String destination = instance.getDestination(); + SinkMetricsHolder holder = new SinkMetricsHolder(); + holder.destLabelValues = Collections.singletonList(destination); + CanalEventSink sink = instance.getEventSink(); + if (!(sink instanceof EntryEventSink)) { + throw new IllegalArgumentException("CanalEventSink must be EntryEventSink"); + } + EntryEventSink entrySink = (EntryEventSink) sink; + holder.eventsSinkBlockingTime = entrySink.getEventsSinkBlockingTime(); + Preconditions.checkNotNull(holder.destLabelValues); + Preconditions.checkNotNull(holder.eventsSinkBlockingTime); + SinkMetricsHolder old = instances.putIfAbsent(destination, holder); + if (old != null) { + logger.warn("Ignore repeated SinkCollector register for instance {}.", destination); + } + } + + @Override + public void unregister(CanalInstance instance) { + final String destination = instance.getDestination(); + instances.remove(destination); + } + + private class SinkMetricsHolder { + private AtomicLong eventsSinkBlockingTime; + private List destLabelValues; + } +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java similarity index 66% rename from prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java rename to prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index 4c212c4c..3784af9b 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MemoryStoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -1,6 +1,8 @@ package com.alibaba.otter.canal.prometheus.impl; +import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.prometheus.CanalInstanceExports; +import com.alibaba.otter.canal.prometheus.InstanceRegistry; import com.alibaba.otter.canal.store.CanalEventStore; import com.alibaba.otter.canal.store.CanalStoreException; import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer; @@ -12,21 +14,24 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; /** * @author Chuanyi Li */ -public class MemoryStoreCollector extends Collector { +public class StoreCollector extends Collector implements InstanceRegistry { private static final Class clazz = MemoryEventStoreWithBuffer.class; - private final String destination; - private final AtomicLong putSequence; - private final AtomicLong ackSequence; - private final String putHelp; - private final String ackHelp; + private static final String PRODUCE = "canal_instance_store_produce_seq"; + private static final String CONSUME = "canal_instance_store_consume_seq"; + private static final String produceHelp = "Produced sequence of canal instance"; + private static final String consumeHelp = "Consumed sequence of canal instance"; + private final ConcurrentMap instances = new ConcurrentHashMap(); - public MemoryStoreCollector(CanalEventStore store, String destination) { + + public StoreCollector(CanalEventStore store, String destination) { this.destination = destination; if (!(store instanceof MemoryEventStoreWithBuffer)) { throw new IllegalArgumentException("EventStore must be MemoryEventStoreWithBuffer"); @@ -34,8 +39,6 @@ public class MemoryStoreCollector extends Collector { MemoryEventStoreWithBuffer ms = (MemoryEventStoreWithBuffer) store; putSequence = getDeclaredValue(ms, "putSequence"); ackSequence = getDeclaredValue(ms, "ackSequence"); - putHelp = "Produced sequence of canal instance " + destination; - ackHelp = "Consumed sequence of canal instance " + destination; } @Override @@ -67,4 +70,16 @@ public class MemoryStoreCollector extends Collector { return value; } + @Override public void register(CanalInstance instance) { + + } + + @Override public void unregister(CanalInstance instance) { + + } + + private class StoreMetricsHolder { + private AtomicLong putSeq; + private AtomicLong ackSeq; + } } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java index e44476d7..cca3fa6b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/CanalServerWithNettyProfiler.java @@ -1,105 +1,39 @@ package com.alibaba.otter.canal.server.netty; -import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; +import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator.ClientRequestResult; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; /** * @author Chuanyi Li */ public class CanalServerWithNettyProfiler { - public static final ClientInstanceProfilerFactory DISABLED = new DefaultClientInstanceProfilerFactory(); - private volatile ClientInstanceProfilerFactory clientInstanceProfilerFactory; - private final ConcurrentMap cliPfs; - private final CanalServerWithEmbedded server; + public static final ClientInstanceProfiler NOP = new DefaultClientInstanceProfiler(); + private ClientInstanceProfiler instanceProfiler; private static class SingletonHolder { private static CanalServerWithNettyProfiler SINGLETON = new CanalServerWithNettyProfiler(); } private CanalServerWithNettyProfiler() { - this.clientInstanceProfilerFactory = DISABLED; - this.cliPfs = new ConcurrentHashMap(); - this.server = CanalServerWithEmbedded.instance(); + this.instanceProfiler = NOP; } public static CanalServerWithNettyProfiler profiler() { return SingletonHolder.SINGLETON; } - public void profiling(String destination, ClientRequestResult result) { - if (isDisabled()) { - return; - } - ClientInstanceProfiler profiler = cliPfs.get(destination); - if (profiler != null) { - profiler.profiling(result); - } + public void profiling(ClientRequestResult result) { + instanceProfiler.profiling(result); } - public void start(String destination) { - if (isDisabled()) { - return; - } - if (server.isStart(destination)) { - throw new IllegalStateException("Instance profiler should not be start while running."); - } - ClientInstanceProfiler profiler = clientInstanceProfilerFactory.create(destination); - profiler.start(); - cliPfs.put(destination, profiler); + public void setInstanceProfiler(ClientInstanceProfiler instanceProfiler) { + this.instanceProfiler = instanceProfiler; } - /** - * Remove instance profiler for specified instance. - * Only accepted while instance is not running. - * @param destination canal instance destination - */ - public void stop(String destination) { - if (isDisabled()) { - return; - } - if (server.isStart(destination)) { - throw new IllegalStateException("Instance profiler should not be stop while running."); - } - ClientInstanceProfiler profiler = cliPfs.remove(destination); - if (profiler != null && profiler.isStart()) { - profiler.stop(); - } - } - - public void setInstanceProfilerFactory(ClientInstanceProfilerFactory factory) { - this.clientInstanceProfilerFactory = factory; - } - - private boolean isDisabled() { - return clientInstanceProfilerFactory == DISABLED || clientInstanceProfilerFactory == null; - } - - private ClientInstanceProfiler tryGet(String destination) { - //try fast get - ClientInstanceProfiler profiler = cliPfs.get(destination); - if (profiler == null) { - synchronized (cliPfs) { - if (server.isStart(destination)) { - // avoid overwriting - cliPfs.putIfAbsent(destination, clientInstanceProfilerFactory.create(destination)); - profiler = cliPfs.get(destination); - if (!profiler.isStart()) { - profiler.start(); - } - } - } - } - return profiler; - } - - private static class DefaultClientInstanceProfilerFactory implements ClientInstanceProfilerFactory { + private static class DefaultClientInstanceProfiler extends AbstractCanalLifeCycle implements ClientInstanceProfiler { @Override - public ClientInstanceProfiler create(String destination) { - throw new UnsupportedOperationException(); - } + public void profiling(ClientRequestResult result) {} } } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java index bd4e2dbc..2ffbd46e 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfiler.java @@ -1,15 +1,13 @@ package com.alibaba.otter.canal.server.netty; import com.alibaba.otter.canal.common.CanalLifeCycle; -import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator; +import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator.ClientRequestResult; /** * @author Chuanyi Li */ public interface ClientInstanceProfiler extends CanalLifeCycle { - String getDestination(); - - void profiling(ChannelFutureAggregator.ClientRequestResult result); + void profiling(ClientRequestResult result); } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java deleted file mode 100644 index 12b54bc6..00000000 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/ClientInstanceProfilerFactory.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.alibaba.otter.canal.server.netty; - -/** - * @author Chuanyi Li - */ -public interface ClientInstanceProfilerFactory { - - ClientInstanceProfiler create(String destination); - -} diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index 89755431..b962b9a0 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -45,7 +45,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { if (future.getCause() != null) { result.channelError = future.getCause(); } - profiler().profiling(result.destination, result); + profiler().profiling(result); } /** From fec9803aaef80a86ef79c7144806e8263c3fb951 Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Tue, 7 Aug 2018 17:17:33 +0800 Subject: [PATCH 062/360] Refactor metrics code. --- .../common/utils/SerializedLongAdder.java | 31 ---- .../parse/inbound/AbstractEventParser.java | 2 +- .../mysql/AbstractMysqlEventParser.java | 32 ++--- .../parse/inbound/mysql/MysqlConnection.java | 8 +- .../parse/inbound/mysql/MysqlEventParser.java | 7 +- .../mysql/MysqlMultiStageCoprocessor.java | 8 +- .../prometheus/CanalInstanceExports.java | 134 +++++++----------- .../canal/prometheus/CanalServerExports.java | 17 --- .../canal/prometheus/PrometheusService.java | 33 +++-- .../canal/prometheus/impl/EntryCollector.java | 78 ++++++++-- .../canal/prometheus/impl/MetaCollector.java | 27 ++-- .../prometheus/impl/MysqlParserCollector.java | 74 ---------- .../prometheus/impl/ParserCollector.java | 114 +++++++++++++++ ...PrometheusCanalEventDownStreamHandler.java | 67 +++++++-- .../canal/prometheus/impl/SinkCollector.java | 27 ++-- .../canal/prometheus/impl/StoreCollector.java | 131 +++++++++++------ .../memory/MemoryEventStoreWithBuffer.java | 19 +++ 17 files changed, 476 insertions(+), 333 deletions(-) delete mode 100644 common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java delete mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java delete mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java create mode 100644 prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java diff --git a/common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java b/common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java deleted file mode 100644 index 4d86a99e..00000000 --- a/common/src/main/java/com/alibaba/otter/canal/common/utils/SerializedLongAdder.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.alibaba.otter.canal.common.utils; - -/** - * 用于dump记录counter - * 这种场景下, - * 写操作>>读操作 - * dump线程为串行,读取counter时,并不严格要求最新值可见,使用普通long即可。 - * - * @author Chuanyi Li - */ -public class SerializedLongAdder { - - private long value = 0; - - public SerializedLongAdder(long initial) { - this.value = initial; - } - - public void add(long x) { - value += x; - } - - public void increment() { - value ++; - } - - public long get() { - return value; - } - -} diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index 62096b6a..cd50afa4 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -9,7 +9,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; -import com.alibaba.otter.canal.common.utils.SerializedLongAdder; + import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.math.RandomUtils; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index 5209b178..db21a9e1 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -1,8 +1,8 @@ package com.alibaba.otter.canal.parse.inbound.mysql; import java.nio.charset.Charset; +import java.util.concurrent.atomic.AtomicLong; -import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,24 +22,24 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition; public abstract class AbstractMysqlEventParser extends AbstractEventParser { - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - protected static final long BINLOG_START_OFFEST = 4L; + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + protected static final long BINLOG_START_OFFEST = 4L; - protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory(); - protected boolean enableTsdb = false; + protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory(); + protected boolean enableTsdb = false; protected String tsdbSpringXml; protected TableMetaTSDB tableMetaTSDB; // 编码信息 - protected byte connectionCharsetNumber = (byte) 33; - protected Charset connectionCharset = Charset.forName("UTF-8"); - protected boolean filterQueryDcl = false; - protected boolean filterQueryDml = false; - protected boolean filterQueryDdl = false; - protected boolean filterRows = false; - protected boolean filterTableError = false; - protected boolean useDruidDdlFilter = true; - private final SerializedLongAdder eventsPublishBlockingTime = new SerializedLongAdder(0L); + protected byte connectionCharsetNumber = (byte) 33; + protected Charset connectionCharset = Charset.forName("UTF-8"); + protected boolean filterQueryDcl = false; + protected boolean filterQueryDml = false; + protected boolean filterQueryDdl = false; + protected boolean filterRows = false; + protected boolean filterTableError = false; + protected boolean useDruidDdlFilter = true; + private final AtomicLong eventsPublishBlockingTime = new AtomicLong(0L); protected BinlogParser buildParser() { LogEventConvert convert = new LogEventConvert(); @@ -207,8 +207,8 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { public void setTableMetaTSDBFactory(TableMetaTSDBFactory tableMetaTSDBFactory) { this.tableMetaTSDBFactory = tableMetaTSDBFactory; } - - public SerializedLongAdder getEventsPublishBlockingTime() { + + public AtomicLong getEventsPublishBlockingTime() { return this.eventsPublishBlockingTime; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index c0c658fb..d21bb49c 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -7,8 +7,8 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; import java.util.List; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; -import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.slf4j.Logger; @@ -52,7 +52,7 @@ public class MysqlConnection implements ErosaConnection { private AuthenticationInfo authInfo; protected int connTimeout = 5 * 1000; // 5秒 protected int soTimeout = 60 * 60 * 1000; // 1小时 - private SerializedLongAdder receivedBinlogBytes; + private AtomicLong receivedBinlogBytes; public MysqlConnection(){ } @@ -473,7 +473,7 @@ public class MysqlConnection implements ErosaConnection { private void accumulateReceivedBytes(long x) { if (receivedBinlogBytes != null) { - receivedBinlogBytes.add(x); + receivedBinlogBytes.addAndGet(x); } } @@ -618,7 +618,7 @@ public class MysqlConnection implements ErosaConnection { this.authInfo = authInfo; } - public void setReceivedBinlogBytes(SerializedLongAdder receivedBinlogBytes) { + public void setReceivedBinlogBytes(AtomicLong receivedBinlogBytes) { this.receivedBinlogBytes = receivedBinlogBytes; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index 2b1e65e2..fefb1cbe 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -10,7 +10,6 @@ import java.util.Map; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicLong; -import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.apache.commons.lang.StringUtils; import org.springframework.util.CollectionUtils; @@ -73,7 +72,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 // instance received binlog bytes - private final SerializedLongAdder receivedBinlogBytes = new SerializedLongAdder(0L); + private final AtomicLong receivedBinlogBytes = new AtomicLong(0L); protected ErosaConnection buildErosaConnection() { return buildMysqlConnection(this.runningInfo); @@ -516,7 +515,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE private Long findTransactionBeginPosition(ErosaConnection mysqlConnection, final EntryPosition entryPosition) throws IOException { // 针对开始的第一条为非Begin记录,需要从该binlog扫描 - final AtomicLong preTransactionStartPosition = new AtomicLong(0L); + final java.util.concurrent.atomic.AtomicLong preTransactionStartPosition = new java.util.concurrent.atomic.AtomicLong(0L); mysqlConnection.reconnect(); mysqlConnection.seek(entryPosition.getJournalName(), 4L, new SinkFunction() { @@ -917,7 +916,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE - public SerializedLongAdder getReceivedBinlogBytes() { + public AtomicLong getReceivedBinlogBytes() { return this.receivedBinlogBytes; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index e348c386..0ceb91dd 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -2,9 +2,9 @@ package com.alibaba.otter.canal.parse.inbound.mysql; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; -import com.alibaba.otter.canal.common.utils.SerializedLongAdder; import org.apache.commons.lang.StringUtils; import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; @@ -65,7 +65,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement private ExecutorService stageExecutor; private String destination; private volatile CanalParseException exception; - private SerializedLongAdder eventsPublishBlockingTime; + private AtomicLong eventsPublishBlockingTime; public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, EventTransactionBuffer transactionBuffer, String destination){ @@ -176,7 +176,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement } disruptorMsgBuffer.publish(next); if (fullTimes > 0) { - eventsPublishBlockingTime.add(System.nanoTime() - blockingStart); + eventsPublishBlockingTime.addAndGet(System.nanoTime() - blockingStart); } break; } catch (InsufficientCapacityException e) { @@ -457,7 +457,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement this.connection = connection; } - public void setEventsPublishBlockingTime(SerializedLongAdder eventsPublishBlockingTime) { + public void setEventsPublishBlockingTime(AtomicLong eventsPublishBlockingTime) { this.eventsPublishBlockingTime = eventsPublishBlockingTime; } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java index 6908a3bd..487855fa 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java @@ -2,9 +2,6 @@ package com.alibaba.otter.canal.prometheus; import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.prometheus.impl.*; -import com.alibaba.otter.canal.sink.CanalEventSink; -import com.alibaba.otter.canal.sink.entry.EntryEventSink; -import com.alibaba.otter.canal.store.CanalStoreException; import io.prometheus.client.Collector; import io.prometheus.client.CollectorRegistry; import org.slf4j.Logger; @@ -18,98 +15,71 @@ import java.util.List; */ public class CanalInstanceExports { - private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class); - public static final String DEST = "destination"; - public static final String[] DEST_LABELS = {DEST}; - public static final List DEST_LABEL_LIST = Collections.singletonList(DEST); -// private final String destination; - private Collector storeCollector; - private Collector delayCollector; - private Collector metaCollector; - private Collector sinkCollector; - private Collector parserCollector; + private static final Logger logger = LoggerFactory.getLogger(CanalInstanceExports.class); + public static final String DEST = "destination"; + public static final String[] DEST_LABELS = {DEST}; + public static final List DEST_LABELS_LIST = Collections.singletonList(DEST); + private final Collector storeCollector; + private final Collector entryCollector; + private final Collector metaCollector; + private final Collector sinkCollector; + private final Collector parserCollector; - public CanalInstanceExports() { -// this.destination = instance.getDestination(); -// initEventsMetrics(instance); -// initStoreCollector(instance); -// initMetaCollector(instance); -// initSinkCollector(instance); -// initParserCollector(instance); + private CanalInstanceExports() { + this.storeCollector = StoreCollector.instance(); + this.entryCollector = EntryCollector.instance(); + this.metaCollector = MetaCollector.instance(); + this.sinkCollector = SinkCollector.instance(); + this.parserCollector = ParserCollector.instance(); } + private static class SingletonHolder { + private static final CanalInstanceExports SINGLETON = new CanalInstanceExports(); + } + public static CanalInstanceExports instance() { + return SingletonHolder.SINGLETON; + } -// static CanalInstanceExports forInstance(CanalInstance instance) { -// return new CanalInstanceExports(instance); -// } + public void initialize() { + storeCollector.register(); + entryCollector.register(); + metaCollector.register(); + sinkCollector.register(); + parserCollector.register(); + } + + public void terminate() { + CollectorRegistry.defaultRegistry.unregister(storeCollector); + CollectorRegistry.defaultRegistry.unregister(entryCollector); + CollectorRegistry.defaultRegistry.unregister(metaCollector); + CollectorRegistry.defaultRegistry.unregister(sinkCollector); + CollectorRegistry.defaultRegistry.unregister(parserCollector); + } void register(CanalInstance instance) { - if (delayCollector != null) { - delayCollector.register(); - } - if (storeCollector != null) { - storeCollector.register(); - } - if (metaCollector != null) { - metaCollector.register(); - } - if (sinkCollector != null) { - sinkCollector.register(); - } - if (parserCollector != null) { - parserCollector.register(); - } + requiredInstanceRegistry(storeCollector).register(instance); + requiredInstanceRegistry(entryCollector).register(instance); + requiredInstanceRegistry(metaCollector).register(instance); + requiredInstanceRegistry(sinkCollector).register(instance); + requiredInstanceRegistry(parserCollector).register(instance); + logger.info("Successfully register metrics for instance {}.", instance.getDestination()); } void unregister(CanalInstance instance) { - if (delayCollector != null) { - CollectorRegistry.defaultRegistry.unregister(delayCollector); - } - if (storeCollector != null) { - CollectorRegistry.defaultRegistry.unregister(storeCollector); - } - if (metaCollector != null) { - CollectorRegistry.defaultRegistry.unregister(metaCollector); - } - if (sinkCollector != null) { - CollectorRegistry.defaultRegistry.unregister(sinkCollector); - } - if (parserCollector != null) { - CollectorRegistry.defaultRegistry.unregister(parserCollector); - } + requiredInstanceRegistry(storeCollector).unregister(instance); + requiredInstanceRegistry(entryCollector).unregister(instance); + requiredInstanceRegistry(metaCollector).unregister(instance); + requiredInstanceRegistry(sinkCollector).unregister(instance); + requiredInstanceRegistry(parserCollector).unregister(instance); + logger.info("Successfully unregister metrics for instance {}.", instance.getDestination()); } - private void initEventsMetrics(CanalInstance instance) { - CanalEventSink sink = instance.getEventSink(); - if (sink instanceof EntryEventSink) { - EntryEventSink entryEventSink = (EntryEventSink) sink; - // TODO ensure not to add handler again -// PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(destination); -// entryEventSink.addHandler(handler); -// delayCollector = handler.getCollector(); - } else { - logger.warn("This impl register metrics for only EntryEventSink, skip."); + private InstanceRegistry requiredInstanceRegistry(Collector collector) { + if (!(collector instanceof InstanceRegistry)) { + throw new IllegalArgumentException("Canal collector need to implement InstanceRegistry."); } + return (InstanceRegistry) collector; } -// private void initStoreCollector(CanalInstance instance) { -// try { -// storeCollector = new StoreCollector(instance.getEventStore(), destination); -// } catch (CanalStoreException cse) { -// logger.warn("Failed to register metrics for destination {}.", destination, cse); -// } -// } - - private void initMetaCollector(CanalInstance instance) { - metaCollector = new MetaCollector(instance); - } - - private void initSinkCollector(CanalInstance instance) { -// sinkCollector = new EntryCollector(instance.getEventSink(), instance.getDestination()); - } - - private void initParserCollector(CanalInstance instance) { - parserCollector = new MysqlParserCollector(instance.getEventParser(), instance.getDestination()); - } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java deleted file mode 100644 index ded44fb8..00000000 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalServerExports.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.alibaba.otter.canal.prometheus; - - -/** - * @author Chuanyi Li - */ -public class CanalServerExports { - - private static boolean initialized = false; - - public static synchronized void initialize() { - if (!initialized) { - initialized = true; - } - } - -} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index c3a241bd..9e3ef18c 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -20,12 +20,13 @@ import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler. public class PrometheusService implements CanalMetricsService { private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); - private final CanalInstanceExports instanceExports = new CanalInstanceExports(); + private final CanalInstanceExports instanceExports; private volatile boolean running = false; private HTTPServer server; private ClientInstanceProfiler clientProfiler = new PrometheusClientInstanceProfiler(); private PrometheusService() { + this.instanceExports = CanalInstanceExports.instance(); } private static class SingletonHolder { @@ -49,8 +50,7 @@ public class PrometheusService implements CanalMetricsService { try { // JVM exports DefaultExports.initialize(); - // Canal server level exports - CanalServerExports.initialize(); + instanceExports.initialize(); if (!clientProfiler.isStart()) { clientProfiler.start(); } @@ -65,13 +65,17 @@ public class PrometheusService implements CanalMetricsService { @Override public void terminate() { running = false; - instanceExports.unregister(); - if (clientProfiler.isStart()) { - clientProfiler.stop(); - } - profiler().setInstanceProfiler(NOP); - if (server != null) { - server.stop(); + try { + instanceExports.terminate(); + if (clientProfiler.isStart()) { + clientProfiler.stop(); + } + profiler().setInstanceProfiler(NOP); + if (server != null) { + server.stop(); + } + } catch (Throwable t) { + logger.warn("Something happened while terminating.", t); } } @@ -87,9 +91,7 @@ public class PrometheusService implements CanalMetricsService { return; } try { - CanalInstanceExports export = CanalInstanceExports.forInstance(instance); - export.register(); - exports.put(instance.getDestination(), export); + instanceExports.register(instance); } catch (Throwable t) { logger.warn("Unable to register instance exports for {}.", instance.getDestination(), t); } @@ -102,10 +104,7 @@ public class PrometheusService implements CanalMetricsService { logger.warn("Try unregister metrics after destination {} is stopped.", instance.getDestination()); } try { - CanalInstanceExports export = exports.remove(instance.getDestination()); - if (export != null) { - export.unregister(); - } + instanceExports.unregister(instance); } catch (Throwable t) { logger.warn("Unable to unregister instance exports for {}.", instance.getDestination(), t); } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java index 83e4ef95..dd282065 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java @@ -2,10 +2,12 @@ package com.alibaba.otter.canal.prometheus.impl; import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.prometheus.InstanceRegistry; +import com.alibaba.otter.canal.sink.CanalEventDownStreamHandler; import com.alibaba.otter.canal.sink.CanalEventSink; import com.alibaba.otter.canal.sink.entry.EntryEventSink; import com.google.common.base.Preconditions; import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; import io.prometheus.client.GaugeMetricFamily; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,27 +19,43 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; /** * @author Chuanyi Li */ public class EntryCollector extends Collector implements InstanceRegistry { - private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); - private static final String DELAY_NAME = "canal_instance_traffic_delay"; - private static final String delayHelpName = "Traffic delay of canal instance"; - private final ConcurrentMap instances = new ConcurrentHashMap(); + private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); + private static final String DELAY = "canal_instance_traffic_delay"; + private static final String TRANSACTION = "canal_instance_transactions"; + private static final String DELAY_HELP = "Traffic delay of canal instance"; + private static final String TRANSACTION_HELP = "Transactions counter of canal instance"; + private final ConcurrentMap instances = new ConcurrentHashMap(); + + private EntryCollector() {} + + private static class SingletonHolder { + private static final EntryCollector SINGLETON = new EntryCollector(); + } + + public static EntryCollector instance() { + return SingletonHolder.SINGLETON; + } @Override public List collect() { List mfs = new ArrayList(); - GaugeMetricFamily delay = new GaugeMetricFamily(DELAY_NAME, - delayHelpName, DEST_LABEL_LIST); + GaugeMetricFamily delay = new GaugeMetricFamily(DELAY, + DELAY_HELP, DEST_LABELS_LIST); + CounterMetricFamily transactions = new CounterMetricFamily(TRANSACTION, + TRANSACTION_HELP, DEST_LABELS_LIST); for (EntryMetricsHolder emh : instances.values()) { delay.addMetric(emh.destLabelValues, emh.latestExecTime.doubleValue()); + transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue()); } mfs.add(delay); + mfs.add(transactions); return mfs; } @@ -51,25 +69,61 @@ public class EntryCollector extends Collector implements InstanceRegistry { throw new IllegalArgumentException("CanalEventSink must be EntryEventSink"); } EntryEventSink entrySink = (EntryEventSink) sink; - PrometheusCanalEventDownStreamHandler handler = new PrometheusCanalEventDownStreamHandler(); + PrometheusCanalEventDownStreamHandler handler = assembleHandler(entrySink); holder.latestExecTime = handler.getLatestExecuteTime(); - entrySink.addHandler(handler, 0); - Preconditions.checkNotNull(holder.destLabelValues); + holder.transactionCounter = handler.getTransactionCounter(); Preconditions.checkNotNull(holder.latestExecTime); - EntryMetricsHolder old = instances.putIfAbsent(destination, holder); + Preconditions.checkNotNull(holder.transactionCounter); + EntryMetricsHolder old = instances.put(destination, holder); if (old != null) { - logger.warn("Ignore repeated EntryCollector register for instance {}.", destination); + logger.warn("Remove stale EntryCollector for instance {}.", destination); } } @Override public void unregister(CanalInstance instance) { final String destination = instance.getDestination(); + CanalEventSink sink = instance.getEventSink(); + if (!(sink instanceof EntryEventSink)) { + throw new IllegalArgumentException("CanalEventSink must be EntryEventSink"); + } + unloadHandler((EntryEventSink) sink); instances.remove(destination); } + private PrometheusCanalEventDownStreamHandler assembleHandler(EntryEventSink entrySink) { + PrometheusCanalEventDownStreamHandler ph = new PrometheusCanalEventDownStreamHandler(); + List handlers = entrySink.getHandlers(); + for (CanalEventDownStreamHandler handler : handlers) { + if (handler instanceof PrometheusCanalEventDownStreamHandler) { + throw new IllegalStateException("PrometheusCanalEventDownStreamHandler already exists in handlers."); + } + } + entrySink.addHandler(ph, 0); + return ph; + } + + private void unloadHandler(EntryEventSink entrySink) { + List handlers = entrySink.getHandlers(); + int i = 0; + for (; i < handlers.size(); i++) { + if (handlers.get(i) instanceof PrometheusCanalEventDownStreamHandler) { + break; + } + } + entrySink.removeHandler(i); + // Ensure no PrometheusCanalEventDownStreamHandler + handlers = entrySink.getHandlers(); + for (CanalEventDownStreamHandler handler : handlers) { + if (handler instanceof PrometheusCanalEventDownStreamHandler) { + throw new IllegalStateException("Multiple prometheusCanalEventDownStreamHandler exists in handlers."); + } + } + } + private class EntryMetricsHolder { private AtomicLong latestExecTime; + private AtomicLong transactionCounter; private List destLabelValues; } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java index eb5a2bde..aec9a424 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java @@ -23,18 +23,28 @@ import java.util.concurrent.ConcurrentMap; */ public class MetaCollector extends Collector implements InstanceRegistry { - private static final List InfoLabel = Arrays.asList("destination", "mode"); - private static final Logger logger = LoggerFactory.getLogger(MetaCollector.class); - private static final String SUBSCRIPTION = "canal_instance"; - private static final String subscriptionHelp = "Canal instance"; - private final ConcurrentMap instances = new ConcurrentHashMap(); + private static final List InfoLabel = Arrays.asList("destination", "mode"); + private static final Logger logger = LoggerFactory.getLogger(MetaCollector.class); + private static final String SUBSCRIPTION = "canal_instance"; + private static final String SUBSCRIPTION_HELP = "Canal instance"; + private final ConcurrentMap instances = new ConcurrentHashMap(); + + private MetaCollector() {} + + private static class SingletonHolder { + private static final MetaCollector SINGLETON = new MetaCollector(); + } + + public static MetaCollector instance() { + return SingletonHolder.SINGLETON; + } @Override public List collect() { List mfs = new ArrayList(); GaugeMetricFamily instanceInfo = new GaugeMetricFamily( SUBSCRIPTION, - subscriptionHelp, + SUBSCRIPTION_HELP, InfoLabel); for (Map.Entry nme : instances.entrySet()) { final String destination = nme.getKey(); @@ -54,11 +64,10 @@ public class MetaCollector extends Collector implements InstanceRegistry { String mode = (instance instanceof CanalInstanceWithSpring) ? "spring" : "manager"; holder.infoLabelValues = Arrays.asList(destination, mode); holder.metaManager = instance.getMetaManager(); - Preconditions.checkNotNull(holder.infoLabelValues); Preconditions.checkNotNull(holder.metaManager); - MetaMetricsHolder old = instances.putIfAbsent(destination, holder); + MetaMetricsHolder old = instances.put(destination, holder); if (old != null) { - logger.warn("Ignore repeated MetaCollector register for instance {}.", destination); + logger.warn("Remove stale MetaCollector for instance {}.", destination); } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java deleted file mode 100644 index c8543f3c..00000000 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MysqlParserCollector.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.alibaba.otter.canal.prometheus.impl; - -import com.alibaba.otter.canal.common.utils.SerializedLongAdder; -import com.alibaba.otter.canal.parse.CanalEventParser; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import io.prometheus.client.Collector; -import io.prometheus.client.CounterMetricFamily; -import io.prometheus.client.GaugeMetricFamily; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; - -/** - * @author Chuanyi Li - */ -public class MysqlParserCollector extends Collector { - - private static final String PUBLISH_BLOCKING_TIME = "canal_instance_publish_blocking_time"; - private static final String RECEIVED_BINLOG_BYTES = "canal_instance_received_binlog_bytes"; - private static final String PARSER_MODE = "canal_instance_parser_mode"; - private static final String MODE_LABEL = "parallel"; - private final List destLabelValues; - private final List modeLabelValues; - private final SerializedLongAdder receivedBinlogBytes; - private final SerializedLongAdder eventsPublishBlockingTime; - private final String publishBlockingTimeHelp; - private final String receivedBinlogBytesHelp; - private final String modeHelp; - private final List modeLabels; - private final Boolean isParallel; - - public MysqlParserCollector(CanalEventParser parser, String destination) { - if (!(parser instanceof MysqlEventParser)) { - throw new IllegalArgumentException("CanalEventParser must be MysqlEventParser"); - } - this.destLabelValues = Collections.singletonList(destination); - this.eventsPublishBlockingTime = ((MysqlEventParser)parser).getEventsPublishBlockingTime(); - this.publishBlockingTimeHelp = "Blocking time of publishing of instance " + destination; - this.receivedBinlogBytes = ((MysqlEventParser)parser).getReceivedBinlogBytes(); - this.receivedBinlogBytesHelp = "Received binlog bytes of instance" + destination; - this.isParallel = ((MysqlEventParser)parser).isParallel(); - this.modeHelp = "Parser mode of instance" + destination; - this.modeLabels = Arrays.asList(DEST, MODE_LABEL); - this.modeLabelValues = Arrays.asList(destination, isParallel.toString()); - } - - @Override public List collect() { - List mfs = new ArrayList(); - // canal_instance_received_binlog_bytes - CounterMetricFamily bytesCounter = new CounterMetricFamily(RECEIVED_BINLOG_BYTES, - receivedBinlogBytesHelp, DEST_LABEL_LIST); - bytesCounter.addMetric(destLabelValues, receivedBinlogBytes.get()); - mfs.add(bytesCounter); - // canal_instance_parser_mode - GaugeMetricFamily modeGauge = new GaugeMetricFamily(PARSER_MODE, - modeHelp, - modeLabels); - modeGauge.addMetric(modeLabelValues, 1); - mfs.add(modeGauge); - // canal_instance_publish_blocking_time - if (isParallel) { - CounterMetricFamily blockingCounter = new CounterMetricFamily(PUBLISH_BLOCKING_TIME, - publishBlockingTimeHelp, DEST_LABEL_LIST); - blockingCounter.addMetric(destLabelValues, eventsPublishBlockingTime.get()); - mfs.add(blockingCounter); - } - return mfs; - } -} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java new file mode 100644 index 00000000..60906989 --- /dev/null +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java @@ -0,0 +1,114 @@ +package com.alibaba.otter.canal.prometheus.impl; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.parse.CanalEventParser; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; +import com.alibaba.otter.canal.prometheus.InstanceRegistry; +import com.google.common.base.Preconditions; +import io.prometheus.client.Collector; +import io.prometheus.client.CounterMetricFamily; +import io.prometheus.client.GaugeMetricFamily; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicLong; + +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; + +/** + * @author Chuanyi Li + */ +public class ParserCollector extends Collector implements InstanceRegistry { + + private static final Logger logger = LoggerFactory.getLogger(ParserCollector.class); + private static final String PUBLISH_BLOCKING = "canal_instance_publish_blocking_time"; + private static final String RECEIVED_BINLOG = "canal_instance_received_binlog_bytes"; + private static final String PARSER_MODE = "canal_instance_parser_mode"; + private static final String MODE_LABEL = "parallel"; + private static final String PUBLISH_BLOCKING_HELP = "Publish blocking time of dump thread"; + private static final String RECEIVED_BINLOG_HELP = "Received binlog bytes"; + private static final String MODE_HELP = "Parser mode(parallel/serial) of instance"; + private final List modeLabels = Arrays.asList(DEST, MODE_LABEL); + private final ConcurrentMap instances = new ConcurrentHashMap(); + + private ParserCollector() {} + + private static class SingletonHolder { + private static final ParserCollector SINGLETON = new ParserCollector(); + } + + public static ParserCollector instance() { + return SingletonHolder.SINGLETON; + } + + @Override + public List collect() { + List mfs = new ArrayList(); + boolean hasParallel = false; + CounterMetricFamily bytesCounter = new CounterMetricFamily(RECEIVED_BINLOG, + RECEIVED_BINLOG_HELP, DEST_LABELS_LIST); + GaugeMetricFamily modeGauge = new GaugeMetricFamily(PARSER_MODE, + MODE_HELP, modeLabels); + CounterMetricFamily blockingCounter = new CounterMetricFamily(PUBLISH_BLOCKING, + PUBLISH_BLOCKING_HELP, DEST_LABELS_LIST); + for (ParserMetricsHolder emh : instances.values()) { + if (emh.isParallel) { + blockingCounter.addMetric(emh.destLabelValues, emh.eventsPublishBlockingTime.doubleValue()); + hasParallel = true; + } + modeGauge.addMetric(emh.modeLabelValues, 1); + bytesCounter.addMetric(emh.destLabelValues, emh.receivedBinlogBytes.doubleValue()); + + } + mfs.add(bytesCounter); + mfs.add(modeGauge); + if (hasParallel) { + mfs.add(blockingCounter); + } + return mfs; + } + + @Override + public void register(CanalInstance instance) { + final String destination = instance.getDestination(); + ParserMetricsHolder holder = new ParserMetricsHolder(); + CanalEventParser parser = instance.getEventParser(); + if (!(parser instanceof MysqlEventParser)) { + throw new IllegalArgumentException("CanalEventParser must be MysqlEventParser"); + } + MysqlEventParser mysqlParser = (MysqlEventParser) parser; + holder.destLabelValues = Collections.singletonList(destination); + holder.modeLabelValues = Arrays.asList(destination, Boolean.toString(mysqlParser.isParallel())); + holder.eventsPublishBlockingTime = mysqlParser.getEventsPublishBlockingTime(); + holder.receivedBinlogBytes = mysqlParser.getReceivedBinlogBytes(); + holder.isParallel = mysqlParser.isParallel(); + Preconditions.checkNotNull(holder.eventsPublishBlockingTime); + Preconditions.checkNotNull(holder.receivedBinlogBytes); + ParserMetricsHolder old = instances.put(destination, holder); + if (old != null) { + logger.warn("Remove stale ParserCollector for instance {}.", destination); + } + } + + @Override + public void unregister(CanalInstance instance) { + final String destination = instance.getDestination(); + instances.remove(destination); + } + + private class ParserMetricsHolder { + private List destLabelValues; + private List modeLabelValues; + private AtomicLong receivedBinlogBytes; + private AtomicLong eventsPublishBlockingTime; + private boolean isParallel; + } + +} diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java index 763ce46b..6fc2f3d7 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -1,7 +1,9 @@ package com.alibaba.otter.canal.prometheus.impl; +import com.alibaba.otter.canal.protocol.CanalEntry.EntryType; import com.alibaba.otter.canal.sink.AbstractCanalEventDownStreamHandler; import com.alibaba.otter.canal.store.model.Event; + import java.util.List; import java.util.concurrent.atomic.AtomicLong; @@ -10,28 +12,71 @@ import java.util.concurrent.atomic.AtomicLong; */ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler> { - private final AtomicLong latestExecuteTime = new AtomicLong(0L); + private final AtomicLong latestExecuteTime = new AtomicLong(0L); + private final AtomicLong transactionCounter = new AtomicLong(0L); + private final AtomicLong rowEventCounter = new AtomicLong(0L); + private final AtomicLong rowsCounter = new AtomicLong(0L); @Override public List before(List events) { - // TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming - // see: https://dev.mysql.com/worklog/task/?id=342 - // heartbeats are sent by the master only if there is no - // more unsent events in the actual binlog file for a period longer that - // master_heartbeat_period. + long localExecTime = 0L; if (events != null && !events.isEmpty()) { - Event last = events.get(events.size() - 1); - long ts = last.getExecuteTime(); - long ls = latestExecuteTime.get(); - if (ts > ls) { - latestExecuteTime.lazySet(ts); + for (Event e : events) { + EntryType type = e.getEntryType(); + if (type == null) continue; + long exec = e.getExecuteTime(); + if (exec > 0) localExecTime = exec; + switch (type) { + case ROWDATA: + // TODO 当前proto无法直接获得荣威change的变更行数(需要parse),可考虑放到header里面 + break; + case TRANSACTIONEND: + transactionCounter.incrementAndGet(); + break; + case HEARTBEAT: + // TODO 确认一下不是canal自己产生的 + // EventType eventType = e.getEventType(); + // TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming + // see: https://dev.mysql.com/worklog/task/?id=342 + // heartbeats are sent by the master only if there is no + // more unsent events in the actual binlog file for a period longer that + // master_heartbeat_period. + break; + default: + break; + } + } + if (localExecTime > 0) { + latestExecuteTime.lazySet(localExecTime); } } return events; } + @Override + public void start() { + + super.start(); + } + + @Override + public void stop() { + super.stop(); + } + public AtomicLong getLatestExecuteTime() { return latestExecuteTime; } + public AtomicLong getTransactionCounter() { + return transactionCounter; + } + + public AtomicLong getRowsCounter() { + return rowsCounter; + } + + public AtomicLong getRowEventCounter() { + return rowEventCounter; + } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java index 11903314..fc5ed3b2 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java @@ -17,23 +17,33 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL_LIST; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; /** * @author Chuanyi Li */ public class SinkCollector extends Collector implements InstanceRegistry { - private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); - private static final String SINK_BLOCKING_TIME = "canal_instance_sink_blocking_time"; - private static final String sinkBlockTimeHelp = "Total sink blocking time"; - private final ConcurrentMap instances = new ConcurrentHashMap(); + private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); + private static final String SINK_BLOCKING_TIME = "canal_instance_sink_blocking_time"; + private static final String SINK_BLOCK_TIME_HELP = "Total sink blocking time"; + private final ConcurrentMap instances = new ConcurrentHashMap(); + + private SinkCollector() {} + + private static class SingletonHolder { + private static final SinkCollector SINGLETON = new SinkCollector(); + } + + public static SinkCollector instance() { + return SingletonHolder.SINGLETON; + } @Override public List collect() { List mfs = new ArrayList(); CounterMetricFamily blockingCounter = new CounterMetricFamily(SINK_BLOCKING_TIME, - sinkBlockTimeHelp, DEST_LABEL_LIST); + SINK_BLOCK_TIME_HELP, DEST_LABELS_LIST); for (SinkMetricsHolder smh : instances.values()) { blockingCounter.addMetric(smh.destLabelValues, smh.eventsSinkBlockingTime.doubleValue()); } @@ -52,11 +62,10 @@ public class SinkCollector extends Collector implements InstanceRegistry { } EntryEventSink entrySink = (EntryEventSink) sink; holder.eventsSinkBlockingTime = entrySink.getEventsSinkBlockingTime(); - Preconditions.checkNotNull(holder.destLabelValues); Preconditions.checkNotNull(holder.eventsSinkBlockingTime); - SinkMetricsHolder old = instances.putIfAbsent(destination, holder); + SinkMetricsHolder old = instances.put(destination, holder); if (old != null) { - logger.warn("Ignore repeated SinkCollector register for instance {}.", destination); + logger.warn("Remote stale SinkCollector for instance {}.", destination); } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index 3784af9b..8bd39574 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -1,15 +1,17 @@ package com.alibaba.otter.canal.prometheus.impl; import com.alibaba.otter.canal.instance.core.CanalInstance; -import com.alibaba.otter.canal.prometheus.CanalInstanceExports; import com.alibaba.otter.canal.prometheus.InstanceRegistry; import com.alibaba.otter.canal.store.CanalEventStore; -import com.alibaba.otter.canal.store.CanalStoreException; import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer; +import com.alibaba.otter.canal.store.model.BatchMode; +import com.google.common.base.Preconditions; import io.prometheus.client.Collector; import io.prometheus.client.CounterMetricFamily; +import io.prometheus.client.GaugeMetricFamily; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -18,68 +20,113 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; + /** * @author Chuanyi Li */ public class StoreCollector extends Collector implements InstanceRegistry { - private static final Class clazz = MemoryEventStoreWithBuffer.class; - private static final String PRODUCE = "canal_instance_store_produce_seq"; - private static final String CONSUME = "canal_instance_store_consume_seq"; - private static final String produceHelp = "Produced sequence of canal instance"; - private static final String consumeHelp = "Consumed sequence of canal instance"; - private final ConcurrentMap instances = new ConcurrentHashMap(); + private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); + private static final String PRODUCE = "canal_instance_store_produce_seq"; + private static final String CONSUME = "canal_instance_store_consume_seq"; + private static final String STORE = "canal_instance_store"; + private static final String PRODUCE_MEM = "canal_instance_store_produce_mem"; + private static final String CONSUME_MEM = "canal_instance_store_consume_mem"; + private static final String PRODUCE_HELP = "Produced events counter of canal instance"; + private static final String CONSUME_HELP = "Consumed events counter of canal instance"; + private static final String STORE_HELP = "Canal instance info"; + private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance"; + private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance"; + private final ConcurrentMap instances = new ConcurrentHashMap(); + private final List storeLabelsList = Arrays.asList(DEST, "batchMode"); + private StoreCollector() {} - public StoreCollector(CanalEventStore store, String destination) { - this.destination = destination; - if (!(store instanceof MemoryEventStoreWithBuffer)) { - throw new IllegalArgumentException("EventStore must be MemoryEventStoreWithBuffer"); - } - MemoryEventStoreWithBuffer ms = (MemoryEventStoreWithBuffer) store; - putSequence = getDeclaredValue(ms, "putSequence"); - ackSequence = getDeclaredValue(ms, "ackSequence"); + private static class SingletonHolder { + private static final StoreCollector SINGLETON = new StoreCollector(); + } + + public static StoreCollector instance() { + return SingletonHolder.SINGLETON; } @Override public List collect() { List mfs = new ArrayList(); - CounterMetricFamily put = new CounterMetricFamily("canal_instance_store_produce_seq", - putHelp, Arrays.asList(CanalInstanceExports.DEST_LABELS)); - put.addMetric(Collections.singletonList(destination), putSequence.doubleValue()); + CounterMetricFamily put = new CounterMetricFamily(PRODUCE, + PRODUCE_HELP, DEST_LABELS_LIST); + CounterMetricFamily ack = new CounterMetricFamily(CONSUME, + CONSUME_HELP, DEST_LABELS_LIST); + GaugeMetricFamily store = new GaugeMetricFamily(STORE, + STORE_HELP, storeLabelsList); + CounterMetricFamily putMem = new CounterMetricFamily(PRODUCE_MEM, + PRODUCE_MEM_HELP, DEST_LABELS_LIST); + CounterMetricFamily ackMem = new CounterMetricFamily(CONSUME_MEM, + CONSUME_MEM_HELP, DEST_LABELS_LIST); + boolean hasMem = false; + for (StoreMetricsHolder smh : instances.values()) { + final boolean isMem = smh.batchMode.isMemSize(); + put.addMetric(smh.destLabelValues, smh.putSeq.doubleValue()); + ack.addMetric(smh.destLabelValues, smh.ackSeq.doubleValue()); + store.addMetric(smh.storeLabelValues, 1); + if (isMem) { + hasMem = true; + putMem.addMetric(smh.destLabelValues, smh.putMemSize.doubleValue()); + ackMem.addMetric(smh.destLabelValues, smh.ackMemSize.doubleValue()); + } + } mfs.add(put); - CounterMetricFamily ack = new CounterMetricFamily("canal_instance_store_consume_seq", - ackHelp, Arrays.asList(CanalInstanceExports.DEST_LABELS)); - ack.addMetric(Collections.singletonList(destination), ackSequence.doubleValue()); mfs.add(ack); + mfs.add(store); + if (hasMem) { + mfs.add(putMem); + mfs.add(ackMem); + } return mfs; } - @SuppressWarnings("unchecked") - private static T getDeclaredValue(MemoryEventStoreWithBuffer store, String name) { - T value; - try { - Field putField = clazz.getDeclaredField(name); - putField.setAccessible(true); - value = (T) putField.get(store); - } catch (NoSuchFieldException e) { - throw new CanalStoreException(e); - } catch (IllegalAccessException e) { - throw new CanalStoreException(e); - } - return value; - } - @Override public void register(CanalInstance instance) { - + final String destination = instance.getDestination(); + StoreMetricsHolder holder = new StoreMetricsHolder(); + CanalEventStore store = instance.getEventStore(); + if (!(store instanceof MemoryEventStoreWithBuffer)) { + throw new IllegalArgumentException("EventStore must be MemoryEventStoreWithBuffer"); + } + MemoryEventStoreWithBuffer memStore = (MemoryEventStoreWithBuffer) store; + holder.batchMode = memStore.getBatchMode(); + holder.putSeq = memStore.getPutSequence(); + holder.ackSeq = memStore.getAckSequence(); + holder.destLabelValues = Collections.singletonList(destination); + holder.storeLabelValues = Arrays.asList(destination, memStore.getBatchMode().name()); + Preconditions.checkNotNull(holder.batchMode); + Preconditions.checkNotNull(holder.putSeq); + Preconditions.checkNotNull(holder.ackSeq); + if (holder.batchMode.isMemSize()) { + holder.putMemSize = memStore.getPutMemSize(); + holder.ackMemSize = memStore.getAckMemSize(); + Preconditions.checkNotNull(holder.putMemSize); + Preconditions.checkNotNull(holder.ackMemSize); + } + StoreMetricsHolder old = instances.putIfAbsent(destination, holder); + if (old != null) { + logger.warn("Remote stale StoreCollector for instance {}.", destination); + } } @Override public void unregister(CanalInstance instance) { - + final String destination = instance.getDestination(); + instances.remove(destination); } private class StoreMetricsHolder { - private AtomicLong putSeq; - private AtomicLong ackSeq; + private AtomicLong putSeq; + private AtomicLong ackSeq; + private BatchMode batchMode; + private AtomicLong putMemSize; + private AtomicLong ackMemSize; + private List destLabelValues; + private List storeLabelValues; } } diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index 075d1524..00ba2e71 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -562,4 +562,23 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple this.ddlIsolation = ddlIsolation; } + public AtomicLong getPutSequence() { + return putSequence; + } + + public AtomicLong getAckSequence() { + return ackSequence; + } + + public AtomicLong getPutMemSize() { + return putMemSize; + } + + public AtomicLong getAckMemSize() { + return ackMemSize; + } + + public BatchMode getBatchMode() { + return batchMode; + } } From 179afb03407f7ebc7aa2b330e6de386d5493b537 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Tue, 7 Aug 2018 22:52:30 +0800 Subject: [PATCH 063/360] Canal metrics snapshot version. --- deployer/pom.xml | 12 ++++---- deployer/src/main/bin/metrics_env.sh | 15 ---------- deployer/src/main/bin/startup.sh | 7 +---- .../parse/inbound/mysql/MysqlConnection.java | 3 +- .../mysql/MysqlMultiStageCoprocessor.java | 5 ++++ .../prometheus/CanalInstanceExports.java | 2 +- .../canal/prometheus/PrometheusService.java | 11 +++---- .../canal/prometheus/impl/EntryCollector.java | 8 +++-- .../canal/prometheus/impl/MetaCollector.java | 29 +++++++++++-------- .../prometheus/impl/ParserCollector.java | 6 ++-- ...PrometheusCanalEventDownStreamHandler.java | 21 ++++++++++---- .../PrometheusClientInstanceProfiler.java | 15 ++++++++-- .../canal/prometheus/impl/SinkCollector.java | 6 ++-- .../canal/sink/entry/EntryEventSink.java | 5 ++++ 14 files changed, 85 insertions(+), 60 deletions(-) delete mode 100644 deployer/src/main/bin/metrics_env.sh diff --git a/deployer/pom.xml b/deployer/pom.xml index 75f05805..68224ca1 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -18,12 +18,12 @@ - - - - - - + + com.alibaba.otter + canal.prometheus + ${project.version} + runtime + diff --git a/deployer/src/main/bin/metrics_env.sh b/deployer/src/main/bin/metrics_env.sh deleted file mode 100644 index 1085eae2..00000000 --- a/deployer/src/main/bin/metrics_env.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# Additional line arg for current prometheus solution -case "`uname`" in -Linux) - bin_abs_path=$(readlink -f $(dirname $0)) - ;; -*) - bin_abs_path=`cd $(dirname $0); pwd` - ;; -esac -base=${bin_abs_path}/.. -if [ $(ls $base/lib/aspectjweaver*.jar | wc -l) -eq 1 ]; then - WEAVER=$(ls $base/lib/aspectjweaver*.jar) - METRICS_OPTS=" -javaagent:"${WEAVER}" " -fi diff --git a/deployer/src/main/bin/startup.sh b/deployer/src/main/bin/startup.sh index b87652c0..5655c713 100644 --- a/deployer/src/main/bin/startup.sh +++ b/deployer/src/main/bin/startup.sh @@ -94,12 +94,7 @@ then echo LOG CONFIGURATION : $logback_configurationFile echo canal conf : $canal_conf echo CLASSPATH :$CLASSPATH -# metrics support options -# if [ -x $base/bin/metrics_env.sh ]; then -# . $base/bin/metrics_env.sh -# echo METRICS_OPTS $METRICS_OPTS -# fi - $JAVA $JAVA_OPTS $METRICS_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 & + $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.deployer.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 & echo $! > $base/bin/canal.pid echo "cd to $current_path for continue" diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index d21bb49c..54483e1f 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -52,7 +52,8 @@ public class MysqlConnection implements ErosaConnection { private AuthenticationInfo authInfo; protected int connTimeout = 5 * 1000; // 5秒 protected int soTimeout = 60 * 60 * 1000; // 1小时 - private AtomicLong receivedBinlogBytes; + // dump binlog bytes, 暂不包括meta与TSDB + private AtomicLong receivedBinlogBytes; public MysqlConnection(){ } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 0ceb91dd..31dfc595 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -187,6 +187,11 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement //LockSupport.parkNanos(1L); applyWait(++fullTimes); interupted = Thread.interrupted(); + if (fullTimes % 1000 == 0) { + long nextStart = System.nanoTime(); + eventsPublishBlockingTime.addAndGet(nextStart - blockingStart); + blockingStart = nextStart; + } } } while (!interupted && isStart()); return isStart(); diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java index 487855fa..6f50214c 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/CanalInstanceExports.java @@ -77,7 +77,7 @@ public class CanalInstanceExports { private InstanceRegistry requiredInstanceRegistry(Collector collector) { if (!(collector instanceof InstanceRegistry)) { - throw new IllegalArgumentException("Canal collector need to implement InstanceRegistry."); + throw new IllegalArgumentException("Canal prometheus collector need to implement InstanceRegistry."); } return (InstanceRegistry) collector; } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 9e3ef18c..485f6728 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -19,14 +19,15 @@ import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler. */ public class PrometheusService implements CanalMetricsService { - private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); - private final CanalInstanceExports instanceExports; - private volatile boolean running = false; - private HTTPServer server; - private ClientInstanceProfiler clientProfiler = new PrometheusClientInstanceProfiler(); + private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); + private final CanalInstanceExports instanceExports; + private volatile boolean running = false; + private HTTPServer server; + private final ClientInstanceProfiler clientProfiler; private PrometheusService() { this.instanceExports = CanalInstanceExports.instance(); + this.clientProfiler = PrometheusClientInstanceProfiler.instance(); } private static class SingletonHolder { diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java index dd282065..4aa6ff42 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java @@ -29,7 +29,7 @@ public class EntryCollector extends Collector implements InstanceRegistry { private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); private static final String DELAY = "canal_instance_traffic_delay"; private static final String TRANSACTION = "canal_instance_transactions"; - private static final String DELAY_HELP = "Traffic delay of canal instance"; + private static final String DELAY_HELP = "Traffic delay of canal instance in milliseconds"; private static final String TRANSACTION_HELP = "Transactions counter of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); @@ -51,7 +51,11 @@ public class EntryCollector extends Collector implements InstanceRegistry { CounterMetricFamily transactions = new CounterMetricFamily(TRANSACTION, TRANSACTION_HELP, DEST_LABELS_LIST); for (EntryMetricsHolder emh : instances.values()) { - delay.addMetric(emh.destLabelValues, emh.latestExecTime.doubleValue()); + long now = System.currentTimeMillis(); + long latest = emh.latestExecTime.get(); + if (now > latest) { + delay.addMetric(emh.destLabelValues, (now - latest)); + } transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue()); } mfs.add(delay); diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java index aec9a424..e51e8e29 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/MetaCollector.java @@ -11,22 +11,23 @@ import io.prometheus.client.GaugeMetricFamily; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; + /** * @author Chuanyi Li */ public class MetaCollector extends Collector implements InstanceRegistry { - private static final List InfoLabel = Arrays.asList("destination", "mode"); + private static final List INFO_LABELS_LIST = Arrays.asList("destination", "mode"); private static final Logger logger = LoggerFactory.getLogger(MetaCollector.class); - private static final String SUBSCRIPTION = "canal_instance"; - private static final String SUBSCRIPTION_HELP = "Canal instance"; + private static final String INSTANCE = "canal_instance"; + private static final String INSTANCE_HELP = "Canal instance"; + private static final String SUBSCRIPTION = "canal_instance_subscriptions"; + private static final String SUBSCRIPTION_HELP = "Canal instance subscriptions"; private final ConcurrentMap instances = new ConcurrentHashMap(); private MetaCollector() {} @@ -42,18 +43,20 @@ public class MetaCollector extends Collector implements InstanceRegistry { @Override public List collect() { List mfs = new ArrayList(); - GaugeMetricFamily instanceInfo = new GaugeMetricFamily( - SUBSCRIPTION, - SUBSCRIPTION_HELP, - InfoLabel); + GaugeMetricFamily instanceInfo = new GaugeMetricFamily(INSTANCE, + INSTANCE_HELP, INFO_LABELS_LIST); + GaugeMetricFamily subsInfo = new GaugeMetricFamily(SUBSCRIPTION, + SUBSCRIPTION_HELP, DEST_LABELS_LIST); for (Map.Entry nme : instances.entrySet()) { final String destination = nme.getKey(); final MetaMetricsHolder nmh = nme.getValue(); + instanceInfo.addMetric(nmh.infoLabelValues, 1); List subs = nmh.metaManager.listAllSubscribeInfo(destination); int count = subs == null ? 0 : subs.size(); - instanceInfo.addMetric(nmh.infoLabelValues, count); + subsInfo.addMetric(nmh.destLabelValues, count); } mfs.add(instanceInfo); + mfs.add(subsInfo); return mfs; } @@ -63,6 +66,7 @@ public class MetaCollector extends Collector implements InstanceRegistry { MetaMetricsHolder holder = new MetaMetricsHolder(); String mode = (instance instanceof CanalInstanceWithSpring) ? "spring" : "manager"; holder.infoLabelValues = Arrays.asList(destination, mode); + holder.destLabelValues = Collections.singletonList(destination); holder.metaManager = instance.getMetaManager(); Preconditions.checkNotNull(holder.metaManager); MetaMetricsHolder old = instances.put(destination, holder); @@ -79,6 +83,7 @@ public class MetaCollector extends Collector implements InstanceRegistry { private class MetaMetricsHolder { private List infoLabelValues; + private List destLabelValues; private CanalMetaManager metaManager; } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java index 60906989..73102795 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicLong; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; +import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * @author Chuanyi Li @@ -28,11 +29,12 @@ import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL public class ParserCollector extends Collector implements InstanceRegistry { private static final Logger logger = LoggerFactory.getLogger(ParserCollector.class); + private static final long NANO_PER_MILLI = 1000 * 1000L; private static final String PUBLISH_BLOCKING = "canal_instance_publish_blocking_time"; private static final String RECEIVED_BINLOG = "canal_instance_received_binlog_bytes"; private static final String PARSER_MODE = "canal_instance_parser_mode"; private static final String MODE_LABEL = "parallel"; - private static final String PUBLISH_BLOCKING_HELP = "Publish blocking time of dump thread"; + private static final String PUBLISH_BLOCKING_HELP = "Publish blocking time of dump thread in milliseconds"; private static final String RECEIVED_BINLOG_HELP = "Received binlog bytes"; private static final String MODE_HELP = "Parser mode(parallel/serial) of instance"; private final List modeLabels = Arrays.asList(DEST, MODE_LABEL); @@ -60,7 +62,7 @@ public class ParserCollector extends Collector implements InstanceRegistry { PUBLISH_BLOCKING_HELP, DEST_LABELS_LIST); for (ParserMetricsHolder emh : instances.values()) { if (emh.isParallel) { - blockingCounter.addMetric(emh.destLabelValues, emh.eventsPublishBlockingTime.doubleValue()); + blockingCounter.addMetric(emh.destLabelValues, (emh.eventsPublishBlockingTime.doubleValue() / NANO_PER_MILLI)); hasParallel = true; } modeGauge.addMetric(emh.modeLabelValues, 1); diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java index 6fc2f3d7..ebbbfa00 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.prometheus.impl; +import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.CanalEntry.EntryType; import com.alibaba.otter.canal.sink.AbstractCanalEventDownStreamHandler; import com.alibaba.otter.canal.store.model.Event; @@ -24,18 +25,28 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow for (Event e : events) { EntryType type = e.getEntryType(); if (type == null) continue; - long exec = e.getExecuteTime(); - if (exec > 0) localExecTime = exec; switch (type) { - case ROWDATA: + case TRANSACTIONBEGIN: { + long exec = e.getExecuteTime(); + if (exec > 0) localExecTime = exec; + break; + } + case ROWDATA: { + long exec = e.getExecuteTime(); + if (exec > 0) localExecTime = exec; // TODO 当前proto无法直接获得荣威change的变更行数(需要parse),可考虑放到header里面 break; - case TRANSACTIONEND: + } + case TRANSACTIONEND: { + long exec = e.getExecuteTime(); + if (exec > 0) localExecTime = exec; transactionCounter.incrementAndGet(); break; + } case HEARTBEAT: + // 发现canal自己的heartbeat是带有execTime的 // TODO 确认一下不是canal自己产生的 - // EventType eventType = e.getEventType(); + CanalEntry.EventType eventType = e.getEventType(); // TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming // see: https://dev.mysql.com/worklog/task/?id=342 // heartbeats are sent by the master only if there is no diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java index 331897ee..da16f2db 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java @@ -15,6 +15,7 @@ import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL */ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler { + private static final long NANO_PER_MILLI = 1000 * 1000L; private static final String PACKET_TYPE = "canal_instance_client_packets"; private static final String OUTBOUND_BYTES = "canal_instance_client_bytes"; private static final String EMPTY_BATCHES = "canal_instance_client_empty_batches"; @@ -27,7 +28,15 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler private final Histogram responseLatency; private volatile boolean running = false; - public PrometheusClientInstanceProfiler() { + private static class SingletonHolder { + private static final PrometheusClientInstanceProfiler SINGLETON = new PrometheusClientInstanceProfiler(); + } + + public static PrometheusClientInstanceProfiler instance() { + return SingletonHolder.SINGLETON; + } + + private PrometheusClientInstanceProfiler() { this.outboundCounter = Counter.build() .labelNames(DEST_LABELS) .name(OUTBOUND_BYTES) @@ -53,7 +62,7 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler .name(LATENCY) .help("Client request latency.") // buckets in milliseconds - .buckets(1.0, 2.5, 5.0, 10.0, 25.0, 50.0, 100.0) + .buckets(2.5, 10.0, 25.0, 100.0) .create(); } @@ -68,7 +77,7 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler errorsCounter.labels(destination, Short.toString(errorCode)).inc(); } long latency = result.getLatency(); - responseLatency.labels(destination).observe(latency / 1000000); + responseLatency.labels(destination).observe(((double) latency) / NANO_PER_MILLI); switch (type) { case GET: boolean empty = result.getEmpty(); diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java index fc5ed3b2..61dc4d64 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/SinkCollector.java @@ -18,6 +18,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; +import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * @author Chuanyi Li @@ -25,8 +26,9 @@ import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL public class SinkCollector extends Collector implements InstanceRegistry { private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); + private static final long NANO_PER_MILLI = 1000 * 1000L; private static final String SINK_BLOCKING_TIME = "canal_instance_sink_blocking_time"; - private static final String SINK_BLOCK_TIME_HELP = "Total sink blocking time"; + private static final String SINK_BLOCK_TIME_HELP = "Total sink blocking time in milliseconds"; private final ConcurrentMap instances = new ConcurrentHashMap(); private SinkCollector() {} @@ -45,7 +47,7 @@ public class SinkCollector extends Collector implements InstanceRegistry { CounterMetricFamily blockingCounter = new CounterMetricFamily(SINK_BLOCKING_TIME, SINK_BLOCK_TIME_HELP, DEST_LABELS_LIST); for (SinkMetricsHolder smh : instances.values()) { - blockingCounter.addMetric(smh.destLabelValues, smh.eventsSinkBlockingTime.doubleValue()); + blockingCounter.addMetric(smh.destLabelValues, (smh.eventsSinkBlockingTime.doubleValue() / NANO_PER_MILLI)); } mfs.add(blockingCounter); return mfs; diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java index 23c5071b..699b06a5 100644 --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java @@ -164,6 +164,11 @@ public class EntryEventSink extends AbstractCanalEventSink> handler : getHandlers()) { From dfd58f4aa1a5d5e590ab76d4c7c40e05befa583c Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Wed, 8 Aug 2018 07:53:44 +0800 Subject: [PATCH 064/360] Improve sink blocking time resolution. --- .../java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java index 699b06a5..7afde6b6 100644 --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java @@ -164,7 +164,7 @@ public class EntryEventSink extends AbstractCanalEventSink Date: Wed, 8 Aug 2018 20:10:34 +0800 Subject: [PATCH 065/360] Reformat code. --- .../alibaba/otter/canal/prometheus/impl/StoreCollector.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index 8bd39574..3be87c3e 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -87,7 +87,8 @@ public class StoreCollector extends Collector implements InstanceRegistry { return mfs; } - @Override public void register(CanalInstance instance) { + @Override + public void register(CanalInstance instance) { final String destination = instance.getDestination(); StoreMetricsHolder holder = new StoreMetricsHolder(); CanalEventStore store = instance.getEventStore(); @@ -115,7 +116,8 @@ public class StoreCollector extends Collector implements InstanceRegistry { } } - @Override public void unregister(CanalInstance instance) { + @Override + public void unregister(CanalInstance instance) { final String destination = instance.getDestination(); instances.remove(destination); } From ec007924755a0dc51c0037c13b03ab23a324715a Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Wed, 8 Aug 2018 22:53:08 +0800 Subject: [PATCH 066/360] Enhance heartbeat, rowscount. --- .../parse/inbound/EventTransactionBuffer.java | 5 + .../inbound/mysql/dbsync/LogEventConvert.java | 27 +- prometheus/pom.xml | 11 - .../canal/prometheus/impl/EntryCollector.java | 31 +- ...PrometheusCanalEventDownStreamHandler.java | 13 +- .../otter/canal/protocol/CanalEntry.java | 25557 ++++++++-------- .../otter/canal/protocol/EntryProtocol.proto | 4 +- .../otter/canal/store/model/Event.java | 22 + 8 files changed, 12743 insertions(+), 12927 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/EventTransactionBuffer.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/EventTransactionBuffer.java index c3c7150f..8034d078 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/EventTransactionBuffer.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/EventTransactionBuffer.java @@ -80,6 +80,11 @@ public class EventTransactionBuffer extends AbstractCanalLifeCycle { flush(); } break; + case HEARTBEAT: + // master过来的heartbeat,说明binlog已经读完了,是idle状态 + put(entry); + flush(); + break; default: break; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 2f9b5304..f5ff93ab 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -10,6 +10,7 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; +import com.taobao.tddl.dbsync.binlog.event.*; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; @@ -40,22 +41,7 @@ import com.alibaba.otter.canal.protocol.CanalEntry.Type; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.google.protobuf.ByteString; import com.taobao.tddl.dbsync.binlog.LogEvent; -import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent; -import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent; -import com.taobao.tddl.dbsync.binlog.event.LogHeader; -import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RandLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer; -import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent.ColumnInfo; -import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent; -import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent; -import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; -import com.taobao.tddl.dbsync.binlog.event.XidLogEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; /** @@ -144,6 +130,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar return parseRandLogEvent((RandLogEvent) logEvent); case LogEvent.GTID_LOG_EVENT: return parseGTIDLogEvent((GtidLogEvent) logEvent); + case LogEvent.HEARTBEAT_LOG_EVENT: + return parseHeartbeatLogEvent((HeartbeatLogEvent) logEvent); default: break; } @@ -158,6 +146,15 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar } } + private Entry parseHeartbeatLogEvent(HeartbeatLogEvent logEvent) { + Header.Builder headerBuilder = Header.newBuilder(); + headerBuilder.setEventType(EventType.MHEARTBEAT); + Entry.Builder entryBuilder = Entry.newBuilder(); + entryBuilder.setHeader(headerBuilder.build()); + entryBuilder.setEntryType(EntryType.HEARTBEAT); + return entryBuilder.build(); + } + private Entry parseGTIDLogEvent(GtidLogEvent logEvent) { LogHeader logHeader = logEvent.getHeader(); String value = logEvent.getSid().toString() + ":" + logEvent.getGno(); diff --git a/prometheus/pom.xml b/prometheus/pom.xml index 16cf8acf..b814c7e6 100644 --- a/prometheus/pom.xml +++ b/prometheus/pom.xml @@ -14,17 +14,6 @@ 1.0.26-SNAPSHOT canal prometheus module for otter ${project.version} - - - org.aspectj - aspectjrt - 1.8.9 - - - org.aspectj - aspectjweaver - 1.8.9 - org.jctools jctools-core diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java index 4aa6ff42..e434bd58 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java @@ -7,6 +7,7 @@ import com.alibaba.otter.canal.sink.CanalEventSink; import com.alibaba.otter.canal.sink.entry.EntryEventSink; import com.google.common.base.Preconditions; import io.prometheus.client.Collector; +import io.prometheus.client.Counter; import io.prometheus.client.CounterMetricFamily; import io.prometheus.client.GaugeMetricFamily; import org.slf4j.Logger; @@ -26,11 +27,15 @@ import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABEL */ public class EntryCollector extends Collector implements InstanceRegistry { - private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); - private static final String DELAY = "canal_instance_traffic_delay"; - private static final String TRANSACTION = "canal_instance_transactions"; - private static final String DELAY_HELP = "Traffic delay of canal instance in milliseconds"; - private static final String TRANSACTION_HELP = "Transactions counter of canal instance"; + private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); + private static final String DELAY = "canal_instance_traffic_delay"; + private static final String TRANSACTION = "canal_instance_transactions"; + private static final String ROW_EVENTS = "canal_instance_row_events"; + private static final String ROWS_COUNTER = "canal_instance_rows_counter"; + private static final String DELAY_HELP = "Traffic delay of canal instance in milliseconds"; + private static final String TRANSACTION_HELP = "Transactions counter of canal instance"; + private static final String ROW_EVENTS_HELP = "Rowdata events counter of canal instance"; + private static final String ROWS_COUNTER_HELP = "Rows counter of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); private EntryCollector() {} @@ -50,16 +55,24 @@ public class EntryCollector extends Collector implements InstanceRegistry { DELAY_HELP, DEST_LABELS_LIST); CounterMetricFamily transactions = new CounterMetricFamily(TRANSACTION, TRANSACTION_HELP, DEST_LABELS_LIST); + CounterMetricFamily rowEvents = new CounterMetricFamily(ROW_EVENTS, + ROW_EVENTS_HELP, DEST_LABELS_LIST); + CounterMetricFamily rowsCounter = new CounterMetricFamily(ROWS_COUNTER, + ROWS_COUNTER_HELP, DEST_LABELS_LIST); for (EntryMetricsHolder emh : instances.values()) { long now = System.currentTimeMillis(); long latest = emh.latestExecTime.get(); - if (now > latest) { + if (now >= latest) { delay.addMetric(emh.destLabelValues, (now - latest)); } transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue()); + rowEvents.addMetric(emh.destLabelValues, emh.rowEventCounter.doubleValue()); + rowsCounter.addMetric(emh.destLabelValues, emh.rowsCounter.doubleValue()); } mfs.add(delay); mfs.add(transactions); + mfs.add(rowEvents); + mfs.add(rowsCounter); return mfs; } @@ -76,8 +89,12 @@ public class EntryCollector extends Collector implements InstanceRegistry { PrometheusCanalEventDownStreamHandler handler = assembleHandler(entrySink); holder.latestExecTime = handler.getLatestExecuteTime(); holder.transactionCounter = handler.getTransactionCounter(); + holder.rowEventCounter = handler.getRowEventCounter(); + holder.rowsCounter = handler.getRowsCounter(); Preconditions.checkNotNull(holder.latestExecTime); Preconditions.checkNotNull(holder.transactionCounter); + Preconditions.checkNotNull(holder.rowEventCounter); + Preconditions.checkNotNull(holder.rowsCounter); EntryMetricsHolder old = instances.put(destination, holder); if (old != null) { logger.warn("Remove stale EntryCollector for instance {}.", destination); @@ -128,6 +145,8 @@ public class EntryCollector extends Collector implements InstanceRegistry { private class EntryMetricsHolder { private AtomicLong latestExecTime; private AtomicLong transactionCounter; + private AtomicLong rowEventCounter; + private AtomicLong rowsCounter; private List destLabelValues; } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java index ebbbfa00..f81033a4 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -34,7 +34,8 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow case ROWDATA: { long exec = e.getExecuteTime(); if (exec > 0) localExecTime = exec; - // TODO 当前proto无法直接获得荣威change的变更行数(需要parse),可考虑放到header里面 + rowEventCounter.incrementAndGet(); + rowsCounter.addAndGet(e.getRowsCount()); break; } case TRANSACTIONEND: { @@ -44,14 +45,10 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow break; } case HEARTBEAT: - // 发现canal自己的heartbeat是带有execTime的 - // TODO 确认一下不是canal自己产生的 CanalEntry.EventType eventType = e.getEventType(); - // TODO utilize MySQL master heartbeat packet to refresh delay if always no more events coming - // see: https://dev.mysql.com/worklog/task/?id=342 - // heartbeats are sent by the master only if there is no - // more unsent events in the actual binlog file for a period longer that - // master_heartbeat_period. + if (eventType == CanalEntry.EventType.MHEARTBEAT) { + localExecTime = System.currentTimeMillis(); + } break; default: break; diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java index 1a0979db..051d8bf6 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java @@ -4,13051 +4,12836 @@ package com.alibaba.otter.canal.protocol; public final class CanalEntry { - - private CanalEntry(){ - } - - public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { - } - + private CanalEntry() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + } + /** + * Protobuf enum {@code com.alibaba.otter.canal.protocol.EntryType} + * + *
      +   **打散后的事件类型,主要用于标识事务的开始,变更数据,结束*
      +   * 
      + */ + public enum EntryType + implements com.google.protobuf.ProtocolMessageEnum { /** - * Protobuf enum {@code com.alibaba.otter.canal.protocol.EntryType} + * TRANSACTIONBEGIN = 1; + */ + TRANSACTIONBEGIN(0, 1), + /** + * ROWDATA = 2; + */ + ROWDATA(1, 2), + /** + * TRANSACTIONEND = 3; + */ + TRANSACTIONEND(2, 3), + /** + * HEARTBEAT = 4; * *
      -     * *打散后的事件类型,主要用于标识事务的开始,变更数据,结束*
      +     ** 心跳类型,内部使用,外部暂不可见,可忽略 *
            * 
      */ - public enum EntryType implements com.google.protobuf.ProtocolMessageEnum { - /** - * TRANSACTIONBEGIN = 1; - */ - TRANSACTIONBEGIN(0, 1), - /** - * ROWDATA = 2; - */ - ROWDATA(1, 2), - /** - * TRANSACTIONEND = 3; - */ - TRANSACTIONEND(2, 3), - /** - * HEARTBEAT = 4; - * - *
      -         * * 心跳类型,内部使用,外部暂不可见,可忽略 *
      -         * 
      - */ - HEARTBEAT(3, 4), - /** - * GTIDLOG = 5; - */ - GTIDLOG(4, 5), ; - - /** - * TRANSACTIONBEGIN = 1; - */ - public static final int TRANSACTIONBEGIN_VALUE = 1; - /** - * ROWDATA = 2; - */ - public static final int ROWDATA_VALUE = 2; - /** - * TRANSACTIONEND = 3; - */ - public static final int TRANSACTIONEND_VALUE = 3; - /** - * HEARTBEAT = 4; - * - *
      -         * * 心跳类型,内部使用,外部暂不可见,可忽略 *
      -         * 
      - */ - public static final int HEARTBEAT_VALUE = 4; - /** - * GTIDLOG = 5; - */ - public static final int GTIDLOG_VALUE = 5; - - public final int getNumber() { - return value; - } - - public static EntryType valueOf(int value) { - switch (value) { - case 1: - return TRANSACTIONBEGIN; - case 2: - return ROWDATA; - case 3: - return TRANSACTIONEND; - case 4: - return HEARTBEAT; - case 5: - return GTIDLOG; - default: - return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { - return internalValueMap; - } - - private static com.google.protobuf.Internal.EnumLiteMap internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { - - public EntryType findValueByNumber(int number) { - return EntryType.valueOf(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); - } - - public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { - return getDescriptor(); - } - - public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.getDescriptor().getEnumTypes().get(0); - } - - private static final EntryType[] VALUES = values(); - - public static EntryType valueOf(com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException("EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int index; - private final int value; - - private EntryType(int index, int value){ - this.index = index; - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.EntryType) - } + HEARTBEAT(3, 4), + /** + * GTIDLOG = 5; + */ + GTIDLOG(4, 5), + ; /** - * Protobuf enum {@code com.alibaba.otter.canal.protocol.EventType} + * TRANSACTIONBEGIN = 1; + */ + public static final int TRANSACTIONBEGIN_VALUE = 1; + /** + * ROWDATA = 2; + */ + public static final int ROWDATA_VALUE = 2; + /** + * TRANSACTIONEND = 3; + */ + public static final int TRANSACTIONEND_VALUE = 3; + /** + * HEARTBEAT = 4; * *
      -     * * 事件类型 *
      +     ** 心跳类型,内部使用,外部暂不可见,可忽略 *
            * 
      */ - public enum EventType implements com.google.protobuf.ProtocolMessageEnum { - /** - * INSERT = 1; - */ - INSERT(0, 1), - /** - * UPDATE = 2; - */ - UPDATE(1, 2), - /** - * DELETE = 3; - */ - DELETE(2, 3), - /** - * CREATE = 4; - */ - CREATE(3, 4), - /** - * ALTER = 5; - */ - ALTER(4, 5), - /** - * ERASE = 6; - */ - ERASE(5, 6), - /** - * QUERY = 7; - */ - QUERY(6, 7), - /** - * TRUNCATE = 8; - */ - TRUNCATE(7, 8), - /** - * RENAME = 9; - */ - RENAME(8, 9), - /** - * CINDEX = 10; - * - *
      -         * *CREATE INDEX*
      -         * 
      - */ - CINDEX(9, 10), - /** - * DINDEX = 11; - */ - DINDEX(10, 11), - /** - * GTID = 12; - */ - GTID(11, 12), - /** - * XASTART = 13; - * - *
      -         * * XA *
      -         * 
      - */ - XASTART(12, 13), - /** - * XAEND = 14; - */ - XAEND(13, 14), - /** - * XACOMMIT = 15; - */ - XACOMMIT(14, 15), - /** - * XAROLLBACK = 16; - */ - XAROLLBACK(15, 16), ; + public static final int HEARTBEAT_VALUE = 4; + /** + * GTIDLOG = 5; + */ + public static final int GTIDLOG_VALUE = 5; - /** - * INSERT = 1; - */ - public static final int INSERT_VALUE = 1; - /** - * UPDATE = 2; - */ - public static final int UPDATE_VALUE = 2; - /** - * DELETE = 3; - */ - public static final int DELETE_VALUE = 3; - /** - * CREATE = 4; - */ - public static final int CREATE_VALUE = 4; - /** - * ALTER = 5; - */ - public static final int ALTER_VALUE = 5; - /** - * ERASE = 6; - */ - public static final int ERASE_VALUE = 6; - /** - * QUERY = 7; - */ - public static final int QUERY_VALUE = 7; - /** - * TRUNCATE = 8; - */ - public static final int TRUNCATE_VALUE = 8; - /** - * RENAME = 9; - */ - public static final int RENAME_VALUE = 9; - /** - * CINDEX = 10; - * - *
      -         * *CREATE INDEX*
      -         * 
      - */ - public static final int CINDEX_VALUE = 10; - /** - * DINDEX = 11; - */ - public static final int DINDEX_VALUE = 11; - /** - * GTID = 12; - */ - public static final int GTID_VALUE = 12; - /** - * XASTART = 13; - * - *
      -         * * XA *
      -         * 
      - */ - public static final int XASTART_VALUE = 13; - /** - * XAEND = 14; - */ - public static final int XAEND_VALUE = 14; - /** - * XACOMMIT = 15; - */ - public static final int XACOMMIT_VALUE = 15; - /** - * XAROLLBACK = 16; - */ - public static final int XAROLLBACK_VALUE = 16; - public final int getNumber() { - return value; - } + public final int getNumber() { return value; } - public static EventType valueOf(int value) { - switch (value) { - case 1: - return INSERT; - case 2: - return UPDATE; - case 3: - return DELETE; - case 4: - return CREATE; - case 5: - return ALTER; - case 6: - return ERASE; - case 7: - return QUERY; - case 8: - return TRUNCATE; - case 9: - return RENAME; - case 10: - return CINDEX; - case 11: - return DINDEX; - case 12: - return GTID; - case 13: - return XASTART; - case 14: - return XAEND; - case 15: - return XACOMMIT; - case 16: - return XAROLLBACK; - default: - return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { - return internalValueMap; - } - - private static com.google.protobuf.Internal.EnumLiteMap internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { - - public EventType findValueByNumber(int number) { - return EventType.valueOf(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); - } - - public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { - return getDescriptor(); - } - - public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.getDescriptor().getEnumTypes().get(1); - } - - private static final EventType[] VALUES = values(); - - public static EventType valueOf(com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException("EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int index; - private final int value; - - private EventType(int index, int value){ - this.index = index; - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.EventType) + public static EntryType valueOf(int value) { + switch (value) { + case 1: return TRANSACTIONBEGIN; + case 2: return ROWDATA; + case 3: return TRANSACTIONEND; + case 4: return HEARTBEAT; + case 5: return GTIDLOG; + default: return null; + } } + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public EntryType findValueByNumber(int number) { + return EntryType.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return CanalEntry.getDescriptor().getEnumTypes().get(0); + } + + private static final EntryType[] VALUES = values(); + + public static EntryType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private EntryType(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.EntryType) + } + + /** + * Protobuf enum {@code com.alibaba.otter.canal.protocol.EventType} + * + *
      +   ** 事件类型 *
      +   * 
      + */ + public enum EventType + implements com.google.protobuf.ProtocolMessageEnum { + /** + * INSERT = 1; + */ + INSERT(0, 1), + /** + * UPDATE = 2; + */ + UPDATE(1, 2), + /** + * DELETE = 3; + */ + DELETE(2, 3), + /** + * CREATE = 4; + */ + CREATE(3, 4), + /** + * ALTER = 5; + */ + ALTER(4, 5), + /** + * ERASE = 6; + */ + ERASE(5, 6), + /** + * QUERY = 7; + */ + QUERY(6, 7), + /** + * TRUNCATE = 8; + */ + TRUNCATE(7, 8), + /** + * RENAME = 9; + */ + RENAME(8, 9), + /** + * CINDEX = 10; + * + *
      +     **CREATE INDEX*
      +     * 
      + */ + CINDEX(9, 10), + /** + * DINDEX = 11; + */ + DINDEX(10, 11), + /** + * GTID = 12; + */ + GTID(11, 12), + /** + * XACOMMIT = 13; + * + *
      +     ** XA *
      +     * 
      + */ + XACOMMIT(12, 13), + /** + * XAROLLBACK = 14; + */ + XAROLLBACK(13, 14), + /** + * MHEARTBEAT = 15; + * + *
      +     ** MASTER HEARTBEAT *
      +     * 
      + */ + MHEARTBEAT(14, 15), + ; + /** - * Protobuf enum {@code com.alibaba.otter.canal.protocol.Type} + * INSERT = 1; + */ + public static final int INSERT_VALUE = 1; + /** + * UPDATE = 2; + */ + public static final int UPDATE_VALUE = 2; + /** + * DELETE = 3; + */ + public static final int DELETE_VALUE = 3; + /** + * CREATE = 4; + */ + public static final int CREATE_VALUE = 4; + /** + * ALTER = 5; + */ + public static final int ALTER_VALUE = 5; + /** + * ERASE = 6; + */ + public static final int ERASE_VALUE = 6; + /** + * QUERY = 7; + */ + public static final int QUERY_VALUE = 7; + /** + * TRUNCATE = 8; + */ + public static final int TRUNCATE_VALUE = 8; + /** + * RENAME = 9; + */ + public static final int RENAME_VALUE = 9; + /** + * CINDEX = 10; * *
      -     * *数据库类型*
      +     **CREATE INDEX*
            * 
      */ - public enum Type implements com.google.protobuf.ProtocolMessageEnum { - /** - * ORACLE = 1; - */ - ORACLE(0, 1), - /** - * MYSQL = 2; - */ - MYSQL(1, 2), - /** - * PGSQL = 3; - */ - PGSQL(2, 3), ; + public static final int CINDEX_VALUE = 10; + /** + * DINDEX = 11; + */ + public static final int DINDEX_VALUE = 11; + /** + * GTID = 12; + */ + public static final int GTID_VALUE = 12; + /** + * XACOMMIT = 13; + * + *
      +     ** XA *
      +     * 
      + */ + public static final int XACOMMIT_VALUE = 13; + /** + * XAROLLBACK = 14; + */ + public static final int XAROLLBACK_VALUE = 14; + /** + * MHEARTBEAT = 15; + * + *
      +     ** MASTER HEARTBEAT *
      +     * 
      + */ + public static final int MHEARTBEAT_VALUE = 15; - /** - * ORACLE = 1; - */ - public static final int ORACLE_VALUE = 1; - /** - * MYSQL = 2; - */ - public static final int MYSQL_VALUE = 2; - /** - * PGSQL = 3; - */ - public static final int PGSQL_VALUE = 3; - public final int getNumber() { - return value; - } + public final int getNumber() { return value; } - public static Type valueOf(int value) { - switch (value) { - case 1: - return ORACLE; - case 2: - return MYSQL; - case 3: - return PGSQL; - default: - return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { - return internalValueMap; - } - - private static com.google.protobuf.Internal.EnumLiteMap internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { - - public Type findValueByNumber(int number) { - return Type.valueOf(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); - } - - public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { - return getDescriptor(); - } - - public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.getDescriptor().getEnumTypes().get(2); - } - - private static final Type[] VALUES = values(); - - public static Type valueOf(com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException("EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int index; - private final int value; - - private Type(int index, int value){ - this.index = index; - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.Type) + public static EventType valueOf(int value) { + switch (value) { + case 1: return INSERT; + case 2: return UPDATE; + case 3: return DELETE; + case 4: return CREATE; + case 5: return ALTER; + case 6: return ERASE; + case 7: return QUERY; + case 8: return TRUNCATE; + case 9: return RENAME; + case 10: return CINDEX; + case 11: return DINDEX; + case 12: return GTID; + case 13: return XACOMMIT; + case 14: return XAROLLBACK; + case 15: return MHEARTBEAT; + default: return null; + } } - public interface EntryOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Entry) - com.google.protobuf.MessageOrBuilder { + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public EventType findValueByNumber(int number) { + return EventType.valueOf(number); + } + }; - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -         * *协议头部信息*
      -         * 
      - */ - boolean hasHeader(); - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -         * *协议头部信息*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader(); - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -         * *协议头部信息*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder(); - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -         * *打散后的事件类型*
      -         * 
      - */ - boolean hasEntryType(); - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -         * *打散后的事件类型*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType(); - - /** - * optional bytes storeValue = 3; - * - *
      -         * *传输的二进制数组*
      -         * 
      - */ - boolean hasStoreValue(); - - /** - * optional bytes storeValue = 3; - * - *
      -         * *传输的二进制数组*
      -         * 
      - */ - com.google.protobuf.ByteString getStoreValue(); + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return CanalEntry.getDescriptor().getEnumTypes().get(1); } + private static final EventType[] VALUES = values(); + + public static EventType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private EventType(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.EventType) + } + + /** + * Protobuf enum {@code com.alibaba.otter.canal.protocol.Type} + * + *
      +   **数据库类型*
      +   * 
      + */ + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { + /** + * ORACLE = 1; + */ + ORACLE(0, 1), + /** + * MYSQL = 2; + */ + MYSQL(1, 2), + /** + * PGSQL = 3; + */ + PGSQL(2, 3), + ; + + /** + * ORACLE = 1; + */ + public static final int ORACLE_VALUE = 1; + /** + * MYSQL = 2; + */ + public static final int MYSQL_VALUE = 2; + /** + * PGSQL = 3; + */ + public static final int PGSQL_VALUE = 3; + + + public final int getNumber() { return value; } + + public static Type valueOf(int value) { + switch (value) { + case 1: return ORACLE; + case 2: return MYSQL; + case 3: return PGSQL; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return CanalEntry.getDescriptor().getEnumTypes().get(2); + } + + private static final Type[] VALUES = values(); + + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Type(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.Type) + } + + public interface EntryOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Entry) + com.google.protobuf.MessageOrBuilder { + + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +     **协议头部信息*
      +     * 
      + */ + boolean hasHeader(); + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +     **协议头部信息*
      +     * 
      + */ + Header getHeader(); + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +     **协议头部信息*
      +     * 
      + */ + HeaderOrBuilder getHeaderOrBuilder(); + + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +     **打散后的事件类型*
      +     * 
      + */ + boolean hasEntryType(); + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +     **打散后的事件类型*
      +     * 
      + */ + EntryType getEntryType(); + + /** + * optional bytes storeValue = 3; + * + *
      +     **传输的二进制数组*
      +     * 
      + */ + boolean hasStoreValue(); + /** + * optional bytes storeValue = 3; + * + *
      +     **传输的二进制数组*
      +     * 
      + */ + com.google.protobuf.ByteString getStoreValue(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.Entry} + * + *
      +   ****************************************************************
      +   * message model
      +   *如果要在Enum中新增类型,确保以前的类型的下标值不变.
      +   ***************************************************************
      +   * 
      + */ + public static final class Entry extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Entry) + EntryOrBuilder { + // Use Entry.newBuilder() to construct. + private Entry(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private Entry(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final Entry defaultInstance; + public static Entry getDefaultInstance() { + return defaultInstance; + } + + public Entry getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Entry( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + Header.Builder subBuilder = null; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + subBuilder = header_.toBuilder(); + } + header_ = input.readMessage(Header.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(header_); + header_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000001; + break; + } + case 16: { + int rawValue = input.readEnum(); + EntryType value = EntryType.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(2, rawValue); + } else { + bitField0_ |= 0x00000002; + entryType_ = value; + } + break; + } + case 26: { + bitField0_ |= 0x00000004; + storeValue_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Entry.class, Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public Entry parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Entry(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + public static final int HEADER_FIELD_NUMBER = 1; + private Header header_; + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +     **协议头部信息*
      +     * 
      + */ + public boolean hasHeader() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +     **协议头部信息*
      +     * 
      + */ + public Header getHeader() { + return header_; + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +     **协议头部信息*
      +     * 
      + */ + public HeaderOrBuilder getHeaderOrBuilder() { + return header_; + } + + public static final int ENTRYTYPE_FIELD_NUMBER = 2; + private EntryType entryType_; + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +     **打散后的事件类型*
      +     * 
      + */ + public boolean hasEntryType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +     **打散后的事件类型*
      +     * 
      + */ + public EntryType getEntryType() { + return entryType_; + } + + public static final int STOREVALUE_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString storeValue_; + /** + * optional bytes storeValue = 3; + * + *
      +     **传输的二进制数组*
      +     * 
      + */ + public boolean hasStoreValue() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional bytes storeValue = 3; + * + *
      +     **传输的二进制数组*
      +     * 
      + */ + public com.google.protobuf.ByteString getStoreValue() { + return storeValue_; + } + + private void initFields() { + header_ = Header.getDefaultInstance(); + entryType_ = EntryType.ROWDATA; + storeValue_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeMessage(1, header_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeEnum(2, entryType_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, storeValue_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, header_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(2, entryType_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, storeValue_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static Entry parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Entry parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Entry parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Entry parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Entry parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Entry parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static Entry parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static Entry parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static Entry parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Entry parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(Entry prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Entry} * *
            ****************************************************************
      -     *  message model
      -     * 如果要在Enum中新增类型,确保以前的类型的下标值不变.
      +     * message model
      +     *如果要在Enum中新增类型,确保以前的类型的下标值不变.
            ***************************************************************
            * 
      */ - public static final class Entry extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Entry) - EntryOrBuilder { - - // Use Entry.newBuilder() to construct. - private Entry(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Entry(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Entry defaultInstance; - - public static Entry getDefaultInstance() { - return defaultInstance; - } - - public Entry getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Entry(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder subBuilder = null; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - subBuilder = header_.toBuilder(); - } - header_ = input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Header.PARSER, - extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(header_); - header_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000001; - break; - } - case 16: { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalEntry.EntryType value = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(2, rawValue); - } else { - bitField0_ |= 0x00000002; - entryType_ = value; - } - break; - } - case 26: { - bitField0_ |= 0x00000004; - storeValue_ = input.readBytes(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Entry.class, - com.alibaba.otter.canal.protocol.CanalEntry.Entry.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Entry parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Entry(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int HEADER_FIELD_NUMBER = 1; - private com.alibaba.otter.canal.protocol.CanalEntry.Header header_; - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -         * *协议头部信息*
      -         * 
      - */ - public boolean hasHeader() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -         * *协议头部信息*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader() { - return header_; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -         * *协议头部信息*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder() { - return header_; - } - - public static final int ENTRYTYPE_FIELD_NUMBER = 2; - private com.alibaba.otter.canal.protocol.CanalEntry.EntryType entryType_; - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -         * *打散后的事件类型*
      -         * 
      - */ - public boolean hasEntryType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -         * *打散后的事件类型*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType() { - return entryType_; - } - - public static final int STOREVALUE_FIELD_NUMBER = 3; - private com.google.protobuf.ByteString storeValue_; - - /** - * optional bytes storeValue = 3; - * - *
      -         * *传输的二进制数组*
      -         * 
      - */ - public boolean hasStoreValue() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional bytes storeValue = 3; - * - *
      -         * *传输的二进制数组*
      -         * 
      - */ - public com.google.protobuf.ByteString getStoreValue() { - return storeValue_; - } - - private void initFields() { - header_ = com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance(); - entryType_ = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA; - storeValue_ = com.google.protobuf.ByteString.EMPTY; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeMessage(1, header_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, entryType_.getNumber()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(3, storeValue_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(1, header_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeEnumSize(2, entryType_.getNumber()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(3, storeValue_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Entry prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Entry} - * - *
      -         ****************************************************************
      -         *  message model
      -         * 如果要在Enum中新增类型,确保以前的类型的下标值不变.
      -         ***************************************************************
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Entry) - com.alibaba.otter.canal.protocol.CanalEntry.EntryOrBuilder { + EntryOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; - } + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Entry.class, Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Entry.class, - com.alibaba.otter.canal.protocol.CanalEntry.Entry.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Entry.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.Entry.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getHeaderFieldBuilder(); - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - if (headerBuilder_ == null) { - header_ = com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance(); - } else { - headerBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000001); - entryType_ = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA; - bitField0_ = (bitField0_ & ~0x00000002); - storeValue_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Entry getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.Entry.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Entry build() { - com.alibaba.otter.canal.protocol.CanalEntry.Entry result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Entry buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.Entry result = new com.alibaba.otter.canal.protocol.CanalEntry.Entry(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - if (headerBuilder_ == null) { - result.header_ = header_; - } else { - result.header_ = headerBuilder_.build(); - } - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.entryType_ = entryType_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.storeValue_ = storeValue_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Entry) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Entry) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Entry other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.Entry.getDefaultInstance()) return this; - if (other.hasHeader()) { - mergeHeader(other.getHeader()); - } - if (other.hasEntryType()) { - setEntryType(other.getEntryType()); - } - if (other.hasStoreValue()) { - setStoreValue(other.getStoreValue()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.Entry parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Entry) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private com.alibaba.otter.canal.protocol.CanalEntry.Header header_ = com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder headerBuilder_; - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public boolean hasHeader() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader() { - if (headerBuilder_ == null) { - return header_; - } else { - return headerBuilder_.getMessage(); - } - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public Builder setHeader(com.alibaba.otter.canal.protocol.CanalEntry.Header value) { - if (headerBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - header_ = value; - onChanged(); - } else { - headerBuilder_.setMessage(value); - } - bitField0_ |= 0x00000001; - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public Builder setHeader(com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder builderForValue) { - if (headerBuilder_ == null) { - header_ = builderForValue.build(); - onChanged(); - } else { - headerBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000001; - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public Builder mergeHeader(com.alibaba.otter.canal.protocol.CanalEntry.Header value) { - if (headerBuilder_ == null) { - if (((bitField0_ & 0x00000001) == 0x00000001) - && header_ != com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance()) { - header_ = com.alibaba.otter.canal.protocol.CanalEntry.Header.newBuilder(header_) - .mergeFrom(value) - .buildPartial(); - } else { - header_ = value; - } - onChanged(); - } else { - headerBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000001; - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public Builder clearHeader() { - if (headerBuilder_ == null) { - header_ = com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance(); - onChanged(); - } else { - headerBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000001); - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder getHeaderBuilder() { - bitField0_ |= 0x00000001; - onChanged(); - return getHeaderFieldBuilder().getBuilder(); - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder() { - if (headerBuilder_ != null) { - return headerBuilder_.getMessageOrBuilder(); - } else { - return header_; - } - } - - /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * - *
      -             * *协议头部信息*
      -             * 
      - */ - private com.google.protobuf.SingleFieldBuilder getHeaderFieldBuilder() { - if (headerBuilder_ == null) { - headerBuilder_ = new com.google.protobuf.SingleFieldBuilder(getHeader(), - getParentForChildren(), - isClean()); - header_ = null; - } - return headerBuilder_; - } - - private com.alibaba.otter.canal.protocol.CanalEntry.EntryType entryType_ = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA; - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -             * *打散后的事件类型*
      -             * 
      - */ - public boolean hasEntryType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -             * *打散后的事件类型*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType() { - return entryType_; - } - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -             * *打散后的事件类型*
      -             * 
      - */ - public Builder setEntryType(com.alibaba.otter.canal.protocol.CanalEntry.EntryType value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - entryType_ = value; - onChanged(); - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * - *
      -             * *打散后的事件类型*
      -             * 
      - */ - public Builder clearEntryType() { - bitField0_ = (bitField0_ & ~0x00000002); - entryType_ = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString storeValue_ = com.google.protobuf.ByteString.EMPTY; - - /** - * optional bytes storeValue = 3; - * - *
      -             * *传输的二进制数组*
      -             * 
      - */ - public boolean hasStoreValue() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional bytes storeValue = 3; - * - *
      -             * *传输的二进制数组*
      -             * 
      - */ - public com.google.protobuf.ByteString getStoreValue() { - return storeValue_; - } - - /** - * optional bytes storeValue = 3; - * - *
      -             * *传输的二进制数组*
      -             * 
      - */ - public Builder setStoreValue(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - storeValue_ = value; - onChanged(); - return this; - } - - /** - * optional bytes storeValue = 3; - * - *
      -             * *传输的二进制数组*
      -             * 
      - */ - public Builder clearStoreValue() { - bitField0_ = (bitField0_ & ~0x00000004); - storeValue_ = getDefaultInstance().getStoreValue(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Entry) + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getHeaderFieldBuilder(); } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new Entry(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + if (headerBuilder_ == null) { + header_ = Header.getDefaultInstance(); + } else { + headerBuilder_.clear(); } + bitField0_ = (bitField0_ & ~0x00000001); + entryType_ = EntryType.ROWDATA; + bitField0_ = (bitField0_ & ~0x00000002); + storeValue_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Entry) + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; + } + + public Entry getDefaultInstanceForType() { + return Entry.getDefaultInstance(); + } + + public Entry build() { + Entry result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public Entry buildPartial() { + Entry result = new Entry(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + if (headerBuilder_ == null) { + result.header_ = header_; + } else { + result.header_ = headerBuilder_.build(); + } + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.entryType_ = entryType_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.storeValue_ = storeValue_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Entry) { + return mergeFrom((Entry)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Entry other) { + if (other == Entry.getDefaultInstance()) return this; + if (other.hasHeader()) { + mergeHeader(other.getHeader()); + } + if (other.hasEntryType()) { + setEntryType(other.getEntryType()); + } + if (other.hasStoreValue()) { + setStoreValue(other.getStoreValue()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Entry parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (Entry) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private Header header_ = Header.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + Header, Header.Builder, HeaderOrBuilder> headerBuilder_; + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public boolean hasHeader() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public Header getHeader() { + if (headerBuilder_ == null) { + return header_; + } else { + return headerBuilder_.getMessage(); + } + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public Builder setHeader(Header value) { + if (headerBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + header_ = value; + onChanged(); + } else { + headerBuilder_.setMessage(value); + } + bitField0_ |= 0x00000001; + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public Builder setHeader( + Header.Builder builderForValue) { + if (headerBuilder_ == null) { + header_ = builderForValue.build(); + onChanged(); + } else { + headerBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000001; + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public Builder mergeHeader(Header value) { + if (headerBuilder_ == null) { + if (((bitField0_ & 0x00000001) == 0x00000001) && + header_ != Header.getDefaultInstance()) { + header_ = + Header.newBuilder(header_).mergeFrom(value).buildPartial(); + } else { + header_ = value; + } + onChanged(); + } else { + headerBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000001; + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public Builder clearHeader() { + if (headerBuilder_ == null) { + header_ = Header.getDefaultInstance(); + onChanged(); + } else { + headerBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public Header.Builder getHeaderBuilder() { + bitField0_ |= 0x00000001; + onChanged(); + return getHeaderFieldBuilder().getBuilder(); + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + public HeaderOrBuilder getHeaderOrBuilder() { + if (headerBuilder_ != null) { + return headerBuilder_.getMessageOrBuilder(); + } else { + return header_; + } + } + /** + * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * + *
      +       **协议头部信息*
      +       * 
      + */ + private com.google.protobuf.SingleFieldBuilder< + Header, Header.Builder, HeaderOrBuilder> + getHeaderFieldBuilder() { + if (headerBuilder_ == null) { + headerBuilder_ = new com.google.protobuf.SingleFieldBuilder< + Header, Header.Builder, HeaderOrBuilder>( + getHeader(), + getParentForChildren(), + isClean()); + header_ = null; + } + return headerBuilder_; + } + + private EntryType entryType_ = EntryType.ROWDATA; + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +       **打散后的事件类型*
      +       * 
      + */ + public boolean hasEntryType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +       **打散后的事件类型*
      +       * 
      + */ + public EntryType getEntryType() { + return entryType_; + } + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +       **打散后的事件类型*
      +       * 
      + */ + public Builder setEntryType(EntryType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + entryType_ = value; + onChanged(); + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * + *
      +       **打散后的事件类型*
      +       * 
      + */ + public Builder clearEntryType() { + bitField0_ = (bitField0_ & ~0x00000002); + entryType_ = EntryType.ROWDATA; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString storeValue_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes storeValue = 3; + * + *
      +       **传输的二进制数组*
      +       * 
      + */ + public boolean hasStoreValue() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional bytes storeValue = 3; + * + *
      +       **传输的二进制数组*
      +       * 
      + */ + public com.google.protobuf.ByteString getStoreValue() { + return storeValue_; + } + /** + * optional bytes storeValue = 3; + * + *
      +       **传输的二进制数组*
      +       * 
      + */ + public Builder setStoreValue(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + storeValue_ = value; + onChanged(); + return this; + } + /** + * optional bytes storeValue = 3; + * + *
      +       **传输的二进制数组*
      +       * 
      + */ + public Builder clearStoreValue() { + bitField0_ = (bitField0_ & ~0x00000004); + storeValue_ = getDefaultInstance().getStoreValue(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Entry) } - public interface HeaderOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Header) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -         * *协议的版本号*
      -         * 
      - */ - boolean hasVersion(); - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -         * *协议的版本号*
      -         * 
      - */ - int getVersion(); - - /** - * optional string logfileName = 2; - * - *
      -         * *binlog/redolog 文件名*
      -         * 
      - */ - boolean hasLogfileName(); - - /** - * optional string logfileName = 2; - * - *
      -         * *binlog/redolog 文件名*
      -         * 
      - */ - java.lang.String getLogfileName(); - - /** - * optional string logfileName = 2; - * - *
      -         * *binlog/redolog 文件名*
      -         * 
      - */ - com.google.protobuf.ByteString getLogfileNameBytes(); - - /** - * optional int64 logfileOffset = 3; - * - *
      -         * *binlog/redolog 文件的偏移位置*
      -         * 
      - */ - boolean hasLogfileOffset(); - - /** - * optional int64 logfileOffset = 3; - * - *
      -         * *binlog/redolog 文件的偏移位置*
      -         * 
      - */ - long getLogfileOffset(); - - /** - * optional int64 serverId = 4; - * - *
      -         * *服务端serverId*
      -         * 
      - */ - boolean hasServerId(); - - /** - * optional int64 serverId = 4; - * - *
      -         * *服务端serverId*
      -         * 
      - */ - long getServerId(); - - /** - * optional string serverenCode = 5; - * - *
      -         * * 变更数据的编码 *
      -         * 
      - */ - boolean hasServerenCode(); - - /** - * optional string serverenCode = 5; - * - *
      -         * * 变更数据的编码 *
      -         * 
      - */ - java.lang.String getServerenCode(); - - /** - * optional string serverenCode = 5; - * - *
      -         * * 变更数据的编码 *
      -         * 
      - */ - com.google.protobuf.ByteString getServerenCodeBytes(); - - /** - * optional int64 executeTime = 6; - * - *
      -         * *变更数据的执行时间 *
      -         * 
      - */ - boolean hasExecuteTime(); - - /** - * optional int64 executeTime = 6; - * - *
      -         * *变更数据的执行时间 *
      -         * 
      - */ - long getExecuteTime(); - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -         * * 变更数据的来源*
      -         * 
      - */ - boolean hasSourceType(); - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -         * * 变更数据的来源*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType(); - - /** - * optional string schemaName = 8; - * - *
      -         * * 变更数据的schemaname*
      -         * 
      - */ - boolean hasSchemaName(); - - /** - * optional string schemaName = 8; - * - *
      -         * * 变更数据的schemaname*
      -         * 
      - */ - java.lang.String getSchemaName(); - - /** - * optional string schemaName = 8; - * - *
      -         * * 变更数据的schemaname*
      -         * 
      - */ - com.google.protobuf.ByteString getSchemaNameBytes(); - - /** - * optional string tableName = 9; - * - *
      -         * *变更数据的tablename*
      -         * 
      - */ - boolean hasTableName(); - - /** - * optional string tableName = 9; - * - *
      -         * *变更数据的tablename*
      -         * 
      - */ - java.lang.String getTableName(); - - /** - * optional string tableName = 9; - * - *
      -         * *变更数据的tablename*
      -         * 
      - */ - com.google.protobuf.ByteString getTableNameBytes(); - - /** - * optional int64 eventLength = 10; - * - *
      -         * *每个event的长度*
      -         * 
      - */ - boolean hasEventLength(); - - /** - * optional int64 eventLength = 10; - * - *
      -         * *每个event的长度*
      -         * 
      - */ - long getEventLength(); - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - boolean hasEventType(); - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - int getPropsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index); - - /** - * optional string gtid = 13; - * - *
      -         * *当前事务的gitd*
      -         * 
      - */ - boolean hasGtid(); - - /** - * optional string gtid = 13; - * - *
      -         * *当前事务的gitd*
      -         * 
      - */ - java.lang.String getGtid(); - - /** - * optional string gtid = 13; - * - *
      -         * *当前事务的gitd*
      -         * 
      - */ - com.google.protobuf.ByteString getGtidBytes(); + static { + defaultInstance = new Entry(true); + defaultInstance.initFields(); } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Entry) + } + + public interface HeaderOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Header) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int32 version = 1 [default = 1]; + * + *
      +     **协议的版本号*
      +     * 
      + */ + boolean hasVersion(); + /** + * optional int32 version = 1 [default = 1]; + * + *
      +     **协议的版本号*
      +     * 
      + */ + int getVersion(); + + /** + * optional string logfileName = 2; + * + *
      +     **binlog/redolog 文件名*
      +     * 
      + */ + boolean hasLogfileName(); + /** + * optional string logfileName = 2; + * + *
      +     **binlog/redolog 文件名*
      +     * 
      + */ + String getLogfileName(); + /** + * optional string logfileName = 2; + * + *
      +     **binlog/redolog 文件名*
      +     * 
      + */ + com.google.protobuf.ByteString + getLogfileNameBytes(); + + /** + * optional int64 logfileOffset = 3; + * + *
      +     **binlog/redolog 文件的偏移位置*
      +     * 
      + */ + boolean hasLogfileOffset(); + /** + * optional int64 logfileOffset = 3; + * + *
      +     **binlog/redolog 文件的偏移位置*
      +     * 
      + */ + long getLogfileOffset(); + + /** + * optional int64 serverId = 4; + * + *
      +     **服务端serverId*
      +     * 
      + */ + boolean hasServerId(); + /** + * optional int64 serverId = 4; + * + *
      +     **服务端serverId*
      +     * 
      + */ + long getServerId(); + + /** + * optional string serverenCode = 5; + * + *
      +     ** 变更数据的编码 *
      +     * 
      + */ + boolean hasServerenCode(); + /** + * optional string serverenCode = 5; + * + *
      +     ** 变更数据的编码 *
      +     * 
      + */ + String getServerenCode(); + /** + * optional string serverenCode = 5; + * + *
      +     ** 变更数据的编码 *
      +     * 
      + */ + com.google.protobuf.ByteString + getServerenCodeBytes(); + + /** + * optional int64 executeTime = 6; + * + *
      +     **变更数据的执行时间 *
      +     * 
      + */ + boolean hasExecuteTime(); + /** + * optional int64 executeTime = 6; + * + *
      +     **变更数据的执行时间 *
      +     * 
      + */ + long getExecuteTime(); + + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +     ** 变更数据的来源*
      +     * 
      + */ + boolean hasSourceType(); + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +     ** 变更数据的来源*
      +     * 
      + */ + Type getSourceType(); + + /** + * optional string schemaName = 8; + * + *
      +     ** 变更数据的schemaname*
      +     * 
      + */ + boolean hasSchemaName(); + /** + * optional string schemaName = 8; + * + *
      +     ** 变更数据的schemaname*
      +     * 
      + */ + String getSchemaName(); + /** + * optional string schemaName = 8; + * + *
      +     ** 变更数据的schemaname*
      +     * 
      + */ + com.google.protobuf.ByteString + getSchemaNameBytes(); + + /** + * optional string tableName = 9; + * + *
      +     **变更数据的tablename*
      +     * 
      + */ + boolean hasTableName(); + /** + * optional string tableName = 9; + * + *
      +     **变更数据的tablename*
      +     * 
      + */ + String getTableName(); + /** + * optional string tableName = 9; + * + *
      +     **变更数据的tablename*
      +     * 
      + */ + com.google.protobuf.ByteString + getTableNameBytes(); + + /** + * optional int64 eventLength = 10; + * + *
      +     **每个event的长度*
      +     * 
      + */ + boolean hasEventLength(); + /** + * optional int64 eventLength = 10; + * + *
      +     **每个event的长度*
      +     * 
      + */ + long getEventLength(); + + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + boolean hasEventType(); + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + EventType getEventType(); + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + Pair getProps(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + int getPropsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + PairOrBuilder getPropsOrBuilder(int index); + + /** + * optional string gtid = 13; + * + *
      +     **当前事务的gitd*
      +     * 
      + */ + boolean hasGtid(); + /** + * optional string gtid = 13; + * + *
      +     **当前事务的gitd*
      +     * 
      + */ + String getGtid(); + /** + * optional string gtid = 13; + * + *
      +     **当前事务的gitd*
      +     * 
      + */ + com.google.protobuf.ByteString + getGtidBytes(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.Header} + * + *
      +   **message Header*
      +   * 
      + */ + public static final class Header extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Header) + HeaderOrBuilder { + // Use Header.newBuilder() to construct. + private Header(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private Header(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final Header defaultInstance; + public static Header getDefaultInstance() { + return defaultInstance; + } + + public Header getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Header( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + version_ = input.readInt32(); + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + logfileName_ = bs; + break; + } + case 24: { + bitField0_ |= 0x00000004; + logfileOffset_ = input.readInt64(); + break; + } + case 32: { + bitField0_ |= 0x00000008; + serverId_ = input.readInt64(); + break; + } + case 42: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000010; + serverenCode_ = bs; + break; + } + case 48: { + bitField0_ |= 0x00000020; + executeTime_ = input.readInt64(); + break; + } + case 56: { + int rawValue = input.readEnum(); + Type value = Type.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(7, rawValue); + } else { + bitField0_ |= 0x00000040; + sourceType_ = value; + } + break; + } + case 66: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000080; + schemaName_ = bs; + break; + } + case 74: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000100; + tableName_ = bs; + break; + } + case 80: { + bitField0_ |= 0x00000200; + eventLength_ = input.readInt64(); + break; + } + case 88: { + int rawValue = input.readEnum(); + EventType value = EventType.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(11, rawValue); + } else { + bitField0_ |= 0x00000400; + eventType_ = value; + } + break; + } + case 98: { + if (!((mutable_bitField0_ & 0x00000800) == 0x00000800)) { + props_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000800; + } + props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + break; + } + case 106: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000800; + gtid_ = bs; + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000800) == 0x00000800)) { + props_ = java.util.Collections.unmodifiableList(props_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Header.class, Builder.class); + } + + public static com.google.protobuf.Parser
      PARSER = + new com.google.protobuf.AbstractParser
      () { + public Header parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Header(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser
      getParserForType() { + return PARSER; + } + + private int bitField0_; + public static final int VERSION_FIELD_NUMBER = 1; + private int version_; + /** + * optional int32 version = 1 [default = 1]; + * + *
      +     **协议的版本号*
      +     * 
      + */ + public boolean hasVersion() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 version = 1 [default = 1]; + * + *
      +     **协议的版本号*
      +     * 
      + */ + public int getVersion() { + return version_; + } + + public static final int LOGFILENAME_FIELD_NUMBER = 2; + private Object logfileName_; + /** + * optional string logfileName = 2; + * + *
      +     **binlog/redolog 文件名*
      +     * 
      + */ + public boolean hasLogfileName() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string logfileName = 2; + * + *
      +     **binlog/redolog 文件名*
      +     * 
      + */ + public String getLogfileName() { + Object ref = logfileName_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + logfileName_ = s; + } + return s; + } + } + /** + * optional string logfileName = 2; + * + *
      +     **binlog/redolog 文件名*
      +     * 
      + */ + public com.google.protobuf.ByteString + getLogfileNameBytes() { + Object ref = logfileName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + logfileName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int LOGFILEOFFSET_FIELD_NUMBER = 3; + private long logfileOffset_; + /** + * optional int64 logfileOffset = 3; + * + *
      +     **binlog/redolog 文件的偏移位置*
      +     * 
      + */ + public boolean hasLogfileOffset() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 logfileOffset = 3; + * + *
      +     **binlog/redolog 文件的偏移位置*
      +     * 
      + */ + public long getLogfileOffset() { + return logfileOffset_; + } + + public static final int SERVERID_FIELD_NUMBER = 4; + private long serverId_; + /** + * optional int64 serverId = 4; + * + *
      +     **服务端serverId*
      +     * 
      + */ + public boolean hasServerId() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional int64 serverId = 4; + * + *
      +     **服务端serverId*
      +     * 
      + */ + public long getServerId() { + return serverId_; + } + + public static final int SERVERENCODE_FIELD_NUMBER = 5; + private Object serverenCode_; + /** + * optional string serverenCode = 5; + * + *
      +     ** 变更数据的编码 *
      +     * 
      + */ + public boolean hasServerenCode() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional string serverenCode = 5; + * + *
      +     ** 变更数据的编码 *
      +     * 
      + */ + public String getServerenCode() { + Object ref = serverenCode_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + serverenCode_ = s; + } + return s; + } + } + /** + * optional string serverenCode = 5; + * + *
      +     ** 变更数据的编码 *
      +     * 
      + */ + public com.google.protobuf.ByteString + getServerenCodeBytes() { + Object ref = serverenCode_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + serverenCode_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int EXECUTETIME_FIELD_NUMBER = 6; + private long executeTime_; + /** + * optional int64 executeTime = 6; + * + *
      +     **变更数据的执行时间 *
      +     * 
      + */ + public boolean hasExecuteTime() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional int64 executeTime = 6; + * + *
      +     **变更数据的执行时间 *
      +     * 
      + */ + public long getExecuteTime() { + return executeTime_; + } + + public static final int SOURCETYPE_FIELD_NUMBER = 7; + private Type sourceType_; + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +     ** 变更数据的来源*
      +     * 
      + */ + public boolean hasSourceType() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +     ** 变更数据的来源*
      +     * 
      + */ + public Type getSourceType() { + return sourceType_; + } + + public static final int SCHEMANAME_FIELD_NUMBER = 8; + private Object schemaName_; + /** + * optional string schemaName = 8; + * + *
      +     ** 变更数据的schemaname*
      +     * 
      + */ + public boolean hasSchemaName() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional string schemaName = 8; + * + *
      +     ** 变更数据的schemaname*
      +     * 
      + */ + public String getSchemaName() { + Object ref = schemaName_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + schemaName_ = s; + } + return s; + } + } + /** + * optional string schemaName = 8; + * + *
      +     ** 变更数据的schemaname*
      +     * 
      + */ + public com.google.protobuf.ByteString + getSchemaNameBytes() { + Object ref = schemaName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + schemaName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int TABLENAME_FIELD_NUMBER = 9; + private Object tableName_; + /** + * optional string tableName = 9; + * + *
      +     **变更数据的tablename*
      +     * 
      + */ + public boolean hasTableName() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional string tableName = 9; + * + *
      +     **变更数据的tablename*
      +     * 
      + */ + public String getTableName() { + Object ref = tableName_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + tableName_ = s; + } + return s; + } + } + /** + * optional string tableName = 9; + * + *
      +     **变更数据的tablename*
      +     * 
      + */ + public com.google.protobuf.ByteString + getTableNameBytes() { + Object ref = tableName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + tableName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int EVENTLENGTH_FIELD_NUMBER = 10; + private long eventLength_; + /** + * optional int64 eventLength = 10; + * + *
      +     **每个event的长度*
      +     * 
      + */ + public boolean hasEventLength() { + return ((bitField0_ & 0x00000200) == 0x00000200); + } + /** + * optional int64 eventLength = 10; + * + *
      +     **每个event的长度*
      +     * 
      + */ + public long getEventLength() { + return eventLength_; + } + + public static final int EVENTTYPE_FIELD_NUMBER = 11; + private EventType eventType_; + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + public boolean hasEventType() { + return ((bitField0_ & 0x00000400) == 0x00000400); + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + public EventType getEventType() { + return eventType_; + } + + public static final int PROPS_FIELD_NUMBER = 12; + private java.util.List props_; + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List getPropsList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List + getPropsOrBuilderList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + public int getPropsCount() { + return props_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + public Pair getProps(int index) { + return props_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +     **预留扩展*
      +     * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + return props_.get(index); + } + + public static final int GTID_FIELD_NUMBER = 13; + private Object gtid_; + /** + * optional string gtid = 13; + * + *
      +     **当前事务的gitd*
      +     * 
      + */ + public boolean hasGtid() { + return ((bitField0_ & 0x00000800) == 0x00000800); + } + /** + * optional string gtid = 13; + * + *
      +     **当前事务的gitd*
      +     * 
      + */ + public String getGtid() { + Object ref = gtid_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + gtid_ = s; + } + return s; + } + } + /** + * optional string gtid = 13; + * + *
      +     **当前事务的gitd*
      +     * 
      + */ + public com.google.protobuf.ByteString + getGtidBytes() { + Object ref = gtid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + gtid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + version_ = 1; + logfileName_ = ""; + logfileOffset_ = 0L; + serverId_ = 0L; + serverenCode_ = ""; + executeTime_ = 0L; + sourceType_ = Type.MYSQL; + schemaName_ = ""; + tableName_ = ""; + eventLength_ = 0L; + eventType_ = EventType.UPDATE; + props_ = java.util.Collections.emptyList(); + gtid_ = ""; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt32(1, version_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getLogfileNameBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt64(3, logfileOffset_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeInt64(4, serverId_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(5, getServerenCodeBytes()); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeInt64(6, executeTime_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + output.writeEnum(7, sourceType_.getNumber()); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + output.writeBytes(8, getSchemaNameBytes()); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + output.writeBytes(9, getTableNameBytes()); + } + if (((bitField0_ & 0x00000200) == 0x00000200)) { + output.writeInt64(10, eventLength_); + } + if (((bitField0_ & 0x00000400) == 0x00000400)) { + output.writeEnum(11, eventType_.getNumber()); + } + for (int i = 0; i < props_.size(); i++) { + output.writeMessage(12, props_.get(i)); + } + if (((bitField0_ & 0x00000800) == 0x00000800)) { + output.writeBytes(13, getGtidBytes()); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, version_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getLogfileNameBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(3, logfileOffset_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(4, serverId_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(5, getServerenCodeBytes()); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(6, executeTime_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(7, sourceType_.getNumber()); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(8, getSchemaNameBytes()); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(9, getTableNameBytes()); + } + if (((bitField0_ & 0x00000200) == 0x00000200)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(10, eventLength_); + } + if (((bitField0_ & 0x00000400) == 0x00000400)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(11, eventType_.getNumber()); + } + for (int i = 0; i < props_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(12, props_.get(i)); + } + if (((bitField0_ & 0x00000800) == 0x00000800)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(13, getGtidBytes()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static Header parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Header parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Header parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Header parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Header parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Header parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static Header parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static Header parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static Header parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Header parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(Header prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Header} * *
      -     * *message Header*
      +     **message Header*
            * 
      */ - public static final class Header extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Header) - HeaderOrBuilder { - - // Use Header.newBuilder() to construct. - private Header(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Header(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Header defaultInstance; - - public static Header getDefaultInstance() { - return defaultInstance; - } - - public Header getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Header(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - version_ = input.readInt32(); - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - logfileName_ = bs; - break; - } - case 24: { - bitField0_ |= 0x00000004; - logfileOffset_ = input.readInt64(); - break; - } - case 32: { - bitField0_ |= 0x00000008; - serverId_ = input.readInt64(); - break; - } - case 42: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000010; - serverenCode_ = bs; - break; - } - case 48: { - bitField0_ |= 0x00000020; - executeTime_ = input.readInt64(); - break; - } - case 56: { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalEntry.Type value = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(7, rawValue); - } else { - bitField0_ |= 0x00000040; - sourceType_ = value; - } - break; - } - case 66: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000080; - schemaName_ = bs; - break; - } - case 74: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000100; - tableName_ = bs; - break; - } - case 80: { - bitField0_ |= 0x00000200; - eventLength_ = input.readInt64(); - break; - } - case 88: { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalEntry.EventType value = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(11, rawValue); - } else { - bitField0_ |= 0x00000400; - eventType_ = value; - } - break; - } - case 98: { - if (!((mutable_bitField0_ & 0x00000800) == 0x00000800)) { - props_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000800; - } - props_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, - extensionRegistry)); - break; - } - case 106: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000800; - gtid_ = bs; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000800) == 0x00000800)) { - props_ = java.util.Collections.unmodifiableList(props_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Header.class, - com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder.class); - } - - public static com.google.protobuf.Parser
      PARSER = new com.google.protobuf.AbstractParser
      () { - - public Header parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Header(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser
      getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int VERSION_FIELD_NUMBER = 1; - private int version_; - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -         * *协议的版本号*
      -         * 
      - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -         * *协议的版本号*
      -         * 
      - */ - public int getVersion() { - return version_; - } - - public static final int LOGFILENAME_FIELD_NUMBER = 2; - private java.lang.Object logfileName_; - - /** - * optional string logfileName = 2; - * - *
      -         * *binlog/redolog 文件名*
      -         * 
      - */ - public boolean hasLogfileName() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string logfileName = 2; - * - *
      -         * *binlog/redolog 文件名*
      -         * 
      - */ - public java.lang.String getLogfileName() { - java.lang.Object ref = logfileName_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - logfileName_ = s; - } - return s; - } - } - - /** - * optional string logfileName = 2; - * - *
      -         * *binlog/redolog 文件名*
      -         * 
      - */ - public com.google.protobuf.ByteString getLogfileNameBytes() { - java.lang.Object ref = logfileName_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - logfileName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int LOGFILEOFFSET_FIELD_NUMBER = 3; - private long logfileOffset_; - - /** - * optional int64 logfileOffset = 3; - * - *
      -         * *binlog/redolog 文件的偏移位置*
      -         * 
      - */ - public boolean hasLogfileOffset() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 logfileOffset = 3; - * - *
      -         * *binlog/redolog 文件的偏移位置*
      -         * 
      - */ - public long getLogfileOffset() { - return logfileOffset_; - } - - public static final int SERVERID_FIELD_NUMBER = 4; - private long serverId_; - - /** - * optional int64 serverId = 4; - * - *
      -         * *服务端serverId*
      -         * 
      - */ - public boolean hasServerId() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional int64 serverId = 4; - * - *
      -         * *服务端serverId*
      -         * 
      - */ - public long getServerId() { - return serverId_; - } - - public static final int SERVERENCODE_FIELD_NUMBER = 5; - private java.lang.Object serverenCode_; - - /** - * optional string serverenCode = 5; - * - *
      -         * * 变更数据的编码 *
      -         * 
      - */ - public boolean hasServerenCode() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional string serverenCode = 5; - * - *
      -         * * 变更数据的编码 *
      -         * 
      - */ - public java.lang.String getServerenCode() { - java.lang.Object ref = serverenCode_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - serverenCode_ = s; - } - return s; - } - } - - /** - * optional string serverenCode = 5; - * - *
      -         * * 变更数据的编码 *
      -         * 
      - */ - public com.google.protobuf.ByteString getServerenCodeBytes() { - java.lang.Object ref = serverenCode_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - serverenCode_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int EXECUTETIME_FIELD_NUMBER = 6; - private long executeTime_; - - /** - * optional int64 executeTime = 6; - * - *
      -         * *变更数据的执行时间 *
      -         * 
      - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional int64 executeTime = 6; - * - *
      -         * *变更数据的执行时间 *
      -         * 
      - */ - public long getExecuteTime() { - return executeTime_; - } - - public static final int SOURCETYPE_FIELD_NUMBER = 7; - private com.alibaba.otter.canal.protocol.CanalEntry.Type sourceType_; - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -         * * 变更数据的来源*
      -         * 
      - */ - public boolean hasSourceType() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -         * * 变更数据的来源*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType() { - return sourceType_; - } - - public static final int SCHEMANAME_FIELD_NUMBER = 8; - private java.lang.Object schemaName_; - - /** - * optional string schemaName = 8; - * - *
      -         * * 变更数据的schemaname*
      -         * 
      - */ - public boolean hasSchemaName() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - - /** - * optional string schemaName = 8; - * - *
      -         * * 变更数据的schemaname*
      -         * 
      - */ - public java.lang.String getSchemaName() { - java.lang.Object ref = schemaName_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - schemaName_ = s; - } - return s; - } - } - - /** - * optional string schemaName = 8; - * - *
      -         * * 变更数据的schemaname*
      -         * 
      - */ - public com.google.protobuf.ByteString getSchemaNameBytes() { - java.lang.Object ref = schemaName_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - schemaName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int TABLENAME_FIELD_NUMBER = 9; - private java.lang.Object tableName_; - - /** - * optional string tableName = 9; - * - *
      -         * *变更数据的tablename*
      -         * 
      - */ - public boolean hasTableName() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - - /** - * optional string tableName = 9; - * - *
      -         * *变更数据的tablename*
      -         * 
      - */ - public java.lang.String getTableName() { - java.lang.Object ref = tableName_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - tableName_ = s; - } - return s; - } - } - - /** - * optional string tableName = 9; - * - *
      -         * *变更数据的tablename*
      -         * 
      - */ - public com.google.protobuf.ByteString getTableNameBytes() { - java.lang.Object ref = tableName_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - tableName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int EVENTLENGTH_FIELD_NUMBER = 10; - private long eventLength_; - - /** - * optional int64 eventLength = 10; - * - *
      -         * *每个event的长度*
      -         * 
      - */ - public boolean hasEventLength() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - - /** - * optional int64 eventLength = 10; - * - *
      -         * *每个event的长度*
      -         * 
      - */ - public long getEventLength() { - return eventLength_; - } - - public static final int EVENTTYPE_FIELD_NUMBER = 11; - private com.alibaba.otter.canal.protocol.CanalEntry.EventType eventType_; - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000400) == 0x00000400); - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - return eventType_; - } - - public static final int PROPS_FIELD_NUMBER = 12; - private java.util.List props_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsOrBuilderList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public int getPropsCount() { - return props_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - return props_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - return props_.get(index); - } - - public static final int GTID_FIELD_NUMBER = 13; - private java.lang.Object gtid_; - - /** - * optional string gtid = 13; - * - *
      -         * *当前事务的gitd*
      -         * 
      - */ - public boolean hasGtid() { - return ((bitField0_ & 0x00000800) == 0x00000800); - } - - /** - * optional string gtid = 13; - * - *
      -         * *当前事务的gitd*
      -         * 
      - */ - public java.lang.String getGtid() { - java.lang.Object ref = gtid_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - gtid_ = s; - } - return s; - } - } - - /** - * optional string gtid = 13; - * - *
      -         * *当前事务的gitd*
      -         * 
      - */ - public com.google.protobuf.ByteString getGtidBytes() { - java.lang.Object ref = gtid_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - gtid_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - version_ = 1; - logfileName_ = ""; - logfileOffset_ = 0L; - serverId_ = 0L; - serverenCode_ = ""; - executeTime_ = 0L; - sourceType_ = com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL; - schemaName_ = ""; - tableName_ = ""; - eventLength_ = 0L; - eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - props_ = java.util.Collections.emptyList(); - gtid_ = ""; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt32(1, version_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getLogfileNameBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt64(3, logfileOffset_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeInt64(4, serverId_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBytes(5, getServerenCodeBytes()); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeInt64(6, executeTime_); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeEnum(7, sourceType_.getNumber()); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - output.writeBytes(8, getSchemaNameBytes()); - } - if (((bitField0_ & 0x00000100) == 0x00000100)) { - output.writeBytes(9, getTableNameBytes()); - } - if (((bitField0_ & 0x00000200) == 0x00000200)) { - output.writeInt64(10, eventLength_); - } - if (((bitField0_ & 0x00000400) == 0x00000400)) { - output.writeEnum(11, eventType_.getNumber()); - } - for (int i = 0; i < props_.size(); i++) { - output.writeMessage(12, props_.get(i)); - } - if (((bitField0_ & 0x00000800) == 0x00000800)) { - output.writeBytes(13, getGtidBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(1, version_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getLogfileNameBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(3, logfileOffset_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(4, serverId_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(5, getServerenCodeBytes()); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(6, executeTime_); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - size += com.google.protobuf.CodedOutputStream.computeEnumSize(7, sourceType_.getNumber()); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(8, getSchemaNameBytes()); - } - if (((bitField0_ & 0x00000100) == 0x00000100)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(9, getTableNameBytes()); - } - if (((bitField0_ & 0x00000200) == 0x00000200)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(10, eventLength_); - } - if (((bitField0_ & 0x00000400) == 0x00000400)) { - size += com.google.protobuf.CodedOutputStream.computeEnumSize(11, eventType_.getNumber()); - } - for (int i = 0; i < props_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(12, props_.get(i)); - } - if (((bitField0_ & 0x00000800) == 0x00000800)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(13, getGtidBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Header prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Header} - * - *
      -         * *message Header*
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Header) - com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder { - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Header.class, - com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder.class); - } - - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.Header.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getPropsFieldBuilder(); - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - version_ = 1; - bitField0_ = (bitField0_ & ~0x00000001); - logfileName_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - logfileOffset_ = 0L; - bitField0_ = (bitField0_ & ~0x00000004); - serverId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000008); - serverenCode_ = ""; - bitField0_ = (bitField0_ & ~0x00000010); - executeTime_ = 0L; - bitField0_ = (bitField0_ & ~0x00000020); - sourceType_ = com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL; - bitField0_ = (bitField0_ & ~0x00000040); - schemaName_ = ""; - bitField0_ = (bitField0_ & ~0x00000080); - tableName_ = ""; - bitField0_ = (bitField0_ & ~0x00000100); - eventLength_ = 0L; - bitField0_ = (bitField0_ & ~0x00000200); - eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - bitField0_ = (bitField0_ & ~0x00000400); - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000800); - } else { - propsBuilder_.clear(); - } - gtid_ = ""; - bitField0_ = (bitField0_ & ~0x00001000); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Header getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Header build() { - com.alibaba.otter.canal.protocol.CanalEntry.Header result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Header buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.Header result = new com.alibaba.otter.canal.protocol.CanalEntry.Header(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.version_ = version_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.logfileName_ = logfileName_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.logfileOffset_ = logfileOffset_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.serverId_ = serverId_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - result.serverenCode_ = serverenCode_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } - result.executeTime_ = executeTime_; - if (((from_bitField0_ & 0x00000040) == 0x00000040)) { - to_bitField0_ |= 0x00000040; - } - result.sourceType_ = sourceType_; - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000080; - } - result.schemaName_ = schemaName_; - if (((from_bitField0_ & 0x00000100) == 0x00000100)) { - to_bitField0_ |= 0x00000100; - } - result.tableName_ = tableName_; - if (((from_bitField0_ & 0x00000200) == 0x00000200)) { - to_bitField0_ |= 0x00000200; - } - result.eventLength_ = eventLength_; - if (((from_bitField0_ & 0x00000400) == 0x00000400)) { - to_bitField0_ |= 0x00000400; - } - result.eventType_ = eventType_; - if (propsBuilder_ == null) { - if (((bitField0_ & 0x00000800) == 0x00000800)) { - props_ = java.util.Collections.unmodifiableList(props_); - bitField0_ = (bitField0_ & ~0x00000800); - } - result.props_ = props_; - } else { - result.props_ = propsBuilder_.build(); - } - if (((from_bitField0_ & 0x00001000) == 0x00001000)) { - to_bitField0_ |= 0x00000800; - } - result.gtid_ = gtid_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Header) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Header) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Header other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance()) return this; - if (other.hasVersion()) { - setVersion(other.getVersion()); - } - if (other.hasLogfileName()) { - bitField0_ |= 0x00000002; - logfileName_ = other.logfileName_; - onChanged(); - } - if (other.hasLogfileOffset()) { - setLogfileOffset(other.getLogfileOffset()); - } - if (other.hasServerId()) { - setServerId(other.getServerId()); - } - if (other.hasServerenCode()) { - bitField0_ |= 0x00000010; - serverenCode_ = other.serverenCode_; - onChanged(); - } - if (other.hasExecuteTime()) { - setExecuteTime(other.getExecuteTime()); - } - if (other.hasSourceType()) { - setSourceType(other.getSourceType()); - } - if (other.hasSchemaName()) { - bitField0_ |= 0x00000080; - schemaName_ = other.schemaName_; - onChanged(); - } - if (other.hasTableName()) { - bitField0_ |= 0x00000100; - tableName_ = other.tableName_; - onChanged(); - } - if (other.hasEventLength()) { - setEventLength(other.getEventLength()); - } - if (other.hasEventType()) { - setEventType(other.getEventType()); - } - if (propsBuilder_ == null) { - if (!other.props_.isEmpty()) { - if (props_.isEmpty()) { - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000800); - } else { - ensurePropsIsMutable(); - props_.addAll(other.props_); - } - onChanged(); - } - } else { - if (!other.props_.isEmpty()) { - if (propsBuilder_.isEmpty()) { - propsBuilder_.dispose(); - propsBuilder_ = null; - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000800); - propsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; - } else { - propsBuilder_.addAllMessages(other.props_); - } - } - } - if (other.hasGtid()) { - bitField0_ |= 0x00001000; - gtid_ = other.gtid_; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.Header parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Header) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private int version_ = 1; - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -             * *协议的版本号*
      -             * 
      - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -             * *协议的版本号*
      -             * 
      - */ - public int getVersion() { - return version_; - } - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -             * *协议的版本号*
      -             * 
      - */ - public Builder setVersion(int value) { - bitField0_ |= 0x00000001; - version_ = value; - onChanged(); - return this; - } - - /** - * optional int32 version = 1 [default = 1]; - * - *
      -             * *协议的版本号*
      -             * 
      - */ - public Builder clearVersion() { - bitField0_ = (bitField0_ & ~0x00000001); - version_ = 1; - onChanged(); - return this; - } - - private java.lang.Object logfileName_ = ""; - - /** - * optional string logfileName = 2; - * - *
      -             * *binlog/redolog 文件名*
      -             * 
      - */ - public boolean hasLogfileName() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string logfileName = 2; - * - *
      -             * *binlog/redolog 文件名*
      -             * 
      - */ - public java.lang.String getLogfileName() { - java.lang.Object ref = logfileName_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - logfileName_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string logfileName = 2; - * - *
      -             * *binlog/redolog 文件名*
      -             * 
      - */ - public com.google.protobuf.ByteString getLogfileNameBytes() { - java.lang.Object ref = logfileName_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - logfileName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string logfileName = 2; - * - *
      -             * *binlog/redolog 文件名*
      -             * 
      - */ - public Builder setLogfileName(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - logfileName_ = value; - onChanged(); - return this; - } - - /** - * optional string logfileName = 2; - * - *
      -             * *binlog/redolog 文件名*
      -             * 
      - */ - public Builder clearLogfileName() { - bitField0_ = (bitField0_ & ~0x00000002); - logfileName_ = getDefaultInstance().getLogfileName(); - onChanged(); - return this; - } - - /** - * optional string logfileName = 2; - * - *
      -             * *binlog/redolog 文件名*
      -             * 
      - */ - public Builder setLogfileNameBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - logfileName_ = value; - onChanged(); - return this; - } - - private long logfileOffset_; - - /** - * optional int64 logfileOffset = 3; - * - *
      -             * *binlog/redolog 文件的偏移位置*
      -             * 
      - */ - public boolean hasLogfileOffset() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 logfileOffset = 3; - * - *
      -             * *binlog/redolog 文件的偏移位置*
      -             * 
      - */ - public long getLogfileOffset() { - return logfileOffset_; - } - - /** - * optional int64 logfileOffset = 3; - * - *
      -             * *binlog/redolog 文件的偏移位置*
      -             * 
      - */ - public Builder setLogfileOffset(long value) { - bitField0_ |= 0x00000004; - logfileOffset_ = value; - onChanged(); - return this; - } - - /** - * optional int64 logfileOffset = 3; - * - *
      -             * *binlog/redolog 文件的偏移位置*
      -             * 
      - */ - public Builder clearLogfileOffset() { - bitField0_ = (bitField0_ & ~0x00000004); - logfileOffset_ = 0L; - onChanged(); - return this; - } - - private long serverId_; - - /** - * optional int64 serverId = 4; - * - *
      -             * *服务端serverId*
      -             * 
      - */ - public boolean hasServerId() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional int64 serverId = 4; - * - *
      -             * *服务端serverId*
      -             * 
      - */ - public long getServerId() { - return serverId_; - } - - /** - * optional int64 serverId = 4; - * - *
      -             * *服务端serverId*
      -             * 
      - */ - public Builder setServerId(long value) { - bitField0_ |= 0x00000008; - serverId_ = value; - onChanged(); - return this; - } - - /** - * optional int64 serverId = 4; - * - *
      -             * *服务端serverId*
      -             * 
      - */ - public Builder clearServerId() { - bitField0_ = (bitField0_ & ~0x00000008); - serverId_ = 0L; - onChanged(); - return this; - } - - private java.lang.Object serverenCode_ = ""; - - /** - * optional string serverenCode = 5; - * - *
      -             * * 变更数据的编码 *
      -             * 
      - */ - public boolean hasServerenCode() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional string serverenCode = 5; - * - *
      -             * * 变更数据的编码 *
      -             * 
      - */ - public java.lang.String getServerenCode() { - java.lang.Object ref = serverenCode_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - serverenCode_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string serverenCode = 5; - * - *
      -             * * 变更数据的编码 *
      -             * 
      - */ - public com.google.protobuf.ByteString getServerenCodeBytes() { - java.lang.Object ref = serverenCode_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - serverenCode_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string serverenCode = 5; - * - *
      -             * * 变更数据的编码 *
      -             * 
      - */ - public Builder setServerenCode(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000010; - serverenCode_ = value; - onChanged(); - return this; - } - - /** - * optional string serverenCode = 5; - * - *
      -             * * 变更数据的编码 *
      -             * 
      - */ - public Builder clearServerenCode() { - bitField0_ = (bitField0_ & ~0x00000010); - serverenCode_ = getDefaultInstance().getServerenCode(); - onChanged(); - return this; - } - - /** - * optional string serverenCode = 5; - * - *
      -             * * 变更数据的编码 *
      -             * 
      - */ - public Builder setServerenCodeBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000010; - serverenCode_ = value; - onChanged(); - return this; - } - - private long executeTime_; - - /** - * optional int64 executeTime = 6; - * - *
      -             * *变更数据的执行时间 *
      -             * 
      - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional int64 executeTime = 6; - * - *
      -             * *变更数据的执行时间 *
      -             * 
      - */ - public long getExecuteTime() { - return executeTime_; - } - - /** - * optional int64 executeTime = 6; - * - *
      -             * *变更数据的执行时间 *
      -             * 
      - */ - public Builder setExecuteTime(long value) { - bitField0_ |= 0x00000020; - executeTime_ = value; - onChanged(); - return this; - } - - /** - * optional int64 executeTime = 6; - * - *
      -             * *变更数据的执行时间 *
      -             * 
      - */ - public Builder clearExecuteTime() { - bitField0_ = (bitField0_ & ~0x00000020); - executeTime_ = 0L; - onChanged(); - return this; - } - - private com.alibaba.otter.canal.protocol.CanalEntry.Type sourceType_ = com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL; - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -             * * 变更数据的来源*
      -             * 
      - */ - public boolean hasSourceType() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -             * * 变更数据的来源*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType() { - return sourceType_; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -             * * 变更数据的来源*
      -             * 
      - */ - public Builder setSourceType(com.alibaba.otter.canal.protocol.CanalEntry.Type value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000040; - sourceType_ = value; - onChanged(); - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * - *
      -             * * 变更数据的来源*
      -             * 
      - */ - public Builder clearSourceType() { - bitField0_ = (bitField0_ & ~0x00000040); - sourceType_ = com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL; - onChanged(); - return this; - } - - private java.lang.Object schemaName_ = ""; - - /** - * optional string schemaName = 8; - * - *
      -             * * 变更数据的schemaname*
      -             * 
      - */ - public boolean hasSchemaName() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - - /** - * optional string schemaName = 8; - * - *
      -             * * 变更数据的schemaname*
      -             * 
      - */ - public java.lang.String getSchemaName() { - java.lang.Object ref = schemaName_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - schemaName_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string schemaName = 8; - * - *
      -             * * 变更数据的schemaname*
      -             * 
      - */ - public com.google.protobuf.ByteString getSchemaNameBytes() { - java.lang.Object ref = schemaName_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - schemaName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string schemaName = 8; - * - *
      -             * * 变更数据的schemaname*
      -             * 
      - */ - public Builder setSchemaName(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000080; - schemaName_ = value; - onChanged(); - return this; - } - - /** - * optional string schemaName = 8; - * - *
      -             * * 变更数据的schemaname*
      -             * 
      - */ - public Builder clearSchemaName() { - bitField0_ = (bitField0_ & ~0x00000080); - schemaName_ = getDefaultInstance().getSchemaName(); - onChanged(); - return this; - } - - /** - * optional string schemaName = 8; - * - *
      -             * * 变更数据的schemaname*
      -             * 
      - */ - public Builder setSchemaNameBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000080; - schemaName_ = value; - onChanged(); - return this; - } - - private java.lang.Object tableName_ = ""; - - /** - * optional string tableName = 9; - * - *
      -             * *变更数据的tablename*
      -             * 
      - */ - public boolean hasTableName() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - - /** - * optional string tableName = 9; - * - *
      -             * *变更数据的tablename*
      -             * 
      - */ - public java.lang.String getTableName() { - java.lang.Object ref = tableName_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - tableName_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string tableName = 9; - * - *
      -             * *变更数据的tablename*
      -             * 
      - */ - public com.google.protobuf.ByteString getTableNameBytes() { - java.lang.Object ref = tableName_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - tableName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string tableName = 9; - * - *
      -             * *变更数据的tablename*
      -             * 
      - */ - public Builder setTableName(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000100; - tableName_ = value; - onChanged(); - return this; - } - - /** - * optional string tableName = 9; - * - *
      -             * *变更数据的tablename*
      -             * 
      - */ - public Builder clearTableName() { - bitField0_ = (bitField0_ & ~0x00000100); - tableName_ = getDefaultInstance().getTableName(); - onChanged(); - return this; - } - - /** - * optional string tableName = 9; - * - *
      -             * *变更数据的tablename*
      -             * 
      - */ - public Builder setTableNameBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000100; - tableName_ = value; - onChanged(); - return this; - } - - private long eventLength_; - - /** - * optional int64 eventLength = 10; - * - *
      -             * *每个event的长度*
      -             * 
      - */ - public boolean hasEventLength() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - - /** - * optional int64 eventLength = 10; - * - *
      -             * *每个event的长度*
      -             * 
      - */ - public long getEventLength() { - return eventLength_; - } - - /** - * optional int64 eventLength = 10; - * - *
      -             * *每个event的长度*
      -             * 
      - */ - public Builder setEventLength(long value) { - bitField0_ |= 0x00000200; - eventLength_ = value; - onChanged(); - return this; - } - - /** - * optional int64 eventLength = 10; - * - *
      -             * *每个event的长度*
      -             * 
      - */ - public Builder clearEventLength() { - bitField0_ = (bitField0_ & ~0x00000200); - eventLength_ = 0L; - onChanged(); - return this; - } - - private com.alibaba.otter.canal.protocol.CanalEntry.EventType eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000400) == 0x00000400); - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - return eventType_; - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public Builder setEventType(com.alibaba.otter.canal.protocol.CanalEntry.EventType value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000400; - eventType_ = value; - onChanged(); - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public Builder clearEventType() { - bitField0_ = (bitField0_ & ~0x00000400); - eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - onChanged(); - return this; - } - - private java.util.List props_ = java.util.Collections.emptyList(); - - private void ensurePropsIsMutable() { - if (!((bitField0_ & 0x00000800) == 0x00000800)) { - props_ = new java.util.ArrayList(props_); - bitField0_ |= 0x00000800; - } - } - - private com.google.protobuf.RepeatedFieldBuilder propsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsList() { - if (propsBuilder_ == null) { - return java.util.Collections.unmodifiableList(props_); - } else { - return propsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public int getPropsCount() { - if (propsBuilder_ == null) { - return props_.size(); - } else { - return propsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.set(index, value); - onChanged(); - } else { - propsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.set(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(value); - onChanged(); - } else { - propsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(index, value); - onChanged(); - } else { - propsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addAllProps(java.lang.Iterable values) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, props_); - onChanged(); - } else { - propsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder clearProps() { - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000800); - onChanged(); - } else { - propsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder removeProps(int index) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.remove(index); - onChanged(); - } else { - propsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder(int index) { - return getPropsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsOrBuilderList() { - if (propsBuilder_ != null) { - return propsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(props_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { - return getPropsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder(int index) { - return getPropsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsBuilderList() { - return getPropsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getPropsFieldBuilder() { - if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(props_, - ((bitField0_ & 0x00000800) == 0x00000800), - getParentForChildren(), - isClean()); - props_ = null; - } - return propsBuilder_; - } - - private java.lang.Object gtid_ = ""; - - /** - * optional string gtid = 13; - * - *
      -             * *当前事务的gitd*
      -             * 
      - */ - public boolean hasGtid() { - return ((bitField0_ & 0x00001000) == 0x00001000); - } - - /** - * optional string gtid = 13; - * - *
      -             * *当前事务的gitd*
      -             * 
      - */ - public java.lang.String getGtid() { - java.lang.Object ref = gtid_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - gtid_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string gtid = 13; - * - *
      -             * *当前事务的gitd*
      -             * 
      - */ - public com.google.protobuf.ByteString getGtidBytes() { - java.lang.Object ref = gtid_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - gtid_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string gtid = 13; - * - *
      -             * *当前事务的gitd*
      -             * 
      - */ - public Builder setGtid(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00001000; - gtid_ = value; - onChanged(); - return this; - } - - /** - * optional string gtid = 13; - * - *
      -             * *当前事务的gitd*
      -             * 
      - */ - public Builder clearGtid() { - bitField0_ = (bitField0_ & ~0x00001000); - gtid_ = getDefaultInstance().getGtid(); - onChanged(); - return this; - } - - /** - * optional string gtid = 13; - * - *
      -             * *当前事务的gitd*
      -             * 
      - */ - public Builder setGtidBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00001000; - gtid_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Header) + HeaderOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Header.class, Builder.class); + } + + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Header.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getPropsFieldBuilder(); } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new Header(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + version_ = 1; + bitField0_ = (bitField0_ & ~0x00000001); + logfileName_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + logfileOffset_ = 0L; + bitField0_ = (bitField0_ & ~0x00000004); + serverId_ = 0L; + bitField0_ = (bitField0_ & ~0x00000008); + serverenCode_ = ""; + bitField0_ = (bitField0_ & ~0x00000010); + executeTime_ = 0L; + bitField0_ = (bitField0_ & ~0x00000020); + sourceType_ = Type.MYSQL; + bitField0_ = (bitField0_ & ~0x00000040); + schemaName_ = ""; + bitField0_ = (bitField0_ & ~0x00000080); + tableName_ = ""; + bitField0_ = (bitField0_ & ~0x00000100); + eventLength_ = 0L; + bitField0_ = (bitField0_ & ~0x00000200); + eventType_ = EventType.UPDATE; + bitField0_ = (bitField0_ & ~0x00000400); + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000800); + } else { + propsBuilder_.clear(); } + gtid_ = ""; + bitField0_ = (bitField0_ & ~0x00001000); + return this; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Header) + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; + } + + public Header getDefaultInstanceForType() { + return Header.getDefaultInstance(); + } + + public Header build() { + Header result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public Header buildPartial() { + Header result = new Header(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.version_ = version_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.logfileName_ = logfileName_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.logfileOffset_ = logfileOffset_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.serverId_ = serverId_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.serverenCode_ = serverenCode_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + result.executeTime_ = executeTime_; + if (((from_bitField0_ & 0x00000040) == 0x00000040)) { + to_bitField0_ |= 0x00000040; + } + result.sourceType_ = sourceType_; + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000080; + } + result.schemaName_ = schemaName_; + if (((from_bitField0_ & 0x00000100) == 0x00000100)) { + to_bitField0_ |= 0x00000100; + } + result.tableName_ = tableName_; + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000200; + } + result.eventLength_ = eventLength_; + if (((from_bitField0_ & 0x00000400) == 0x00000400)) { + to_bitField0_ |= 0x00000400; + } + result.eventType_ = eventType_; + if (propsBuilder_ == null) { + if (((bitField0_ & 0x00000800) == 0x00000800)) { + props_ = java.util.Collections.unmodifiableList(props_); + bitField0_ = (bitField0_ & ~0x00000800); + } + result.props_ = props_; + } else { + result.props_ = propsBuilder_.build(); + } + if (((from_bitField0_ & 0x00001000) == 0x00001000)) { + to_bitField0_ |= 0x00000800; + } + result.gtid_ = gtid_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Header) { + return mergeFrom((Header)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Header other) { + if (other == Header.getDefaultInstance()) return this; + if (other.hasVersion()) { + setVersion(other.getVersion()); + } + if (other.hasLogfileName()) { + bitField0_ |= 0x00000002; + logfileName_ = other.logfileName_; + onChanged(); + } + if (other.hasLogfileOffset()) { + setLogfileOffset(other.getLogfileOffset()); + } + if (other.hasServerId()) { + setServerId(other.getServerId()); + } + if (other.hasServerenCode()) { + bitField0_ |= 0x00000010; + serverenCode_ = other.serverenCode_; + onChanged(); + } + if (other.hasExecuteTime()) { + setExecuteTime(other.getExecuteTime()); + } + if (other.hasSourceType()) { + setSourceType(other.getSourceType()); + } + if (other.hasSchemaName()) { + bitField0_ |= 0x00000080; + schemaName_ = other.schemaName_; + onChanged(); + } + if (other.hasTableName()) { + bitField0_ |= 0x00000100; + tableName_ = other.tableName_; + onChanged(); + } + if (other.hasEventLength()) { + setEventLength(other.getEventLength()); + } + if (other.hasEventType()) { + setEventType(other.getEventType()); + } + if (propsBuilder_ == null) { + if (!other.props_.isEmpty()) { + if (props_.isEmpty()) { + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000800); + } else { + ensurePropsIsMutable(); + props_.addAll(other.props_); + } + onChanged(); + } + } else { + if (!other.props_.isEmpty()) { + if (propsBuilder_.isEmpty()) { + propsBuilder_.dispose(); + propsBuilder_ = null; + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000800); + propsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPropsFieldBuilder() : null; + } else { + propsBuilder_.addAllMessages(other.props_); + } + } + } + if (other.hasGtid()) { + bitField0_ |= 0x00001000; + gtid_ = other.gtid_; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Header parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (Header) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int version_ = 1; + /** + * optional int32 version = 1 [default = 1]; + * + *
      +       **协议的版本号*
      +       * 
      + */ + public boolean hasVersion() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 version = 1 [default = 1]; + * + *
      +       **协议的版本号*
      +       * 
      + */ + public int getVersion() { + return version_; + } + /** + * optional int32 version = 1 [default = 1]; + * + *
      +       **协议的版本号*
      +       * 
      + */ + public Builder setVersion(int value) { + bitField0_ |= 0x00000001; + version_ = value; + onChanged(); + return this; + } + /** + * optional int32 version = 1 [default = 1]; + * + *
      +       **协议的版本号*
      +       * 
      + */ + public Builder clearVersion() { + bitField0_ = (bitField0_ & ~0x00000001); + version_ = 1; + onChanged(); + return this; + } + + private Object logfileName_ = ""; + /** + * optional string logfileName = 2; + * + *
      +       **binlog/redolog 文件名*
      +       * 
      + */ + public boolean hasLogfileName() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string logfileName = 2; + * + *
      +       **binlog/redolog 文件名*
      +       * 
      + */ + public String getLogfileName() { + Object ref = logfileName_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + logfileName_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string logfileName = 2; + * + *
      +       **binlog/redolog 文件名*
      +       * 
      + */ + public com.google.protobuf.ByteString + getLogfileNameBytes() { + Object ref = logfileName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + logfileName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string logfileName = 2; + * + *
      +       **binlog/redolog 文件名*
      +       * 
      + */ + public Builder setLogfileName( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + logfileName_ = value; + onChanged(); + return this; + } + /** + * optional string logfileName = 2; + * + *
      +       **binlog/redolog 文件名*
      +       * 
      + */ + public Builder clearLogfileName() { + bitField0_ = (bitField0_ & ~0x00000002); + logfileName_ = getDefaultInstance().getLogfileName(); + onChanged(); + return this; + } + /** + * optional string logfileName = 2; + * + *
      +       **binlog/redolog 文件名*
      +       * 
      + */ + public Builder setLogfileNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + logfileName_ = value; + onChanged(); + return this; + } + + private long logfileOffset_ ; + /** + * optional int64 logfileOffset = 3; + * + *
      +       **binlog/redolog 文件的偏移位置*
      +       * 
      + */ + public boolean hasLogfileOffset() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 logfileOffset = 3; + * + *
      +       **binlog/redolog 文件的偏移位置*
      +       * 
      + */ + public long getLogfileOffset() { + return logfileOffset_; + } + /** + * optional int64 logfileOffset = 3; + * + *
      +       **binlog/redolog 文件的偏移位置*
      +       * 
      + */ + public Builder setLogfileOffset(long value) { + bitField0_ |= 0x00000004; + logfileOffset_ = value; + onChanged(); + return this; + } + /** + * optional int64 logfileOffset = 3; + * + *
      +       **binlog/redolog 文件的偏移位置*
      +       * 
      + */ + public Builder clearLogfileOffset() { + bitField0_ = (bitField0_ & ~0x00000004); + logfileOffset_ = 0L; + onChanged(); + return this; + } + + private long serverId_ ; + /** + * optional int64 serverId = 4; + * + *
      +       **服务端serverId*
      +       * 
      + */ + public boolean hasServerId() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional int64 serverId = 4; + * + *
      +       **服务端serverId*
      +       * 
      + */ + public long getServerId() { + return serverId_; + } + /** + * optional int64 serverId = 4; + * + *
      +       **服务端serverId*
      +       * 
      + */ + public Builder setServerId(long value) { + bitField0_ |= 0x00000008; + serverId_ = value; + onChanged(); + return this; + } + /** + * optional int64 serverId = 4; + * + *
      +       **服务端serverId*
      +       * 
      + */ + public Builder clearServerId() { + bitField0_ = (bitField0_ & ~0x00000008); + serverId_ = 0L; + onChanged(); + return this; + } + + private Object serverenCode_ = ""; + /** + * optional string serverenCode = 5; + * + *
      +       ** 变更数据的编码 *
      +       * 
      + */ + public boolean hasServerenCode() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional string serverenCode = 5; + * + *
      +       ** 变更数据的编码 *
      +       * 
      + */ + public String getServerenCode() { + Object ref = serverenCode_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + serverenCode_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string serverenCode = 5; + * + *
      +       ** 变更数据的编码 *
      +       * 
      + */ + public com.google.protobuf.ByteString + getServerenCodeBytes() { + Object ref = serverenCode_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + serverenCode_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string serverenCode = 5; + * + *
      +       ** 变更数据的编码 *
      +       * 
      + */ + public Builder setServerenCode( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + serverenCode_ = value; + onChanged(); + return this; + } + /** + * optional string serverenCode = 5; + * + *
      +       ** 变更数据的编码 *
      +       * 
      + */ + public Builder clearServerenCode() { + bitField0_ = (bitField0_ & ~0x00000010); + serverenCode_ = getDefaultInstance().getServerenCode(); + onChanged(); + return this; + } + /** + * optional string serverenCode = 5; + * + *
      +       ** 变更数据的编码 *
      +       * 
      + */ + public Builder setServerenCodeBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + serverenCode_ = value; + onChanged(); + return this; + } + + private long executeTime_ ; + /** + * optional int64 executeTime = 6; + * + *
      +       **变更数据的执行时间 *
      +       * 
      + */ + public boolean hasExecuteTime() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional int64 executeTime = 6; + * + *
      +       **变更数据的执行时间 *
      +       * 
      + */ + public long getExecuteTime() { + return executeTime_; + } + /** + * optional int64 executeTime = 6; + * + *
      +       **变更数据的执行时间 *
      +       * 
      + */ + public Builder setExecuteTime(long value) { + bitField0_ |= 0x00000020; + executeTime_ = value; + onChanged(); + return this; + } + /** + * optional int64 executeTime = 6; + * + *
      +       **变更数据的执行时间 *
      +       * 
      + */ + public Builder clearExecuteTime() { + bitField0_ = (bitField0_ & ~0x00000020); + executeTime_ = 0L; + onChanged(); + return this; + } + + private Type sourceType_ = Type.MYSQL; + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +       ** 变更数据的来源*
      +       * 
      + */ + public boolean hasSourceType() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +       ** 变更数据的来源*
      +       * 
      + */ + public Type getSourceType() { + return sourceType_; + } + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +       ** 变更数据的来源*
      +       * 
      + */ + public Builder setSourceType(Type value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000040; + sourceType_ = value; + onChanged(); + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * + *
      +       ** 变更数据的来源*
      +       * 
      + */ + public Builder clearSourceType() { + bitField0_ = (bitField0_ & ~0x00000040); + sourceType_ = Type.MYSQL; + onChanged(); + return this; + } + + private Object schemaName_ = ""; + /** + * optional string schemaName = 8; + * + *
      +       ** 变更数据的schemaname*
      +       * 
      + */ + public boolean hasSchemaName() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional string schemaName = 8; + * + *
      +       ** 变更数据的schemaname*
      +       * 
      + */ + public String getSchemaName() { + Object ref = schemaName_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + schemaName_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string schemaName = 8; + * + *
      +       ** 变更数据的schemaname*
      +       * 
      + */ + public com.google.protobuf.ByteString + getSchemaNameBytes() { + Object ref = schemaName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + schemaName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string schemaName = 8; + * + *
      +       ** 变更数据的schemaname*
      +       * 
      + */ + public Builder setSchemaName( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000080; + schemaName_ = value; + onChanged(); + return this; + } + /** + * optional string schemaName = 8; + * + *
      +       ** 变更数据的schemaname*
      +       * 
      + */ + public Builder clearSchemaName() { + bitField0_ = (bitField0_ & ~0x00000080); + schemaName_ = getDefaultInstance().getSchemaName(); + onChanged(); + return this; + } + /** + * optional string schemaName = 8; + * + *
      +       ** 变更数据的schemaname*
      +       * 
      + */ + public Builder setSchemaNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000080; + schemaName_ = value; + onChanged(); + return this; + } + + private Object tableName_ = ""; + /** + * optional string tableName = 9; + * + *
      +       **变更数据的tablename*
      +       * 
      + */ + public boolean hasTableName() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional string tableName = 9; + * + *
      +       **变更数据的tablename*
      +       * 
      + */ + public String getTableName() { + Object ref = tableName_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + tableName_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string tableName = 9; + * + *
      +       **变更数据的tablename*
      +       * 
      + */ + public com.google.protobuf.ByteString + getTableNameBytes() { + Object ref = tableName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + tableName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string tableName = 9; + * + *
      +       **变更数据的tablename*
      +       * 
      + */ + public Builder setTableName( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000100; + tableName_ = value; + onChanged(); + return this; + } + /** + * optional string tableName = 9; + * + *
      +       **变更数据的tablename*
      +       * 
      + */ + public Builder clearTableName() { + bitField0_ = (bitField0_ & ~0x00000100); + tableName_ = getDefaultInstance().getTableName(); + onChanged(); + return this; + } + /** + * optional string tableName = 9; + * + *
      +       **变更数据的tablename*
      +       * 
      + */ + public Builder setTableNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000100; + tableName_ = value; + onChanged(); + return this; + } + + private long eventLength_ ; + /** + * optional int64 eventLength = 10; + * + *
      +       **每个event的长度*
      +       * 
      + */ + public boolean hasEventLength() { + return ((bitField0_ & 0x00000200) == 0x00000200); + } + /** + * optional int64 eventLength = 10; + * + *
      +       **每个event的长度*
      +       * 
      + */ + public long getEventLength() { + return eventLength_; + } + /** + * optional int64 eventLength = 10; + * + *
      +       **每个event的长度*
      +       * 
      + */ + public Builder setEventLength(long value) { + bitField0_ |= 0x00000200; + eventLength_ = value; + onChanged(); + return this; + } + /** + * optional int64 eventLength = 10; + * + *
      +       **每个event的长度*
      +       * 
      + */ + public Builder clearEventLength() { + bitField0_ = (bitField0_ & ~0x00000200); + eventLength_ = 0L; + onChanged(); + return this; + } + + private EventType eventType_ = EventType.UPDATE; + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public boolean hasEventType() { + return ((bitField0_ & 0x00000400) == 0x00000400); + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public EventType getEventType() { + return eventType_; + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public Builder setEventType(EventType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000400; + eventType_ = value; + onChanged(); + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public Builder clearEventType() { + bitField0_ = (bitField0_ & ~0x00000400); + eventType_ = EventType.UPDATE; + onChanged(); + return this; + } + + private java.util.List props_ = + java.util.Collections.emptyList(); + private void ensurePropsIsMutable() { + if (!((bitField0_ & 0x00000800) == 0x00000800)) { + props_ = new java.util.ArrayList(props_); + bitField0_ |= 0x00000800; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List getPropsList() { + if (propsBuilder_ == null) { + return java.util.Collections.unmodifiableList(props_); + } else { + return propsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public int getPropsCount() { + if (propsBuilder_ == null) { + return props_.size(); + } else { + return propsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair getProps(int index) { + if (propsBuilder_ == null) { + return props_.get(index); + } else { + return propsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.set(index, value); + onChanged(); + } else { + propsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.set(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps(Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(value); + onChanged(); + } else { + propsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(index, value); + onChanged(); + } else { + propsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addAllProps( + Iterable values) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, props_); + onChanged(); + } else { + propsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder clearProps() { + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000800); + onChanged(); + } else { + propsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder removeProps(int index) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.remove(index); + onChanged(); + } else { + propsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder getPropsBuilder( + int index) { + return getPropsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + if (propsBuilder_ == null) { + return props_.get(index); } else { + return propsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsOrBuilderList() { + if (propsBuilder_ != null) { + return propsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(props_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder() { + return getPropsFieldBuilder().addBuilder( + Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder( + int index) { + return getPropsFieldBuilder().addBuilder( + index, Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsBuilderList() { + return getPropsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> + getPropsFieldBuilder() { + if (propsBuilder_ == null) { + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder>( + props_, + ((bitField0_ & 0x00000800) == 0x00000800), + getParentForChildren(), + isClean()); + props_ = null; + } + return propsBuilder_; + } + + private Object gtid_ = ""; + /** + * optional string gtid = 13; + * + *
      +       **当前事务的gitd*
      +       * 
      + */ + public boolean hasGtid() { + return ((bitField0_ & 0x00001000) == 0x00001000); + } + /** + * optional string gtid = 13; + * + *
      +       **当前事务的gitd*
      +       * 
      + */ + public String getGtid() { + Object ref = gtid_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + gtid_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string gtid = 13; + * + *
      +       **当前事务的gitd*
      +       * 
      + */ + public com.google.protobuf.ByteString + getGtidBytes() { + Object ref = gtid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + gtid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string gtid = 13; + * + *
      +       **当前事务的gitd*
      +       * 
      + */ + public Builder setGtid( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00001000; + gtid_ = value; + onChanged(); + return this; + } + /** + * optional string gtid = 13; + * + *
      +       **当前事务的gitd*
      +       * 
      + */ + public Builder clearGtid() { + bitField0_ = (bitField0_ & ~0x00001000); + gtid_ = getDefaultInstance().getGtid(); + onChanged(); + return this; + } + /** + * optional string gtid = 13; + * + *
      +       **当前事务的gitd*
      +       * 
      + */ + public Builder setGtidBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00001000; + gtid_ = value; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Header) } - public interface ColumnOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Column) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int32 index = 1; - * - *
      -         * *字段下标*
      -         * 
      - */ - boolean hasIndex(); - - /** - * optional int32 index = 1; - * - *
      -         * *字段下标*
      -         * 
      - */ - int getIndex(); - - /** - * optional int32 sqlType = 2; - * - *
      -         * *字段java中类型*
      -         * 
      - */ - boolean hasSqlType(); - - /** - * optional int32 sqlType = 2; - * - *
      -         * *字段java中类型*
      -         * 
      - */ - int getSqlType(); - - /** - * optional string name = 3; - * - *
      -         * *字段名称(忽略大小写),在mysql中是没有的*
      -         * 
      - */ - boolean hasName(); - - /** - * optional string name = 3; - * - *
      -         * *字段名称(忽略大小写),在mysql中是没有的*
      -         * 
      - */ - java.lang.String getName(); - - /** - * optional string name = 3; - * - *
      -         * *字段名称(忽略大小写),在mysql中是没有的*
      -         * 
      - */ - com.google.protobuf.ByteString getNameBytes(); - - /** - * optional bool isKey = 4; - * - *
      -         * *是否是主键*
      -         * 
      - */ - boolean hasIsKey(); - - /** - * optional bool isKey = 4; - * - *
      -         * *是否是主键*
      -         * 
      - */ - boolean getIsKey(); - - /** - * optional bool updated = 5; - * - *
      -         * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -         * 
      - */ - boolean hasUpdated(); - - /** - * optional bool updated = 5; - * - *
      -         * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -         * 
      - */ - boolean getUpdated(); - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -         * * 标识是否为空  *
      -         * 
      - */ - boolean hasIsNull(); - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -         * * 标识是否为空  *
      -         * 
      - */ - boolean getIsNull(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - int getPropsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index); - - /** - * optional string value = 8; - * - *
      -         * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -         * 
      - */ - boolean hasValue(); - - /** - * optional string value = 8; - * - *
      -         * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -         * 
      - */ - java.lang.String getValue(); - - /** - * optional string value = 8; - * - *
      -         * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -         * 
      - */ - com.google.protobuf.ByteString getValueBytes(); - - /** - * optional int32 length = 9; - * - *
      -         * * 对应数据对象原始长度 *
      -         * 
      - */ - boolean hasLength(); - - /** - * optional int32 length = 9; - * - *
      -         * * 对应数据对象原始长度 *
      -         * 
      - */ - int getLength(); - - /** - * optional string mysqlType = 10; - * - *
      -         * *字段mysql类型*
      -         * 
      - */ - boolean hasMysqlType(); - - /** - * optional string mysqlType = 10; - * - *
      -         * *字段mysql类型*
      -         * 
      - */ - java.lang.String getMysqlType(); - - /** - * optional string mysqlType = 10; - * - *
      -         * *字段mysql类型*
      -         * 
      - */ - com.google.protobuf.ByteString getMysqlTypeBytes(); + static { + defaultInstance = new Header(true); + defaultInstance.initFields(); } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Header) + } + + public interface ColumnOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Column) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int32 index = 1; + * + *
      +     **字段下标*
      +     * 
      + */ + boolean hasIndex(); + /** + * optional int32 index = 1; + * + *
      +     **字段下标*
      +     * 
      + */ + int getIndex(); + + /** + * optional int32 sqlType = 2; + * + *
      +     **字段java中类型*
      +     * 
      + */ + boolean hasSqlType(); + /** + * optional int32 sqlType = 2; + * + *
      +     **字段java中类型*
      +     * 
      + */ + int getSqlType(); + + /** + * optional string name = 3; + * + *
      +     **字段名称(忽略大小写),在mysql中是没有的*
      +     * 
      + */ + boolean hasName(); + /** + * optional string name = 3; + * + *
      +     **字段名称(忽略大小写),在mysql中是没有的*
      +     * 
      + */ + String getName(); + /** + * optional string name = 3; + * + *
      +     **字段名称(忽略大小写),在mysql中是没有的*
      +     * 
      + */ + com.google.protobuf.ByteString + getNameBytes(); + + /** + * optional bool isKey = 4; + * + *
      +     **是否是主键*
      +     * 
      + */ + boolean hasIsKey(); + /** + * optional bool isKey = 4; + * + *
      +     **是否是主键*
      +     * 
      + */ + boolean getIsKey(); + + /** + * optional bool updated = 5; + * + *
      +     **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +     * 
      + */ + boolean hasUpdated(); + /** + * optional bool updated = 5; + * + *
      +     **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +     * 
      + */ + boolean getUpdated(); + + /** + * optional bool isNull = 6 [default = false]; + * + *
      +     ** 标识是否为空  *
      +     * 
      + */ + boolean hasIsNull(); + /** + * optional bool isNull = 6 [default = false]; + * + *
      +     ** 标识是否为空  *
      +     * 
      + */ + boolean getIsNull(); + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + Pair getProps(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + int getPropsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + PairOrBuilder getPropsOrBuilder(int index); + + /** + * optional string value = 8; + * + *
      +     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +     * 
      + */ + boolean hasValue(); + /** + * optional string value = 8; + * + *
      +     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +     * 
      + */ + String getValue(); + /** + * optional string value = 8; + * + *
      +     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +     * 
      + */ + com.google.protobuf.ByteString + getValueBytes(); + + /** + * optional int32 length = 9; + * + *
      +     ** 对应数据对象原始长度 *
      +     * 
      + */ + boolean hasLength(); + /** + * optional int32 length = 9; + * + *
      +     ** 对应数据对象原始长度 *
      +     * 
      + */ + int getLength(); + + /** + * optional string mysqlType = 10; + * + *
      +     **字段mysql类型*
      +     * 
      + */ + boolean hasMysqlType(); + /** + * optional string mysqlType = 10; + * + *
      +     **字段mysql类型*
      +     * 
      + */ + String getMysqlType(); + /** + * optional string mysqlType = 10; + * + *
      +     **字段mysql类型*
      +     * 
      + */ + com.google.protobuf.ByteString + getMysqlTypeBytes(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.Column} + * + *
      +   **每个字段的数据结构*
      +   * 
      + */ + public static final class Column extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Column) + ColumnOrBuilder { + // Use Column.newBuilder() to construct. + private Column(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private Column(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final Column defaultInstance; + public static Column getDefaultInstance() { + return defaultInstance; + } + + public Column getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Column( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + index_ = input.readInt32(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + sqlType_ = input.readInt32(); + break; + } + case 26: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000004; + name_ = bs; + break; + } + case 32: { + bitField0_ |= 0x00000008; + isKey_ = input.readBool(); + break; + } + case 40: { + bitField0_ |= 0x00000010; + updated_ = input.readBool(); + break; + } + case 48: { + bitField0_ |= 0x00000020; + isNull_ = input.readBool(); + break; + } + case 58: { + if (!((mutable_bitField0_ & 0x00000040) == 0x00000040)) { + props_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000040; + } + props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + break; + } + case 66: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000040; + value_ = bs; + break; + } + case 72: { + bitField0_ |= 0x00000080; + length_ = input.readInt32(); + break; + } + case 82: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000100; + mysqlType_ = bs; + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000040) == 0x00000040)) { + props_ = java.util.Collections.unmodifiableList(props_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Column.class, Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public Column parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Column(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + public static final int INDEX_FIELD_NUMBER = 1; + private int index_; + /** + * optional int32 index = 1; + * + *
      +     **字段下标*
      +     * 
      + */ + public boolean hasIndex() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 index = 1; + * + *
      +     **字段下标*
      +     * 
      + */ + public int getIndex() { + return index_; + } + + public static final int SQLTYPE_FIELD_NUMBER = 2; + private int sqlType_; + /** + * optional int32 sqlType = 2; + * + *
      +     **字段java中类型*
      +     * 
      + */ + public boolean hasSqlType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int32 sqlType = 2; + * + *
      +     **字段java中类型*
      +     * 
      + */ + public int getSqlType() { + return sqlType_; + } + + public static final int NAME_FIELD_NUMBER = 3; + private Object name_; + /** + * optional string name = 3; + * + *
      +     **字段名称(忽略大小写),在mysql中是没有的*
      +     * 
      + */ + public boolean hasName() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string name = 3; + * + *
      +     **字段名称(忽略大小写),在mysql中是没有的*
      +     * 
      + */ + public String getName() { + Object ref = name_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + name_ = s; + } + return s; + } + } + /** + * optional string name = 3; + * + *
      +     **字段名称(忽略大小写),在mysql中是没有的*
      +     * 
      + */ + public com.google.protobuf.ByteString + getNameBytes() { + Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ISKEY_FIELD_NUMBER = 4; + private boolean isKey_; + /** + * optional bool isKey = 4; + * + *
      +     **是否是主键*
      +     * 
      + */ + public boolean hasIsKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional bool isKey = 4; + * + *
      +     **是否是主键*
      +     * 
      + */ + public boolean getIsKey() { + return isKey_; + } + + public static final int UPDATED_FIELD_NUMBER = 5; + private boolean updated_; + /** + * optional bool updated = 5; + * + *
      +     **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +     * 
      + */ + public boolean hasUpdated() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional bool updated = 5; + * + *
      +     **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +     * 
      + */ + public boolean getUpdated() { + return updated_; + } + + public static final int ISNULL_FIELD_NUMBER = 6; + private boolean isNull_; + /** + * optional bool isNull = 6 [default = false]; + * + *
      +     ** 标识是否为空  *
      +     * 
      + */ + public boolean hasIsNull() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bool isNull = 6 [default = false]; + * + *
      +     ** 标识是否为空  *
      +     * 
      + */ + public boolean getIsNull() { + return isNull_; + } + + public static final int PROPS_FIELD_NUMBER = 7; + private java.util.List props_; + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List getPropsList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List + getPropsOrBuilderList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + public int getPropsCount() { + return props_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + public Pair getProps(int index) { + return props_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +     **预留扩展*
      +     * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + return props_.get(index); + } + + public static final int VALUE_FIELD_NUMBER = 8; + private Object value_; + /** + * optional string value = 8; + * + *
      +     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +     * 
      + */ + public boolean hasValue() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional string value = 8; + * + *
      +     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +     * 
      + */ + public String getValue() { + Object ref = value_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + value_ = s; + } + return s; + } + } + /** + * optional string value = 8; + * + *
      +     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +     * 
      + */ + public com.google.protobuf.ByteString + getValueBytes() { + Object ref = value_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + value_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int LENGTH_FIELD_NUMBER = 9; + private int length_; + /** + * optional int32 length = 9; + * + *
      +     ** 对应数据对象原始长度 *
      +     * 
      + */ + public boolean hasLength() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional int32 length = 9; + * + *
      +     ** 对应数据对象原始长度 *
      +     * 
      + */ + public int getLength() { + return length_; + } + + public static final int MYSQLTYPE_FIELD_NUMBER = 10; + private Object mysqlType_; + /** + * optional string mysqlType = 10; + * + *
      +     **字段mysql类型*
      +     * 
      + */ + public boolean hasMysqlType() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional string mysqlType = 10; + * + *
      +     **字段mysql类型*
      +     * 
      + */ + public String getMysqlType() { + Object ref = mysqlType_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + mysqlType_ = s; + } + return s; + } + } + /** + * optional string mysqlType = 10; + * + *
      +     **字段mysql类型*
      +     * 
      + */ + public com.google.protobuf.ByteString + getMysqlTypeBytes() { + Object ref = mysqlType_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + mysqlType_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + index_ = 0; + sqlType_ = 0; + name_ = ""; + isKey_ = false; + updated_ = false; + isNull_ = false; + props_ = java.util.Collections.emptyList(); + value_ = ""; + length_ = 0; + mysqlType_ = ""; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt32(1, index_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeInt32(2, sqlType_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, getNameBytes()); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBool(4, isKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBool(5, updated_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBool(6, isNull_); + } + for (int i = 0; i < props_.size(); i++) { + output.writeMessage(7, props_.get(i)); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + output.writeBytes(8, getValueBytes()); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + output.writeInt32(9, length_); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + output.writeBytes(10, getMysqlTypeBytes()); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, index_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(2, sqlType_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, getNameBytes()); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(4, isKey_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(5, updated_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(6, isNull_); + } + for (int i = 0; i < props_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(7, props_.get(i)); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(8, getValueBytes()); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(9, length_); + } + if (((bitField0_ & 0x00000100) == 0x00000100)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(10, getMysqlTypeBytes()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static Column parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Column parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Column parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Column parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Column parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Column parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static Column parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static Column parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static Column parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Column parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(Column prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Column} * *
      -     * *每个字段的数据结构*
      +     **每个字段的数据结构*
            * 
      */ - public static final class Column extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Column) - ColumnOrBuilder { - - // Use Column.newBuilder() to construct. - private Column(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Column(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Column defaultInstance; - - public static Column getDefaultInstance() { - return defaultInstance; - } - - public Column getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Column(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - index_ = input.readInt32(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - sqlType_ = input.readInt32(); - break; - } - case 26: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000004; - name_ = bs; - break; - } - case 32: { - bitField0_ |= 0x00000008; - isKey_ = input.readBool(); - break; - } - case 40: { - bitField0_ |= 0x00000010; - updated_ = input.readBool(); - break; - } - case 48: { - bitField0_ |= 0x00000020; - isNull_ = input.readBool(); - break; - } - case 58: { - if (!((mutable_bitField0_ & 0x00000040) == 0x00000040)) { - props_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000040; - } - props_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, - extensionRegistry)); - break; - } - case 66: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000040; - value_ = bs; - break; - } - case 72: { - bitField0_ |= 0x00000080; - length_ = input.readInt32(); - break; - } - case 82: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000100; - mysqlType_ = bs; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000040) == 0x00000040)) { - props_ = java.util.Collections.unmodifiableList(props_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Column.class, - com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Column parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Column(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int INDEX_FIELD_NUMBER = 1; - private int index_; - - /** - * optional int32 index = 1; - * - *
      -         * *字段下标*
      -         * 
      - */ - public boolean hasIndex() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 index = 1; - * - *
      -         * *字段下标*
      -         * 
      - */ - public int getIndex() { - return index_; - } - - public static final int SQLTYPE_FIELD_NUMBER = 2; - private int sqlType_; - - /** - * optional int32 sqlType = 2; - * - *
      -         * *字段java中类型*
      -         * 
      - */ - public boolean hasSqlType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int32 sqlType = 2; - * - *
      -         * *字段java中类型*
      -         * 
      - */ - public int getSqlType() { - return sqlType_; - } - - public static final int NAME_FIELD_NUMBER = 3; - private java.lang.Object name_; - - /** - * optional string name = 3; - * - *
      -         * *字段名称(忽略大小写),在mysql中是没有的*
      -         * 
      - */ - public boolean hasName() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional string name = 3; - * - *
      -         * *字段名称(忽略大小写),在mysql中是没有的*
      -         * 
      - */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } - } - - /** - * optional string name = 3; - * - *
      -         * *字段名称(忽略大小写),在mysql中是没有的*
      -         * 
      - */ - public com.google.protobuf.ByteString getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int ISKEY_FIELD_NUMBER = 4; - private boolean isKey_; - - /** - * optional bool isKey = 4; - * - *
      -         * *是否是主键*
      -         * 
      - */ - public boolean hasIsKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional bool isKey = 4; - * - *
      -         * *是否是主键*
      -         * 
      - */ - public boolean getIsKey() { - return isKey_; - } - - public static final int UPDATED_FIELD_NUMBER = 5; - private boolean updated_; - - /** - * optional bool updated = 5; - * - *
      -         * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -         * 
      - */ - public boolean hasUpdated() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional bool updated = 5; - * - *
      -         * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -         * 
      - */ - public boolean getUpdated() { - return updated_; - } - - public static final int ISNULL_FIELD_NUMBER = 6; - private boolean isNull_; - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -         * * 标识是否为空  *
      -         * 
      - */ - public boolean hasIsNull() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -         * * 标识是否为空  *
      -         * 
      - */ - public boolean getIsNull() { - return isNull_; - } - - public static final int PROPS_FIELD_NUMBER = 7; - private java.util.List props_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsOrBuilderList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public int getPropsCount() { - return props_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - return props_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - return props_.get(index); - } - - public static final int VALUE_FIELD_NUMBER = 8; - private java.lang.Object value_; - - /** - * optional string value = 8; - * - *
      -         * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -         * 
      - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - - /** - * optional string value = 8; - * - *
      -         * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -         * 
      - */ - public java.lang.String getValue() { - java.lang.Object ref = value_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } - return s; - } - } - - /** - * optional string value = 8; - * - *
      -         * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -         * 
      - */ - public com.google.protobuf.ByteString getValueBytes() { - java.lang.Object ref = value_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - value_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int LENGTH_FIELD_NUMBER = 9; - private int length_; - - /** - * optional int32 length = 9; - * - *
      -         * * 对应数据对象原始长度 *
      -         * 
      - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - - /** - * optional int32 length = 9; - * - *
      -         * * 对应数据对象原始长度 *
      -         * 
      - */ - public int getLength() { - return length_; - } - - public static final int MYSQLTYPE_FIELD_NUMBER = 10; - private java.lang.Object mysqlType_; - - /** - * optional string mysqlType = 10; - * - *
      -         * *字段mysql类型*
      -         * 
      - */ - public boolean hasMysqlType() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - - /** - * optional string mysqlType = 10; - * - *
      -         * *字段mysql类型*
      -         * 
      - */ - public java.lang.String getMysqlType() { - java.lang.Object ref = mysqlType_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - mysqlType_ = s; - } - return s; - } - } - - /** - * optional string mysqlType = 10; - * - *
      -         * *字段mysql类型*
      -         * 
      - */ - public com.google.protobuf.ByteString getMysqlTypeBytes() { - java.lang.Object ref = mysqlType_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - mysqlType_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - index_ = 0; - sqlType_ = 0; - name_ = ""; - isKey_ = false; - updated_ = false; - isNull_ = false; - props_ = java.util.Collections.emptyList(); - value_ = ""; - length_ = 0; - mysqlType_ = ""; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt32(1, index_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeInt32(2, sqlType_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(3, getNameBytes()); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBool(4, isKey_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBool(5, updated_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBool(6, isNull_); - } - for (int i = 0; i < props_.size(); i++) { - output.writeMessage(7, props_.get(i)); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeBytes(8, getValueBytes()); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - output.writeInt32(9, length_); - } - if (((bitField0_ & 0x00000100) == 0x00000100)) { - output.writeBytes(10, getMysqlTypeBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(1, index_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(2, sqlType_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(3, getNameBytes()); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream.computeBoolSize(4, isKey_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream.computeBoolSize(5, updated_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - size += com.google.protobuf.CodedOutputStream.computeBoolSize(6, isNull_); - } - for (int i = 0; i < props_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(7, props_.get(i)); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(8, getValueBytes()); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(9, length_); - } - if (((bitField0_ & 0x00000100) == 0x00000100)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(10, getMysqlTypeBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Column prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Column} - * - *
      -         * *每个字段的数据结构*
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Column) - com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder { - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Column.class, - com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder.class); - } - - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.Column.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getPropsFieldBuilder(); - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - index_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); - sqlType_ = 0; - bitField0_ = (bitField0_ & ~0x00000002); - name_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); - isKey_ = false; - bitField0_ = (bitField0_ & ~0x00000008); - updated_ = false; - bitField0_ = (bitField0_ & ~0x00000010); - isNull_ = false; - bitField0_ = (bitField0_ & ~0x00000020); - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000040); - } else { - propsBuilder_.clear(); - } - value_ = ""; - bitField0_ = (bitField0_ & ~0x00000080); - length_ = 0; - bitField0_ = (bitField0_ & ~0x00000100); - mysqlType_ = ""; - bitField0_ = (bitField0_ & ~0x00000200); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Column getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Column build() { - com.alibaba.otter.canal.protocol.CanalEntry.Column result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Column buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.Column result = new com.alibaba.otter.canal.protocol.CanalEntry.Column(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.index_ = index_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.sqlType_ = sqlType_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.name_ = name_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.isKey_ = isKey_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - result.updated_ = updated_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } - result.isNull_ = isNull_; - if (propsBuilder_ == null) { - if (((bitField0_ & 0x00000040) == 0x00000040)) { - props_ = java.util.Collections.unmodifiableList(props_); - bitField0_ = (bitField0_ & ~0x00000040); - } - result.props_ = props_; - } else { - result.props_ = propsBuilder_.build(); - } - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000040; - } - result.value_ = value_; - if (((from_bitField0_ & 0x00000100) == 0x00000100)) { - to_bitField0_ |= 0x00000080; - } - result.length_ = length_; - if (((from_bitField0_ & 0x00000200) == 0x00000200)) { - to_bitField0_ |= 0x00000100; - } - result.mysqlType_ = mysqlType_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Column) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Column) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Column other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()) return this; - if (other.hasIndex()) { - setIndex(other.getIndex()); - } - if (other.hasSqlType()) { - setSqlType(other.getSqlType()); - } - if (other.hasName()) { - bitField0_ |= 0x00000004; - name_ = other.name_; - onChanged(); - } - if (other.hasIsKey()) { - setIsKey(other.getIsKey()); - } - if (other.hasUpdated()) { - setUpdated(other.getUpdated()); - } - if (other.hasIsNull()) { - setIsNull(other.getIsNull()); - } - if (propsBuilder_ == null) { - if (!other.props_.isEmpty()) { - if (props_.isEmpty()) { - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000040); - } else { - ensurePropsIsMutable(); - props_.addAll(other.props_); - } - onChanged(); - } - } else { - if (!other.props_.isEmpty()) { - if (propsBuilder_.isEmpty()) { - propsBuilder_.dispose(); - propsBuilder_ = null; - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000040); - propsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; - } else { - propsBuilder_.addAllMessages(other.props_); - } - } - } - if (other.hasValue()) { - bitField0_ |= 0x00000080; - value_ = other.value_; - onChanged(); - } - if (other.hasLength()) { - setLength(other.getLength()); - } - if (other.hasMysqlType()) { - bitField0_ |= 0x00000200; - mysqlType_ = other.mysqlType_; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.Column parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Column) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private int index_; - - /** - * optional int32 index = 1; - * - *
      -             * *字段下标*
      -             * 
      - */ - public boolean hasIndex() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 index = 1; - * - *
      -             * *字段下标*
      -             * 
      - */ - public int getIndex() { - return index_; - } - - /** - * optional int32 index = 1; - * - *
      -             * *字段下标*
      -             * 
      - */ - public Builder setIndex(int value) { - bitField0_ |= 0x00000001; - index_ = value; - onChanged(); - return this; - } - - /** - * optional int32 index = 1; - * - *
      -             * *字段下标*
      -             * 
      - */ - public Builder clearIndex() { - bitField0_ = (bitField0_ & ~0x00000001); - index_ = 0; - onChanged(); - return this; - } - - private int sqlType_; - - /** - * optional int32 sqlType = 2; - * - *
      -             * *字段java中类型*
      -             * 
      - */ - public boolean hasSqlType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int32 sqlType = 2; - * - *
      -             * *字段java中类型*
      -             * 
      - */ - public int getSqlType() { - return sqlType_; - } - - /** - * optional int32 sqlType = 2; - * - *
      -             * *字段java中类型*
      -             * 
      - */ - public Builder setSqlType(int value) { - bitField0_ |= 0x00000002; - sqlType_ = value; - onChanged(); - return this; - } - - /** - * optional int32 sqlType = 2; - * - *
      -             * *字段java中类型*
      -             * 
      - */ - public Builder clearSqlType() { - bitField0_ = (bitField0_ & ~0x00000002); - sqlType_ = 0; - onChanged(); - return this; - } - - private java.lang.Object name_ = ""; - - /** - * optional string name = 3; - * - *
      -             * *字段名称(忽略大小写),在mysql中是没有的*
      -             * 
      - */ - public boolean hasName() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional string name = 3; - * - *
      -             * *字段名称(忽略大小写),在mysql中是没有的*
      -             * 
      - */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string name = 3; - * - *
      -             * *字段名称(忽略大小写),在mysql中是没有的*
      -             * 
      - */ - public com.google.protobuf.ByteString getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string name = 3; - * - *
      -             * *字段名称(忽略大小写),在mysql中是没有的*
      -             * 
      - */ - public Builder setName(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - name_ = value; - onChanged(); - return this; - } - - /** - * optional string name = 3; - * - *
      -             * *字段名称(忽略大小写),在mysql中是没有的*
      -             * 
      - */ - public Builder clearName() { - bitField0_ = (bitField0_ & ~0x00000004); - name_ = getDefaultInstance().getName(); - onChanged(); - return this; - } - - /** - * optional string name = 3; - * - *
      -             * *字段名称(忽略大小写),在mysql中是没有的*
      -             * 
      - */ - public Builder setNameBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - name_ = value; - onChanged(); - return this; - } - - private boolean isKey_; - - /** - * optional bool isKey = 4; - * - *
      -             * *是否是主键*
      -             * 
      - */ - public boolean hasIsKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional bool isKey = 4; - * - *
      -             * *是否是主键*
      -             * 
      - */ - public boolean getIsKey() { - return isKey_; - } - - /** - * optional bool isKey = 4; - * - *
      -             * *是否是主键*
      -             * 
      - */ - public Builder setIsKey(boolean value) { - bitField0_ |= 0x00000008; - isKey_ = value; - onChanged(); - return this; - } - - /** - * optional bool isKey = 4; - * - *
      -             * *是否是主键*
      -             * 
      - */ - public Builder clearIsKey() { - bitField0_ = (bitField0_ & ~0x00000008); - isKey_ = false; - onChanged(); - return this; - } - - private boolean updated_; - - /** - * optional bool updated = 5; - * - *
      -             * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -             * 
      - */ - public boolean hasUpdated() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional bool updated = 5; - * - *
      -             * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -             * 
      - */ - public boolean getUpdated() { - return updated_; - } - - /** - * optional bool updated = 5; - * - *
      -             * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -             * 
      - */ - public Builder setUpdated(boolean value) { - bitField0_ |= 0x00000010; - updated_ = value; - onChanged(); - return this; - } - - /** - * optional bool updated = 5; - * - *
      -             * *如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -             * 
      - */ - public Builder clearUpdated() { - bitField0_ = (bitField0_ & ~0x00000010); - updated_ = false; - onChanged(); - return this; - } - - private boolean isNull_; - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -             * * 标识是否为空  *
      -             * 
      - */ - public boolean hasIsNull() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -             * * 标识是否为空  *
      -             * 
      - */ - public boolean getIsNull() { - return isNull_; - } - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -             * * 标识是否为空  *
      -             * 
      - */ - public Builder setIsNull(boolean value) { - bitField0_ |= 0x00000020; - isNull_ = value; - onChanged(); - return this; - } - - /** - * optional bool isNull = 6 [default = false]; - * - *
      -             * * 标识是否为空  *
      -             * 
      - */ - public Builder clearIsNull() { - bitField0_ = (bitField0_ & ~0x00000020); - isNull_ = false; - onChanged(); - return this; - } - - private java.util.List props_ = java.util.Collections.emptyList(); - - private void ensurePropsIsMutable() { - if (!((bitField0_ & 0x00000040) == 0x00000040)) { - props_ = new java.util.ArrayList(props_); - bitField0_ |= 0x00000040; - } - } - - private com.google.protobuf.RepeatedFieldBuilder propsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsList() { - if (propsBuilder_ == null) { - return java.util.Collections.unmodifiableList(props_); - } else { - return propsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public int getPropsCount() { - if (propsBuilder_ == null) { - return props_.size(); - } else { - return propsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.set(index, value); - onChanged(); - } else { - propsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.set(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(value); - onChanged(); - } else { - propsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(index, value); - onChanged(); - } else { - propsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addAllProps(java.lang.Iterable values) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, props_); - onChanged(); - } else { - propsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder clearProps() { - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000040); - onChanged(); - } else { - propsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder removeProps(int index) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.remove(index); - onChanged(); - } else { - propsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder(int index) { - return getPropsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsOrBuilderList() { - if (propsBuilder_ != null) { - return propsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(props_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { - return getPropsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder(int index) { - return getPropsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsBuilderList() { - return getPropsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getPropsFieldBuilder() { - if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(props_, - ((bitField0_ & 0x00000040) == 0x00000040), - getParentForChildren(), - isClean()); - props_ = null; - } - return propsBuilder_; - } - - private java.lang.Object value_ = ""; - - /** - * optional string value = 8; - * - *
      -             * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -             * 
      - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - - /** - * optional string value = 8; - * - *
      -             * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -             * 
      - */ - public java.lang.String getValue() { - java.lang.Object ref = value_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string value = 8; - * - *
      -             * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -             * 
      - */ - public com.google.protobuf.ByteString getValueBytes() { - java.lang.Object ref = value_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - value_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string value = 8; - * - *
      -             * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -             * 
      - */ - public Builder setValue(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000080; - value_ = value; - onChanged(); - return this; - } - - /** - * optional string value = 8; - * - *
      -             * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -             * 
      - */ - public Builder clearValue() { - bitField0_ = (bitField0_ & ~0x00000080); - value_ = getDefaultInstance().getValue(); - onChanged(); - return this; - } - - /** - * optional string value = 8; - * - *
      -             * * 字段值,timestamp,Datetime是一个时间格式的文本 *
      -             * 
      - */ - public Builder setValueBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000080; - value_ = value; - onChanged(); - return this; - } - - private int length_; - - /** - * optional int32 length = 9; - * - *
      -             * * 对应数据对象原始长度 *
      -             * 
      - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - - /** - * optional int32 length = 9; - * - *
      -             * * 对应数据对象原始长度 *
      -             * 
      - */ - public int getLength() { - return length_; - } - - /** - * optional int32 length = 9; - * - *
      -             * * 对应数据对象原始长度 *
      -             * 
      - */ - public Builder setLength(int value) { - bitField0_ |= 0x00000100; - length_ = value; - onChanged(); - return this; - } - - /** - * optional int32 length = 9; - * - *
      -             * * 对应数据对象原始长度 *
      -             * 
      - */ - public Builder clearLength() { - bitField0_ = (bitField0_ & ~0x00000100); - length_ = 0; - onChanged(); - return this; - } - - private java.lang.Object mysqlType_ = ""; - - /** - * optional string mysqlType = 10; - * - *
      -             * *字段mysql类型*
      -             * 
      - */ - public boolean hasMysqlType() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - - /** - * optional string mysqlType = 10; - * - *
      -             * *字段mysql类型*
      -             * 
      - */ - public java.lang.String getMysqlType() { - java.lang.Object ref = mysqlType_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - mysqlType_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string mysqlType = 10; - * - *
      -             * *字段mysql类型*
      -             * 
      - */ - public com.google.protobuf.ByteString getMysqlTypeBytes() { - java.lang.Object ref = mysqlType_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - mysqlType_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string mysqlType = 10; - * - *
      -             * *字段mysql类型*
      -             * 
      - */ - public Builder setMysqlType(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000200; - mysqlType_ = value; - onChanged(); - return this; - } - - /** - * optional string mysqlType = 10; - * - *
      -             * *字段mysql类型*
      -             * 
      - */ - public Builder clearMysqlType() { - bitField0_ = (bitField0_ & ~0x00000200); - mysqlType_ = getDefaultInstance().getMysqlType(); - onChanged(); - return this; - } - - /** - * optional string mysqlType = 10; - * - *
      -             * *字段mysql类型*
      -             * 
      - */ - public Builder setMysqlTypeBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000200; - mysqlType_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Column) + ColumnOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Column.class, Builder.class); + } + + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Column.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getPropsFieldBuilder(); } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new Column(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + index_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + sqlType_ = 0; + bitField0_ = (bitField0_ & ~0x00000002); + name_ = ""; + bitField0_ = (bitField0_ & ~0x00000004); + isKey_ = false; + bitField0_ = (bitField0_ & ~0x00000008); + updated_ = false; + bitField0_ = (bitField0_ & ~0x00000010); + isNull_ = false; + bitField0_ = (bitField0_ & ~0x00000020); + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000040); + } else { + propsBuilder_.clear(); } + value_ = ""; + bitField0_ = (bitField0_ & ~0x00000080); + length_ = 0; + bitField0_ = (bitField0_ & ~0x00000100); + mysqlType_ = ""; + bitField0_ = (bitField0_ & ~0x00000200); + return this; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Column) + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; + } + + public Column getDefaultInstanceForType() { + return Column.getDefaultInstance(); + } + + public Column build() { + Column result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public Column buildPartial() { + Column result = new Column(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.index_ = index_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.sqlType_ = sqlType_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.name_ = name_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.isKey_ = isKey_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.updated_ = updated_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + result.isNull_ = isNull_; + if (propsBuilder_ == null) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { + props_ = java.util.Collections.unmodifiableList(props_); + bitField0_ = (bitField0_ & ~0x00000040); + } + result.props_ = props_; + } else { + result.props_ = propsBuilder_.build(); + } + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000040; + } + result.value_ = value_; + if (((from_bitField0_ & 0x00000100) == 0x00000100)) { + to_bitField0_ |= 0x00000080; + } + result.length_ = length_; + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000100; + } + result.mysqlType_ = mysqlType_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Column) { + return mergeFrom((Column)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Column other) { + if (other == Column.getDefaultInstance()) return this; + if (other.hasIndex()) { + setIndex(other.getIndex()); + } + if (other.hasSqlType()) { + setSqlType(other.getSqlType()); + } + if (other.hasName()) { + bitField0_ |= 0x00000004; + name_ = other.name_; + onChanged(); + } + if (other.hasIsKey()) { + setIsKey(other.getIsKey()); + } + if (other.hasUpdated()) { + setUpdated(other.getUpdated()); + } + if (other.hasIsNull()) { + setIsNull(other.getIsNull()); + } + if (propsBuilder_ == null) { + if (!other.props_.isEmpty()) { + if (props_.isEmpty()) { + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000040); + } else { + ensurePropsIsMutable(); + props_.addAll(other.props_); + } + onChanged(); + } + } else { + if (!other.props_.isEmpty()) { + if (propsBuilder_.isEmpty()) { + propsBuilder_.dispose(); + propsBuilder_ = null; + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000040); + propsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPropsFieldBuilder() : null; + } else { + propsBuilder_.addAllMessages(other.props_); + } + } + } + if (other.hasValue()) { + bitField0_ |= 0x00000080; + value_ = other.value_; + onChanged(); + } + if (other.hasLength()) { + setLength(other.getLength()); + } + if (other.hasMysqlType()) { + bitField0_ |= 0x00000200; + mysqlType_ = other.mysqlType_; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Column parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (Column) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int index_ ; + /** + * optional int32 index = 1; + * + *
      +       **字段下标*
      +       * 
      + */ + public boolean hasIndex() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 index = 1; + * + *
      +       **字段下标*
      +       * 
      + */ + public int getIndex() { + return index_; + } + /** + * optional int32 index = 1; + * + *
      +       **字段下标*
      +       * 
      + */ + public Builder setIndex(int value) { + bitField0_ |= 0x00000001; + index_ = value; + onChanged(); + return this; + } + /** + * optional int32 index = 1; + * + *
      +       **字段下标*
      +       * 
      + */ + public Builder clearIndex() { + bitField0_ = (bitField0_ & ~0x00000001); + index_ = 0; + onChanged(); + return this; + } + + private int sqlType_ ; + /** + * optional int32 sqlType = 2; + * + *
      +       **字段java中类型*
      +       * 
      + */ + public boolean hasSqlType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int32 sqlType = 2; + * + *
      +       **字段java中类型*
      +       * 
      + */ + public int getSqlType() { + return sqlType_; + } + /** + * optional int32 sqlType = 2; + * + *
      +       **字段java中类型*
      +       * 
      + */ + public Builder setSqlType(int value) { + bitField0_ |= 0x00000002; + sqlType_ = value; + onChanged(); + return this; + } + /** + * optional int32 sqlType = 2; + * + *
      +       **字段java中类型*
      +       * 
      + */ + public Builder clearSqlType() { + bitField0_ = (bitField0_ & ~0x00000002); + sqlType_ = 0; + onChanged(); + return this; + } + + private Object name_ = ""; + /** + * optional string name = 3; + * + *
      +       **字段名称(忽略大小写),在mysql中是没有的*
      +       * 
      + */ + public boolean hasName() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string name = 3; + * + *
      +       **字段名称(忽略大小写),在mysql中是没有的*
      +       * 
      + */ + public String getName() { + Object ref = name_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + name_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string name = 3; + * + *
      +       **字段名称(忽略大小写),在mysql中是没有的*
      +       * 
      + */ + public com.google.protobuf.ByteString + getNameBytes() { + Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string name = 3; + * + *
      +       **字段名称(忽略大小写),在mysql中是没有的*
      +       * 
      + */ + public Builder setName( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + name_ = value; + onChanged(); + return this; + } + /** + * optional string name = 3; + * + *
      +       **字段名称(忽略大小写),在mysql中是没有的*
      +       * 
      + */ + public Builder clearName() { + bitField0_ = (bitField0_ & ~0x00000004); + name_ = getDefaultInstance().getName(); + onChanged(); + return this; + } + /** + * optional string name = 3; + * + *
      +       **字段名称(忽略大小写),在mysql中是没有的*
      +       * 
      + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + name_ = value; + onChanged(); + return this; + } + + private boolean isKey_ ; + /** + * optional bool isKey = 4; + * + *
      +       **是否是主键*
      +       * 
      + */ + public boolean hasIsKey() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional bool isKey = 4; + * + *
      +       **是否是主键*
      +       * 
      + */ + public boolean getIsKey() { + return isKey_; + } + /** + * optional bool isKey = 4; + * + *
      +       **是否是主键*
      +       * 
      + */ + public Builder setIsKey(boolean value) { + bitField0_ |= 0x00000008; + isKey_ = value; + onChanged(); + return this; + } + /** + * optional bool isKey = 4; + * + *
      +       **是否是主键*
      +       * 
      + */ + public Builder clearIsKey() { + bitField0_ = (bitField0_ & ~0x00000008); + isKey_ = false; + onChanged(); + return this; + } + + private boolean updated_ ; + /** + * optional bool updated = 5; + * + *
      +       **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +       * 
      + */ + public boolean hasUpdated() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional bool updated = 5; + * + *
      +       **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +       * 
      + */ + public boolean getUpdated() { + return updated_; + } + /** + * optional bool updated = 5; + * + *
      +       **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +       * 
      + */ + public Builder setUpdated(boolean value) { + bitField0_ |= 0x00000010; + updated_ = value; + onChanged(); + return this; + } + /** + * optional bool updated = 5; + * + *
      +       **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      +       * 
      + */ + public Builder clearUpdated() { + bitField0_ = (bitField0_ & ~0x00000010); + updated_ = false; + onChanged(); + return this; + } + + private boolean isNull_ ; + /** + * optional bool isNull = 6 [default = false]; + * + *
      +       ** 标识是否为空  *
      +       * 
      + */ + public boolean hasIsNull() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bool isNull = 6 [default = false]; + * + *
      +       ** 标识是否为空  *
      +       * 
      + */ + public boolean getIsNull() { + return isNull_; + } + /** + * optional bool isNull = 6 [default = false]; + * + *
      +       ** 标识是否为空  *
      +       * 
      + */ + public Builder setIsNull(boolean value) { + bitField0_ |= 0x00000020; + isNull_ = value; + onChanged(); + return this; + } + /** + * optional bool isNull = 6 [default = false]; + * + *
      +       ** 标识是否为空  *
      +       * 
      + */ + public Builder clearIsNull() { + bitField0_ = (bitField0_ & ~0x00000020); + isNull_ = false; + onChanged(); + return this; + } + + private java.util.List props_ = + java.util.Collections.emptyList(); + private void ensurePropsIsMutable() { + if (!((bitField0_ & 0x00000040) == 0x00000040)) { + props_ = new java.util.ArrayList(props_); + bitField0_ |= 0x00000040; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List getPropsList() { + if (propsBuilder_ == null) { + return java.util.Collections.unmodifiableList(props_); + } else { + return propsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public int getPropsCount() { + if (propsBuilder_ == null) { + return props_.size(); + } else { + return propsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair getProps(int index) { + if (propsBuilder_ == null) { + return props_.get(index); + } else { + return propsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.set(index, value); + onChanged(); + } else { + propsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.set(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps(Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(value); + onChanged(); + } else { + propsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(index, value); + onChanged(); + } else { + propsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addAllProps( + Iterable values) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, props_); + onChanged(); + } else { + propsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder clearProps() { + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000040); + onChanged(); + } else { + propsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder removeProps(int index) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.remove(index); + onChanged(); + } else { + propsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder getPropsBuilder( + int index) { + return getPropsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + if (propsBuilder_ == null) { + return props_.get(index); } else { + return propsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsOrBuilderList() { + if (propsBuilder_ != null) { + return propsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(props_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder() { + return getPropsFieldBuilder().addBuilder( + Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder( + int index) { + return getPropsFieldBuilder().addBuilder( + index, Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsBuilderList() { + return getPropsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> + getPropsFieldBuilder() { + if (propsBuilder_ == null) { + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder>( + props_, + ((bitField0_ & 0x00000040) == 0x00000040), + getParentForChildren(), + isClean()); + props_ = null; + } + return propsBuilder_; + } + + private Object value_ = ""; + /** + * optional string value = 8; + * + *
      +       ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +       * 
      + */ + public boolean hasValue() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional string value = 8; + * + *
      +       ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +       * 
      + */ + public String getValue() { + Object ref = value_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + value_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string value = 8; + * + *
      +       ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +       * 
      + */ + public com.google.protobuf.ByteString + getValueBytes() { + Object ref = value_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + value_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string value = 8; + * + *
      +       ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +       * 
      + */ + public Builder setValue( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000080; + value_ = value; + onChanged(); + return this; + } + /** + * optional string value = 8; + * + *
      +       ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +       * 
      + */ + public Builder clearValue() { + bitField0_ = (bitField0_ & ~0x00000080); + value_ = getDefaultInstance().getValue(); + onChanged(); + return this; + } + /** + * optional string value = 8; + * + *
      +       ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      +       * 
      + */ + public Builder setValueBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000080; + value_ = value; + onChanged(); + return this; + } + + private int length_ ; + /** + * optional int32 length = 9; + * + *
      +       ** 对应数据对象原始长度 *
      +       * 
      + */ + public boolean hasLength() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional int32 length = 9; + * + *
      +       ** 对应数据对象原始长度 *
      +       * 
      + */ + public int getLength() { + return length_; + } + /** + * optional int32 length = 9; + * + *
      +       ** 对应数据对象原始长度 *
      +       * 
      + */ + public Builder setLength(int value) { + bitField0_ |= 0x00000100; + length_ = value; + onChanged(); + return this; + } + /** + * optional int32 length = 9; + * + *
      +       ** 对应数据对象原始长度 *
      +       * 
      + */ + public Builder clearLength() { + bitField0_ = (bitField0_ & ~0x00000100); + length_ = 0; + onChanged(); + return this; + } + + private Object mysqlType_ = ""; + /** + * optional string mysqlType = 10; + * + *
      +       **字段mysql类型*
      +       * 
      + */ + public boolean hasMysqlType() { + return ((bitField0_ & 0x00000200) == 0x00000200); + } + /** + * optional string mysqlType = 10; + * + *
      +       **字段mysql类型*
      +       * 
      + */ + public String getMysqlType() { + Object ref = mysqlType_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + mysqlType_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string mysqlType = 10; + * + *
      +       **字段mysql类型*
      +       * 
      + */ + public com.google.protobuf.ByteString + getMysqlTypeBytes() { + Object ref = mysqlType_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + mysqlType_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string mysqlType = 10; + * + *
      +       **字段mysql类型*
      +       * 
      + */ + public Builder setMysqlType( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000200; + mysqlType_ = value; + onChanged(); + return this; + } + /** + * optional string mysqlType = 10; + * + *
      +       **字段mysql类型*
      +       * 
      + */ + public Builder clearMysqlType() { + bitField0_ = (bitField0_ & ~0x00000200); + mysqlType_ = getDefaultInstance().getMysqlType(); + onChanged(); + return this; + } + /** + * optional string mysqlType = 10; + * + *
      +       **字段mysql类型*
      +       * 
      + */ + public Builder setMysqlTypeBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000200; + mysqlType_ = value; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Column) } - public interface RowDataOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.RowData) - com.google.protobuf.MessageOrBuilder { - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - java.util.List getBeforeColumnsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Column getBeforeColumns(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - int getBeforeColumnsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - java.util.List getBeforeColumnsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getBeforeColumnsOrBuilder(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - java.util.List getAfterColumnsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Column getAfterColumns(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - int getAfterColumnsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - java.util.List getAfterColumnsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getAfterColumnsOrBuilder(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - int getPropsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index); + static { + defaultInstance = new Column(true); + defaultInstance.initFields(); } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Column) + } + + public interface RowDataOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.RowData) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + java.util.List + getBeforeColumnsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + Column getBeforeColumns(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + int getBeforeColumnsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + java.util.List + getBeforeColumnsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + ColumnOrBuilder getBeforeColumnsOrBuilder(int index); + + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + java.util.List + getAfterColumnsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + Column getAfterColumns(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + int getAfterColumnsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + java.util.List + getAfterColumnsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + ColumnOrBuilder getAfterColumnsOrBuilder(int index); + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + Pair getProps(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + int getPropsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + PairOrBuilder getPropsOrBuilder(int index); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.RowData} + */ + public static final class RowData extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.RowData) + RowDataOrBuilder { + // Use RowData.newBuilder() to construct. + private RowData(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private RowData(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final RowData defaultInstance; + public static RowData getDefaultInstance() { + return defaultInstance; + } + + public RowData getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private RowData( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) { + beforeColumns_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000001; + } + beforeColumns_.add(input.readMessage(Column.PARSER, extensionRegistry)); + break; + } + case 18: { + if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { + afterColumns_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000002; + } + afterColumns_.add(input.readMessage(Column.PARSER, extensionRegistry)); + break; + } + case 26: { + if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + props_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; + } + props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) { + beforeColumns_ = java.util.Collections.unmodifiableList(beforeColumns_); + } + if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { + afterColumns_ = java.util.Collections.unmodifiableList(afterColumns_); + } + if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + props_ = java.util.Collections.unmodifiableList(props_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable + .ensureFieldAccessorsInitialized( + RowData.class, Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public RowData parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new RowData(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + public static final int BEFORECOLUMNS_FIELD_NUMBER = 1; + private java.util.List beforeColumns_; + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + public java.util.List getBeforeColumnsList() { + return beforeColumns_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + public java.util.List + getBeforeColumnsOrBuilderList() { + return beforeColumns_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + public int getBeforeColumnsCount() { + return beforeColumns_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + public Column getBeforeColumns(int index) { + return beforeColumns_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +     ** 字段信息,增量数据(修改前,删除前) *
      +     * 
      + */ + public ColumnOrBuilder getBeforeColumnsOrBuilder( + int index) { + return beforeColumns_.get(index); + } + + public static final int AFTERCOLUMNS_FIELD_NUMBER = 2; + private java.util.List afterColumns_; + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + public java.util.List getAfterColumnsList() { + return afterColumns_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + public java.util.List + getAfterColumnsOrBuilderList() { + return afterColumns_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + public int getAfterColumnsCount() { + return afterColumns_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + public Column getAfterColumns(int index) { + return afterColumns_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +     ** 字段信息,增量数据(修改后,新增后)  *
      +     * 
      + */ + public ColumnOrBuilder getAfterColumnsOrBuilder( + int index) { + return afterColumns_.get(index); + } + + public static final int PROPS_FIELD_NUMBER = 3; + private java.util.List props_; + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List getPropsList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List + getPropsOrBuilderList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public int getPropsCount() { + return props_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public Pair getProps(int index) { + return props_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + return props_.get(index); + } + + private void initFields() { + beforeColumns_ = java.util.Collections.emptyList(); + afterColumns_ = java.util.Collections.emptyList(); + props_ = java.util.Collections.emptyList(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + for (int i = 0; i < beforeColumns_.size(); i++) { + output.writeMessage(1, beforeColumns_.get(i)); + } + for (int i = 0; i < afterColumns_.size(); i++) { + output.writeMessage(2, afterColumns_.get(i)); + } + for (int i = 0; i < props_.size(); i++) { + output.writeMessage(3, props_.get(i)); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < beforeColumns_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, beforeColumns_.get(i)); + } + for (int i = 0; i < afterColumns_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(2, afterColumns_.get(i)); + } + for (int i = 0; i < props_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, props_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static RowData parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static RowData parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static RowData parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static RowData parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static RowData parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static RowData parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static RowData parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static RowData parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static RowData parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static RowData parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(RowData prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.RowData} */ - public static final class RowData extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.RowData) - RowDataOrBuilder { - - // Use RowData.newBuilder() to construct. - private RowData(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private RowData(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final RowData defaultInstance; - - public static RowData getDefaultInstance() { - return defaultInstance; - } - - public RowData getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private RowData(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) { - beforeColumns_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000001; - } - beforeColumns_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.PARSER, - extensionRegistry)); - break; - } - case 18: { - if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - afterColumns_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000002; - } - afterColumns_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.PARSER, - extensionRegistry)); - break; - } - case 26: { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - props_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, - extensionRegistry)); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) { - beforeColumns_ = java.util.Collections.unmodifiableList(beforeColumns_); - } - if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - afterColumns_ = java.util.Collections.unmodifiableList(afterColumns_); - } - if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = java.util.Collections.unmodifiableList(props_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.RowData.class, - com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public RowData parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new RowData(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public static final int BEFORECOLUMNS_FIELD_NUMBER = 1; - private java.util.List beforeColumns_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - public java.util.List getBeforeColumnsList() { - return beforeColumns_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - public java.util.List getBeforeColumnsOrBuilderList() { - return beforeColumns_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - public int getBeforeColumnsCount() { - return beforeColumns_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column getBeforeColumns(int index) { - return beforeColumns_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -         * * 字段信息,增量数据(修改前,删除前) *
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getBeforeColumnsOrBuilder(int index) { - return beforeColumns_.get(index); - } - - public static final int AFTERCOLUMNS_FIELD_NUMBER = 2; - private java.util.List afterColumns_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - public java.util.List getAfterColumnsList() { - return afterColumns_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - public java.util.List getAfterColumnsOrBuilderList() { - return afterColumns_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - public int getAfterColumnsCount() { - return afterColumns_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column getAfterColumns(int index) { - return afterColumns_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -         * * 字段信息,增量数据(修改后,新增后)  *
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getAfterColumnsOrBuilder(int index) { - return afterColumns_.get(index); - } - - public static final int PROPS_FIELD_NUMBER = 3; - private java.util.List props_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsOrBuilderList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public int getPropsCount() { - return props_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - return props_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - return props_.get(index); - } - - private void initFields() { - beforeColumns_ = java.util.Collections.emptyList(); - afterColumns_ = java.util.Collections.emptyList(); - props_ = java.util.Collections.emptyList(); - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - for (int i = 0; i < beforeColumns_.size(); i++) { - output.writeMessage(1, beforeColumns_.get(i)); - } - for (int i = 0; i < afterColumns_.size(); i++) { - output.writeMessage(2, afterColumns_.get(i)); - } - for (int i = 0; i < props_.size(); i++) { - output.writeMessage(3, props_.get(i)); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - for (int i = 0; i < beforeColumns_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(1, beforeColumns_.get(i)); - } - for (int i = 0; i < afterColumns_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(2, afterColumns_.get(i)); - } - for (int i = 0; i < props_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(3, props_.get(i)); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.RowData prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.RowData} - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.RowData) - com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder { - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.RowData.class, - com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder.class); - } - - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.RowData.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getBeforeColumnsFieldBuilder(); - getAfterColumnsFieldBuilder(); - getPropsFieldBuilder(); - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - if (beforeColumnsBuilder_ == null) { - beforeColumns_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000001); - } else { - beforeColumnsBuilder_.clear(); - } - if (afterColumnsBuilder_ == null) { - afterColumns_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - } else { - afterColumnsBuilder_.clear(); - } - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - } else { - propsBuilder_.clear(); - } - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.RowData getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.RowData build() { - com.alibaba.otter.canal.protocol.CanalEntry.RowData result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.RowData buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.RowData result = new com.alibaba.otter.canal.protocol.CanalEntry.RowData(this); - int from_bitField0_ = bitField0_; - if (beforeColumnsBuilder_ == null) { - if (((bitField0_ & 0x00000001) == 0x00000001)) { - beforeColumns_ = java.util.Collections.unmodifiableList(beforeColumns_); - bitField0_ = (bitField0_ & ~0x00000001); - } - result.beforeColumns_ = beforeColumns_; - } else { - result.beforeColumns_ = beforeColumnsBuilder_.build(); - } - if (afterColumnsBuilder_ == null) { - if (((bitField0_ & 0x00000002) == 0x00000002)) { - afterColumns_ = java.util.Collections.unmodifiableList(afterColumns_); - bitField0_ = (bitField0_ & ~0x00000002); - } - result.afterColumns_ = afterColumns_; - } else { - result.afterColumns_ = afterColumnsBuilder_.build(); - } - if (propsBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = java.util.Collections.unmodifiableList(props_); - bitField0_ = (bitField0_ & ~0x00000004); - } - result.props_ = props_; - } else { - result.props_ = propsBuilder_.build(); - } - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.RowData) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.RowData) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.RowData other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance()) return this; - if (beforeColumnsBuilder_ == null) { - if (!other.beforeColumns_.isEmpty()) { - if (beforeColumns_.isEmpty()) { - beforeColumns_ = other.beforeColumns_; - bitField0_ = (bitField0_ & ~0x00000001); - } else { - ensureBeforeColumnsIsMutable(); - beforeColumns_.addAll(other.beforeColumns_); - } - onChanged(); - } - } else { - if (!other.beforeColumns_.isEmpty()) { - if (beforeColumnsBuilder_.isEmpty()) { - beforeColumnsBuilder_.dispose(); - beforeColumnsBuilder_ = null; - beforeColumns_ = other.beforeColumns_; - bitField0_ = (bitField0_ & ~0x00000001); - beforeColumnsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getBeforeColumnsFieldBuilder() : null; - } else { - beforeColumnsBuilder_.addAllMessages(other.beforeColumns_); - } - } - } - if (afterColumnsBuilder_ == null) { - if (!other.afterColumns_.isEmpty()) { - if (afterColumns_.isEmpty()) { - afterColumns_ = other.afterColumns_; - bitField0_ = (bitField0_ & ~0x00000002); - } else { - ensureAfterColumnsIsMutable(); - afterColumns_.addAll(other.afterColumns_); - } - onChanged(); - } - } else { - if (!other.afterColumns_.isEmpty()) { - if (afterColumnsBuilder_.isEmpty()) { - afterColumnsBuilder_.dispose(); - afterColumnsBuilder_ = null; - afterColumns_ = other.afterColumns_; - bitField0_ = (bitField0_ & ~0x00000002); - afterColumnsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getAfterColumnsFieldBuilder() : null; - } else { - afterColumnsBuilder_.addAllMessages(other.afterColumns_); - } - } - } - if (propsBuilder_ == null) { - if (!other.props_.isEmpty()) { - if (props_.isEmpty()) { - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000004); - } else { - ensurePropsIsMutable(); - props_.addAll(other.props_); - } - onChanged(); - } - } else { - if (!other.props_.isEmpty()) { - if (propsBuilder_.isEmpty()) { - propsBuilder_.dispose(); - propsBuilder_ = null; - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000004); - propsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; - } else { - propsBuilder_.addAllMessages(other.props_); - } - } - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.RowData parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.RowData) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.util.List beforeColumns_ = java.util.Collections.emptyList(); - - private void ensureBeforeColumnsIsMutable() { - if (!((bitField0_ & 0x00000001) == 0x00000001)) { - beforeColumns_ = new java.util.ArrayList(beforeColumns_); - bitField0_ |= 0x00000001; - } - } - - private com.google.protobuf.RepeatedFieldBuilder beforeColumnsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public java.util.List getBeforeColumnsList() { - if (beforeColumnsBuilder_ == null) { - return java.util.Collections.unmodifiableList(beforeColumns_); - } else { - return beforeColumnsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public int getBeforeColumnsCount() { - if (beforeColumnsBuilder_ == null) { - return beforeColumns_.size(); - } else { - return beforeColumnsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column getBeforeColumns(int index) { - if (beforeColumnsBuilder_ == null) { - return beforeColumns_.get(index); - } else { - return beforeColumnsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder setBeforeColumns(int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { - if (beforeColumnsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureBeforeColumnsIsMutable(); - beforeColumns_.set(index, value); - onChanged(); - } else { - beforeColumnsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder setBeforeColumns(int index, - com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { - if (beforeColumnsBuilder_ == null) { - ensureBeforeColumnsIsMutable(); - beforeColumns_.set(index, builderForValue.build()); - onChanged(); - } else { - beforeColumnsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder addBeforeColumns(com.alibaba.otter.canal.protocol.CanalEntry.Column value) { - if (beforeColumnsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureBeforeColumnsIsMutable(); - beforeColumns_.add(value); - onChanged(); - } else { - beforeColumnsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder addBeforeColumns(int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { - if (beforeColumnsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureBeforeColumnsIsMutable(); - beforeColumns_.add(index, value); - onChanged(); - } else { - beforeColumnsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder addBeforeColumns(com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { - if (beforeColumnsBuilder_ == null) { - ensureBeforeColumnsIsMutable(); - beforeColumns_.add(builderForValue.build()); - onChanged(); - } else { - beforeColumnsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder addBeforeColumns(int index, - com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { - if (beforeColumnsBuilder_ == null) { - ensureBeforeColumnsIsMutable(); - beforeColumns_.add(index, builderForValue.build()); - onChanged(); - } else { - beforeColumnsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder addAllBeforeColumns(java.lang.Iterable values) { - if (beforeColumnsBuilder_ == null) { - ensureBeforeColumnsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, beforeColumns_); - onChanged(); - } else { - beforeColumnsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder clearBeforeColumns() { - if (beforeColumnsBuilder_ == null) { - beforeColumns_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000001); - onChanged(); - } else { - beforeColumnsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public Builder removeBeforeColumns(int index) { - if (beforeColumnsBuilder_ == null) { - ensureBeforeColumnsIsMutable(); - beforeColumns_.remove(index); - onChanged(); - } else { - beforeColumnsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder getBeforeColumnsBuilder(int index) { - return getBeforeColumnsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getBeforeColumnsOrBuilder(int index) { - if (beforeColumnsBuilder_ == null) { - return beforeColumns_.get(index); - } else { - return beforeColumnsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public java.util.List getBeforeColumnsOrBuilderList() { - if (beforeColumnsBuilder_ != null) { - return beforeColumnsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(beforeColumns_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addBeforeColumnsBuilder() { - return getBeforeColumnsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addBeforeColumnsBuilder(int index) { - return getBeforeColumnsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * - *
      -             * * 字段信息,增量数据(修改前,删除前) *
      -             * 
      - */ - public java.util.List getBeforeColumnsBuilderList() { - return getBeforeColumnsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getBeforeColumnsFieldBuilder() { - if (beforeColumnsBuilder_ == null) { - beforeColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(beforeColumns_, - ((bitField0_ & 0x00000001) == 0x00000001), - getParentForChildren(), - isClean()); - beforeColumns_ = null; - } - return beforeColumnsBuilder_; - } - - private java.util.List afterColumns_ = java.util.Collections.emptyList(); - - private void ensureAfterColumnsIsMutable() { - if (!((bitField0_ & 0x00000002) == 0x00000002)) { - afterColumns_ = new java.util.ArrayList(afterColumns_); - bitField0_ |= 0x00000002; - } - } - - private com.google.protobuf.RepeatedFieldBuilder afterColumnsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public java.util.List getAfterColumnsList() { - if (afterColumnsBuilder_ == null) { - return java.util.Collections.unmodifiableList(afterColumns_); - } else { - return afterColumnsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public int getAfterColumnsCount() { - if (afterColumnsBuilder_ == null) { - return afterColumns_.size(); - } else { - return afterColumnsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column getAfterColumns(int index) { - if (afterColumnsBuilder_ == null) { - return afterColumns_.get(index); - } else { - return afterColumnsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder setAfterColumns(int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { - if (afterColumnsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureAfterColumnsIsMutable(); - afterColumns_.set(index, value); - onChanged(); - } else { - afterColumnsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder setAfterColumns(int index, - com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { - if (afterColumnsBuilder_ == null) { - ensureAfterColumnsIsMutable(); - afterColumns_.set(index, builderForValue.build()); - onChanged(); - } else { - afterColumnsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder addAfterColumns(com.alibaba.otter.canal.protocol.CanalEntry.Column value) { - if (afterColumnsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureAfterColumnsIsMutable(); - afterColumns_.add(value); - onChanged(); - } else { - afterColumnsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder addAfterColumns(int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { - if (afterColumnsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureAfterColumnsIsMutable(); - afterColumns_.add(index, value); - onChanged(); - } else { - afterColumnsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder addAfterColumns(com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { - if (afterColumnsBuilder_ == null) { - ensureAfterColumnsIsMutable(); - afterColumns_.add(builderForValue.build()); - onChanged(); - } else { - afterColumnsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder addAfterColumns(int index, - com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { - if (afterColumnsBuilder_ == null) { - ensureAfterColumnsIsMutable(); - afterColumns_.add(index, builderForValue.build()); - onChanged(); - } else { - afterColumnsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder addAllAfterColumns(java.lang.Iterable values) { - if (afterColumnsBuilder_ == null) { - ensureAfterColumnsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, afterColumns_); - onChanged(); - } else { - afterColumnsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder clearAfterColumns() { - if (afterColumnsBuilder_ == null) { - afterColumns_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - } else { - afterColumnsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public Builder removeAfterColumns(int index) { - if (afterColumnsBuilder_ == null) { - ensureAfterColumnsIsMutable(); - afterColumns_.remove(index); - onChanged(); - } else { - afterColumnsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder getAfterColumnsBuilder(int index) { - return getAfterColumnsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getAfterColumnsOrBuilder(int index) { - if (afterColumnsBuilder_ == null) { - return afterColumns_.get(index); - } else { - return afterColumnsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public java.util.List getAfterColumnsOrBuilderList() { - if (afterColumnsBuilder_ != null) { - return afterColumnsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(afterColumns_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addAfterColumnsBuilder() { - return getAfterColumnsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addAfterColumnsBuilder(int index) { - return getAfterColumnsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * - *
      -             * * 字段信息,增量数据(修改后,新增后)  *
      -             * 
      - */ - public java.util.List getAfterColumnsBuilderList() { - return getAfterColumnsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getAfterColumnsFieldBuilder() { - if (afterColumnsBuilder_ == null) { - afterColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(afterColumns_, - ((bitField0_ & 0x00000002) == 0x00000002), - getParentForChildren(), - isClean()); - afterColumns_ = null; - } - return afterColumnsBuilder_; - } - - private java.util.List props_ = java.util.Collections.emptyList(); - - private void ensurePropsIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(props_); - bitField0_ |= 0x00000004; - } - } - - private com.google.protobuf.RepeatedFieldBuilder propsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsList() { - if (propsBuilder_ == null) { - return java.util.Collections.unmodifiableList(props_); - } else { - return propsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public int getPropsCount() { - if (propsBuilder_ == null) { - return props_.size(); - } else { - return propsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.set(index, value); - onChanged(); - } else { - propsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.set(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(value); - onChanged(); - } else { - propsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(index, value); - onChanged(); - } else { - propsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addAllProps(java.lang.Iterable values) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, props_); - onChanged(); - } else { - propsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder clearProps() { - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - } else { - propsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder removeProps(int index) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.remove(index); - onChanged(); - } else { - propsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder(int index) { - return getPropsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsOrBuilderList() { - if (propsBuilder_ != null) { - return propsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(props_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { - return getPropsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder(int index) { - return getPropsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsBuilderList() { - return getPropsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getPropsFieldBuilder() { - if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(props_, - ((bitField0_ & 0x00000004) == 0x00000004), - getParentForChildren(), - isClean()); - props_ = null; - } - return propsBuilder_; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.RowData) + RowDataOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable + .ensureFieldAccessorsInitialized( + RowData.class, Builder.class); + } + + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.RowData.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getBeforeColumnsFieldBuilder(); + getAfterColumnsFieldBuilder(); + getPropsFieldBuilder(); } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new RowData(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + if (beforeColumnsBuilder_ == null) { + beforeColumns_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + } else { + beforeColumnsBuilder_.clear(); } + if (afterColumnsBuilder_ == null) { + afterColumns_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + } else { + afterColumnsBuilder_.clear(); + } + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + } else { + propsBuilder_.clear(); + } + return this; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowData) + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; + } + + public RowData getDefaultInstanceForType() { + return RowData.getDefaultInstance(); + } + + public RowData build() { + RowData result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public RowData buildPartial() { + RowData result = new RowData(this); + int from_bitField0_ = bitField0_; + if (beforeColumnsBuilder_ == null) { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + beforeColumns_ = java.util.Collections.unmodifiableList(beforeColumns_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.beforeColumns_ = beforeColumns_; + } else { + result.beforeColumns_ = beforeColumnsBuilder_.build(); + } + if (afterColumnsBuilder_ == null) { + if (((bitField0_ & 0x00000002) == 0x00000002)) { + afterColumns_ = java.util.Collections.unmodifiableList(afterColumns_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.afterColumns_ = afterColumns_; + } else { + result.afterColumns_ = afterColumnsBuilder_.build(); + } + if (propsBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { + props_ = java.util.Collections.unmodifiableList(props_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.props_ = props_; + } else { + result.props_ = propsBuilder_.build(); + } + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof RowData) { + return mergeFrom((RowData)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(RowData other) { + if (other == RowData.getDefaultInstance()) return this; + if (beforeColumnsBuilder_ == null) { + if (!other.beforeColumns_.isEmpty()) { + if (beforeColumns_.isEmpty()) { + beforeColumns_ = other.beforeColumns_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensureBeforeColumnsIsMutable(); + beforeColumns_.addAll(other.beforeColumns_); + } + onChanged(); + } + } else { + if (!other.beforeColumns_.isEmpty()) { + if (beforeColumnsBuilder_.isEmpty()) { + beforeColumnsBuilder_.dispose(); + beforeColumnsBuilder_ = null; + beforeColumns_ = other.beforeColumns_; + bitField0_ = (bitField0_ & ~0x00000001); + beforeColumnsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getBeforeColumnsFieldBuilder() : null; + } else { + beforeColumnsBuilder_.addAllMessages(other.beforeColumns_); + } + } + } + if (afterColumnsBuilder_ == null) { + if (!other.afterColumns_.isEmpty()) { + if (afterColumns_.isEmpty()) { + afterColumns_ = other.afterColumns_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureAfterColumnsIsMutable(); + afterColumns_.addAll(other.afterColumns_); + } + onChanged(); + } + } else { + if (!other.afterColumns_.isEmpty()) { + if (afterColumnsBuilder_.isEmpty()) { + afterColumnsBuilder_.dispose(); + afterColumnsBuilder_ = null; + afterColumns_ = other.afterColumns_; + bitField0_ = (bitField0_ & ~0x00000002); + afterColumnsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getAfterColumnsFieldBuilder() : null; + } else { + afterColumnsBuilder_.addAllMessages(other.afterColumns_); + } + } + } + if (propsBuilder_ == null) { + if (!other.props_.isEmpty()) { + if (props_.isEmpty()) { + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensurePropsIsMutable(); + props_.addAll(other.props_); + } + onChanged(); + } + } else { + if (!other.props_.isEmpty()) { + if (propsBuilder_.isEmpty()) { + propsBuilder_.dispose(); + propsBuilder_ = null; + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000004); + propsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPropsFieldBuilder() : null; + } else { + propsBuilder_.addAllMessages(other.props_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + RowData parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (RowData) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.util.List beforeColumns_ = + java.util.Collections.emptyList(); + private void ensureBeforeColumnsIsMutable() { + if (!((bitField0_ & 0x00000001) == 0x00000001)) { + beforeColumns_ = new java.util.ArrayList(beforeColumns_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Column, Column.Builder, ColumnOrBuilder> beforeColumnsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public java.util.List getBeforeColumnsList() { + if (beforeColumnsBuilder_ == null) { + return java.util.Collections.unmodifiableList(beforeColumns_); + } else { + return beforeColumnsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public int getBeforeColumnsCount() { + if (beforeColumnsBuilder_ == null) { + return beforeColumns_.size(); + } else { + return beforeColumnsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Column getBeforeColumns(int index) { + if (beforeColumnsBuilder_ == null) { + return beforeColumns_.get(index); + } else { + return beforeColumnsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder setBeforeColumns( + int index, Column value) { + if (beforeColumnsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureBeforeColumnsIsMutable(); + beforeColumns_.set(index, value); + onChanged(); + } else { + beforeColumnsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder setBeforeColumns( + int index, Column.Builder builderForValue) { + if (beforeColumnsBuilder_ == null) { + ensureBeforeColumnsIsMutable(); + beforeColumns_.set(index, builderForValue.build()); + onChanged(); + } else { + beforeColumnsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder addBeforeColumns(Column value) { + if (beforeColumnsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureBeforeColumnsIsMutable(); + beforeColumns_.add(value); + onChanged(); + } else { + beforeColumnsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder addBeforeColumns( + int index, Column value) { + if (beforeColumnsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureBeforeColumnsIsMutable(); + beforeColumns_.add(index, value); + onChanged(); + } else { + beforeColumnsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder addBeforeColumns( + Column.Builder builderForValue) { + if (beforeColumnsBuilder_ == null) { + ensureBeforeColumnsIsMutable(); + beforeColumns_.add(builderForValue.build()); + onChanged(); + } else { + beforeColumnsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder addBeforeColumns( + int index, Column.Builder builderForValue) { + if (beforeColumnsBuilder_ == null) { + ensureBeforeColumnsIsMutable(); + beforeColumns_.add(index, builderForValue.build()); + onChanged(); + } else { + beforeColumnsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder addAllBeforeColumns( + Iterable values) { + if (beforeColumnsBuilder_ == null) { + ensureBeforeColumnsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, beforeColumns_); + onChanged(); + } else { + beforeColumnsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder clearBeforeColumns() { + if (beforeColumnsBuilder_ == null) { + beforeColumns_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + beforeColumnsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Builder removeBeforeColumns(int index) { + if (beforeColumnsBuilder_ == null) { + ensureBeforeColumnsIsMutable(); + beforeColumns_.remove(index); + onChanged(); + } else { + beforeColumnsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Column.Builder getBeforeColumnsBuilder( + int index) { + return getBeforeColumnsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public ColumnOrBuilder getBeforeColumnsOrBuilder( + int index) { + if (beforeColumnsBuilder_ == null) { + return beforeColumns_.get(index); } else { + return beforeColumnsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public java.util.List + getBeforeColumnsOrBuilderList() { + if (beforeColumnsBuilder_ != null) { + return beforeColumnsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(beforeColumns_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Column.Builder addBeforeColumnsBuilder() { + return getBeforeColumnsFieldBuilder().addBuilder( + Column.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public Column.Builder addBeforeColumnsBuilder( + int index) { + return getBeforeColumnsFieldBuilder().addBuilder( + index, Column.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; + * + *
      +       ** 字段信息,增量数据(修改前,删除前) *
      +       * 
      + */ + public java.util.List + getBeforeColumnsBuilderList() { + return getBeforeColumnsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Column, Column.Builder, ColumnOrBuilder> + getBeforeColumnsFieldBuilder() { + if (beforeColumnsBuilder_ == null) { + beforeColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Column, Column.Builder, ColumnOrBuilder>( + beforeColumns_, + ((bitField0_ & 0x00000001) == 0x00000001), + getParentForChildren(), + isClean()); + beforeColumns_ = null; + } + return beforeColumnsBuilder_; + } + + private java.util.List afterColumns_ = + java.util.Collections.emptyList(); + private void ensureAfterColumnsIsMutable() { + if (!((bitField0_ & 0x00000002) == 0x00000002)) { + afterColumns_ = new java.util.ArrayList(afterColumns_); + bitField0_ |= 0x00000002; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Column, Column.Builder, ColumnOrBuilder> afterColumnsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public java.util.List getAfterColumnsList() { + if (afterColumnsBuilder_ == null) { + return java.util.Collections.unmodifiableList(afterColumns_); + } else { + return afterColumnsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public int getAfterColumnsCount() { + if (afterColumnsBuilder_ == null) { + return afterColumns_.size(); + } else { + return afterColumnsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Column getAfterColumns(int index) { + if (afterColumnsBuilder_ == null) { + return afterColumns_.get(index); + } else { + return afterColumnsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder setAfterColumns( + int index, Column value) { + if (afterColumnsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAfterColumnsIsMutable(); + afterColumns_.set(index, value); + onChanged(); + } else { + afterColumnsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder setAfterColumns( + int index, Column.Builder builderForValue) { + if (afterColumnsBuilder_ == null) { + ensureAfterColumnsIsMutable(); + afterColumns_.set(index, builderForValue.build()); + onChanged(); + } else { + afterColumnsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder addAfterColumns(Column value) { + if (afterColumnsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAfterColumnsIsMutable(); + afterColumns_.add(value); + onChanged(); + } else { + afterColumnsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder addAfterColumns( + int index, Column value) { + if (afterColumnsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureAfterColumnsIsMutable(); + afterColumns_.add(index, value); + onChanged(); + } else { + afterColumnsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder addAfterColumns( + Column.Builder builderForValue) { + if (afterColumnsBuilder_ == null) { + ensureAfterColumnsIsMutable(); + afterColumns_.add(builderForValue.build()); + onChanged(); + } else { + afterColumnsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder addAfterColumns( + int index, Column.Builder builderForValue) { + if (afterColumnsBuilder_ == null) { + ensureAfterColumnsIsMutable(); + afterColumns_.add(index, builderForValue.build()); + onChanged(); + } else { + afterColumnsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder addAllAfterColumns( + Iterable values) { + if (afterColumnsBuilder_ == null) { + ensureAfterColumnsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, afterColumns_); + onChanged(); + } else { + afterColumnsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder clearAfterColumns() { + if (afterColumnsBuilder_ == null) { + afterColumns_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + } else { + afterColumnsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Builder removeAfterColumns(int index) { + if (afterColumnsBuilder_ == null) { + ensureAfterColumnsIsMutable(); + afterColumns_.remove(index); + onChanged(); + } else { + afterColumnsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Column.Builder getAfterColumnsBuilder( + int index) { + return getAfterColumnsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public ColumnOrBuilder getAfterColumnsOrBuilder( + int index) { + if (afterColumnsBuilder_ == null) { + return afterColumns_.get(index); } else { + return afterColumnsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public java.util.List + getAfterColumnsOrBuilderList() { + if (afterColumnsBuilder_ != null) { + return afterColumnsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(afterColumns_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Column.Builder addAfterColumnsBuilder() { + return getAfterColumnsFieldBuilder().addBuilder( + Column.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public Column.Builder addAfterColumnsBuilder( + int index) { + return getAfterColumnsFieldBuilder().addBuilder( + index, Column.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; + * + *
      +       ** 字段信息,增量数据(修改后,新增后)  *
      +       * 
      + */ + public java.util.List + getAfterColumnsBuilderList() { + return getAfterColumnsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Column, Column.Builder, ColumnOrBuilder> + getAfterColumnsFieldBuilder() { + if (afterColumnsBuilder_ == null) { + afterColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Column, Column.Builder, ColumnOrBuilder>( + afterColumns_, + ((bitField0_ & 0x00000002) == 0x00000002), + getParentForChildren(), + isClean()); + afterColumns_ = null; + } + return afterColumnsBuilder_; + } + + private java.util.List props_ = + java.util.Collections.emptyList(); + private void ensurePropsIsMutable() { + if (!((bitField0_ & 0x00000004) == 0x00000004)) { + props_ = new java.util.ArrayList(props_); + bitField0_ |= 0x00000004; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List getPropsList() { + if (propsBuilder_ == null) { + return java.util.Collections.unmodifiableList(props_); + } else { + return propsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public int getPropsCount() { + if (propsBuilder_ == null) { + return props_.size(); + } else { + return propsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair getProps(int index) { + if (propsBuilder_ == null) { + return props_.get(index); + } else { + return propsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.set(index, value); + onChanged(); + } else { + propsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.set(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps(Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(value); + onChanged(); + } else { + propsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(index, value); + onChanged(); + } else { + propsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addAllProps( + Iterable values) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, props_); + onChanged(); + } else { + propsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder clearProps() { + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + } else { + propsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder removeProps(int index) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.remove(index); + onChanged(); + } else { + propsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder getPropsBuilder( + int index) { + return getPropsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + if (propsBuilder_ == null) { + return props_.get(index); } else { + return propsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsOrBuilderList() { + if (propsBuilder_ != null) { + return propsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(props_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder() { + return getPropsFieldBuilder().addBuilder( + Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder( + int index) { + return getPropsFieldBuilder().addBuilder( + index, Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsBuilderList() { + return getPropsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> + getPropsFieldBuilder() { + if (propsBuilder_ == null) { + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder>( + props_, + ((bitField0_ & 0x00000004) == 0x00000004), + getParentForChildren(), + isClean()); + props_ = null; + } + return propsBuilder_; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.RowData) } - public interface RowChangeOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.RowChange) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 tableId = 1; - * - *
      -         * *tableId,由数据库产生*
      -         * 
      - */ - boolean hasTableId(); - - /** - * optional int64 tableId = 1; - * - *
      -         * *tableId,由数据库产生*
      -         * 
      - */ - long getTableId(); - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - boolean hasEventType(); - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType(); - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -         * * 标识是否是ddl语句  *
      -         * 
      - */ - boolean hasIsDdl(); - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -         * * 标识是否是ddl语句  *
      -         * 
      - */ - boolean getIsDdl(); - - /** - * optional string sql = 11; - * - *
      -         * * ddl/query的sql语句  *
      -         * 
      - */ - boolean hasSql(); - - /** - * optional string sql = 11; - * - *
      -         * * ddl/query的sql语句  *
      -         * 
      - */ - java.lang.String getSql(); - - /** - * optional string sql = 11; - * - *
      -         * * ddl/query的sql语句  *
      -         * 
      - */ - com.google.protobuf.ByteString getSqlBytes(); - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - java.util.List getRowDatasList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.RowData getRowDatas(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - int getRowDatasCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - java.util.List getRowDatasOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder getRowDatasOrBuilder(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - int getPropsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index); - - /** - * optional string ddlSchemaName = 14; - * - *
      -         * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -         * 
      - */ - boolean hasDdlSchemaName(); - - /** - * optional string ddlSchemaName = 14; - * - *
      -         * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -         * 
      - */ - java.lang.String getDdlSchemaName(); - - /** - * optional string ddlSchemaName = 14; - * - *
      -         * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -         * 
      - */ - com.google.protobuf.ByteString getDdlSchemaNameBytes(); + static { + defaultInstance = new RowData(true); + defaultInstance.initFields(); } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowData) + } + + public interface RowChangeOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.RowChange) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int64 tableId = 1; + * + *
      +     **tableId,由数据库产生*
      +     * 
      + */ + boolean hasTableId(); + /** + * optional int64 tableId = 1; + * + *
      +     **tableId,由数据库产生*
      +     * 
      + */ + long getTableId(); + + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + boolean hasEventType(); + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + EventType getEventType(); + + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +     ** 标识是否是ddl语句  *
      +     * 
      + */ + boolean hasIsDdl(); + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +     ** 标识是否是ddl语句  *
      +     * 
      + */ + boolean getIsDdl(); + + /** + * optional string sql = 11; + * + *
      +     ** ddl/query的sql语句  *
      +     * 
      + */ + boolean hasSql(); + /** + * optional string sql = 11; + * + *
      +     ** ddl/query的sql语句  *
      +     * 
      + */ + String getSql(); + /** + * optional string sql = 11; + * + *
      +     ** ddl/query的sql语句  *
      +     * 
      + */ + com.google.protobuf.ByteString + getSqlBytes(); + + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + java.util.List + getRowDatasList(); + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + RowData getRowDatas(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + int getRowDatasCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + java.util.List + getRowDatasOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + RowDataOrBuilder getRowDatasOrBuilder(int index); + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + Pair getProps(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + int getPropsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + PairOrBuilder getPropsOrBuilder(int index); + + /** + * optional string ddlSchemaName = 14; + * + *
      +     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +     * 
      + */ + boolean hasDdlSchemaName(); + /** + * optional string ddlSchemaName = 14; + * + *
      +     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +     * 
      + */ + String getDdlSchemaName(); + /** + * optional string ddlSchemaName = 14; + * + *
      +     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +     * 
      + */ + com.google.protobuf.ByteString + getDdlSchemaNameBytes(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.RowChange} + * + *
      +   **message row 每行变更数据的数据结构*
      +   * 
      + */ + public static final class RowChange extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.RowChange) + RowChangeOrBuilder { + // Use RowChange.newBuilder() to construct. + private RowChange(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private RowChange(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final RowChange defaultInstance; + public static RowChange getDefaultInstance() { + return defaultInstance; + } + + public RowChange getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private RowChange( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + tableId_ = input.readInt64(); + break; + } + case 16: { + int rawValue = input.readEnum(); + EventType value = EventType.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(2, rawValue); + } else { + bitField0_ |= 0x00000002; + eventType_ = value; + } + break; + } + case 80: { + bitField0_ |= 0x00000004; + isDdl_ = input.readBool(); + break; + } + case 90: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000008; + sql_ = bs; + break; + } + case 98: { + if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) { + rowDatas_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000010; + } + rowDatas_.add(input.readMessage(RowData.PARSER, extensionRegistry)); + break; + } + case 106: { + if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) { + props_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000020; + } + props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + break; + } + case 114: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000010; + ddlSchemaName_ = bs; + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) { + rowDatas_ = java.util.Collections.unmodifiableList(rowDatas_); + } + if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) { + props_ = java.util.Collections.unmodifiableList(props_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable + .ensureFieldAccessorsInitialized( + RowChange.class, Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public RowChange parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new RowChange(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + public static final int TABLEID_FIELD_NUMBER = 1; + private long tableId_; + /** + * optional int64 tableId = 1; + * + *
      +     **tableId,由数据库产生*
      +     * 
      + */ + public boolean hasTableId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 tableId = 1; + * + *
      +     **tableId,由数据库产生*
      +     * 
      + */ + public long getTableId() { + return tableId_; + } + + public static final int EVENTTYPE_FIELD_NUMBER = 2; + private EventType eventType_; + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + public boolean hasEventType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +     **数据变更类型*
      +     * 
      + */ + public EventType getEventType() { + return eventType_; + } + + public static final int ISDDL_FIELD_NUMBER = 10; + private boolean isDdl_; + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +     ** 标识是否是ddl语句  *
      +     * 
      + */ + public boolean hasIsDdl() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +     ** 标识是否是ddl语句  *
      +     * 
      + */ + public boolean getIsDdl() { + return isDdl_; + } + + public static final int SQL_FIELD_NUMBER = 11; + private Object sql_; + /** + * optional string sql = 11; + * + *
      +     ** ddl/query的sql语句  *
      +     * 
      + */ + public boolean hasSql() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional string sql = 11; + * + *
      +     ** ddl/query的sql语句  *
      +     * 
      + */ + public String getSql() { + Object ref = sql_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + sql_ = s; + } + return s; + } + } + /** + * optional string sql = 11; + * + *
      +     ** ddl/query的sql语句  *
      +     * 
      + */ + public com.google.protobuf.ByteString + getSqlBytes() { + Object ref = sql_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + sql_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int ROWDATAS_FIELD_NUMBER = 12; + private java.util.List rowDatas_; + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + public java.util.List getRowDatasList() { + return rowDatas_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + public java.util.List + getRowDatasOrBuilderList() { + return rowDatas_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + public int getRowDatasCount() { + return rowDatas_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + public RowData getRowDatas(int index) { + return rowDatas_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +     ** 一次数据库变更可能存在多行  *
      +     * 
      + */ + public RowDataOrBuilder getRowDatasOrBuilder( + int index) { + return rowDatas_.get(index); + } + + public static final int PROPS_FIELD_NUMBER = 13; + private java.util.List props_; + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List getPropsList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List + getPropsOrBuilderList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + public int getPropsCount() { + return props_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + public Pair getProps(int index) { + return props_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +     **预留扩展*
      +     * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + return props_.get(index); + } + + public static final int DDLSCHEMANAME_FIELD_NUMBER = 14; + private Object ddlSchemaName_; + /** + * optional string ddlSchemaName = 14; + * + *
      +     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +     * 
      + */ + public boolean hasDdlSchemaName() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional string ddlSchemaName = 14; + * + *
      +     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +     * 
      + */ + public String getDdlSchemaName() { + Object ref = ddlSchemaName_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + ddlSchemaName_ = s; + } + return s; + } + } + /** + * optional string ddlSchemaName = 14; + * + *
      +     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +     * 
      + */ + public com.google.protobuf.ByteString + getDdlSchemaNameBytes() { + Object ref = ddlSchemaName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + ddlSchemaName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + tableId_ = 0L; + eventType_ = EventType.UPDATE; + isDdl_ = false; + sql_ = ""; + rowDatas_ = java.util.Collections.emptyList(); + props_ = java.util.Collections.emptyList(); + ddlSchemaName_ = ""; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt64(1, tableId_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeEnum(2, eventType_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBool(10, isDdl_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeBytes(11, getSqlBytes()); + } + for (int i = 0; i < rowDatas_.size(); i++) { + output.writeMessage(12, rowDatas_.get(i)); + } + for (int i = 0; i < props_.size(); i++) { + output.writeMessage(13, props_.get(i)); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(14, getDdlSchemaNameBytes()); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, tableId_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(2, eventType_.getNumber()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(10, isDdl_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(11, getSqlBytes()); + } + for (int i = 0; i < rowDatas_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(12, rowDatas_.get(i)); + } + for (int i = 0; i < props_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(13, props_.get(i)); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(14, getDdlSchemaNameBytes()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static RowChange parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static RowChange parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static RowChange parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static RowChange parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static RowChange parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static RowChange parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static RowChange parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static RowChange parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static RowChange parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static RowChange parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(RowChange prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.RowChange} * *
      -     * *message row 每行变更数据的数据结构*
      +     **message row 每行变更数据的数据结构*
            * 
      */ - public static final class RowChange extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.RowChange) - RowChangeOrBuilder { - - // Use RowChange.newBuilder() to construct. - private RowChange(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private RowChange(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final RowChange defaultInstance; - - public static RowChange getDefaultInstance() { - return defaultInstance; - } - - public RowChange getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private RowChange(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - tableId_ = input.readInt64(); - break; - } - case 16: { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalEntry.EventType value = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(2, rawValue); - } else { - bitField0_ |= 0x00000002; - eventType_ = value; - } - break; - } - case 80: { - bitField0_ |= 0x00000004; - isDdl_ = input.readBool(); - break; - } - case 90: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000008; - sql_ = bs; - break; - } - case 98: { - if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) { - rowDatas_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000010; - } - rowDatas_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.RowData.PARSER, - extensionRegistry)); - break; - } - case 106: { - if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) { - props_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000020; - } - props_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, - extensionRegistry)); - break; - } - case 114: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000010; - ddlSchemaName_ = bs; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) { - rowDatas_ = java.util.Collections.unmodifiableList(rowDatas_); - } - if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) { - props_ = java.util.Collections.unmodifiableList(props_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.RowChange.class, - com.alibaba.otter.canal.protocol.CanalEntry.RowChange.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public RowChange parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new RowChange(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int TABLEID_FIELD_NUMBER = 1; - private long tableId_; - - /** - * optional int64 tableId = 1; - * - *
      -         * *tableId,由数据库产生*
      -         * 
      - */ - public boolean hasTableId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 tableId = 1; - * - *
      -         * *tableId,由数据库产生*
      -         * 
      - */ - public long getTableId() { - return tableId_; - } - - public static final int EVENTTYPE_FIELD_NUMBER = 2; - private com.alibaba.otter.canal.protocol.CanalEntry.EventType eventType_; - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -         * *数据变更类型*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - return eventType_; - } - - public static final int ISDDL_FIELD_NUMBER = 10; - private boolean isDdl_; - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -         * * 标识是否是ddl语句  *
      -         * 
      - */ - public boolean hasIsDdl() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -         * * 标识是否是ddl语句  *
      -         * 
      - */ - public boolean getIsDdl() { - return isDdl_; - } - - public static final int SQL_FIELD_NUMBER = 11; - private java.lang.Object sql_; - - /** - * optional string sql = 11; - * - *
      -         * * ddl/query的sql语句  *
      -         * 
      - */ - public boolean hasSql() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional string sql = 11; - * - *
      -         * * ddl/query的sql语句  *
      -         * 
      - */ - public java.lang.String getSql() { - java.lang.Object ref = sql_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - sql_ = s; - } - return s; - } - } - - /** - * optional string sql = 11; - * - *
      -         * * ddl/query的sql语句  *
      -         * 
      - */ - public com.google.protobuf.ByteString getSqlBytes() { - java.lang.Object ref = sql_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - sql_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int ROWDATAS_FIELD_NUMBER = 12; - private java.util.List rowDatas_; - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - public java.util.List getRowDatasList() { - return rowDatas_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - public java.util.List getRowDatasOrBuilderList() { - return rowDatas_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - public int getRowDatasCount() { - return rowDatas_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.RowData getRowDatas(int index) { - return rowDatas_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -         * * 一次数据库变更可能存在多行  *
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder getRowDatasOrBuilder(int index) { - return rowDatas_.get(index); - } - - public static final int PROPS_FIELD_NUMBER = 13; - private java.util.List props_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsOrBuilderList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public int getPropsCount() { - return props_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - return props_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - return props_.get(index); - } - - public static final int DDLSCHEMANAME_FIELD_NUMBER = 14; - private java.lang.Object ddlSchemaName_; - - /** - * optional string ddlSchemaName = 14; - * - *
      -         * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -         * 
      - */ - public boolean hasDdlSchemaName() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional string ddlSchemaName = 14; - * - *
      -         * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -         * 
      - */ - public java.lang.String getDdlSchemaName() { - java.lang.Object ref = ddlSchemaName_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - ddlSchemaName_ = s; - } - return s; - } - } - - /** - * optional string ddlSchemaName = 14; - * - *
      -         * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -         * 
      - */ - public com.google.protobuf.ByteString getDdlSchemaNameBytes() { - java.lang.Object ref = ddlSchemaName_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - ddlSchemaName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - tableId_ = 0L; - eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - isDdl_ = false; - sql_ = ""; - rowDatas_ = java.util.Collections.emptyList(); - props_ = java.util.Collections.emptyList(); - ddlSchemaName_ = ""; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt64(1, tableId_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, eventType_.getNumber()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBool(10, isDdl_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBytes(11, getSqlBytes()); - } - for (int i = 0; i < rowDatas_.size(); i++) { - output.writeMessage(12, rowDatas_.get(i)); - } - for (int i = 0; i < props_.size(); i++) { - output.writeMessage(13, props_.get(i)); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBytes(14, getDdlSchemaNameBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(1, tableId_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeEnumSize(2, eventType_.getNumber()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeBoolSize(10, isDdl_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(11, getSqlBytes()); - } - for (int i = 0; i < rowDatas_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(12, rowDatas_.get(i)); - } - for (int i = 0; i < props_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(13, props_.get(i)); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(14, getDdlSchemaNameBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.RowChange prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.RowChange} - * - *
      -         * *message row 每行变更数据的数据结构*
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.RowChange) - com.alibaba.otter.canal.protocol.CanalEntry.RowChangeOrBuilder { - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.RowChange.class, - com.alibaba.otter.canal.protocol.CanalEntry.RowChange.Builder.class); - } - - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.RowChange.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getRowDatasFieldBuilder(); - getPropsFieldBuilder(); - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - tableId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - bitField0_ = (bitField0_ & ~0x00000002); - isDdl_ = false; - bitField0_ = (bitField0_ & ~0x00000004); - sql_ = ""; - bitField0_ = (bitField0_ & ~0x00000008); - if (rowDatasBuilder_ == null) { - rowDatas_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); - } else { - rowDatasBuilder_.clear(); - } - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); - } else { - propsBuilder_.clear(); - } - ddlSchemaName_ = ""; - bitField0_ = (bitField0_ & ~0x00000040); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.RowChange getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.RowChange.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.RowChange build() { - com.alibaba.otter.canal.protocol.CanalEntry.RowChange result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.RowChange buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.RowChange result = new com.alibaba.otter.canal.protocol.CanalEntry.RowChange(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.tableId_ = tableId_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.eventType_ = eventType_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.isDdl_ = isDdl_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.sql_ = sql_; - if (rowDatasBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010)) { - rowDatas_ = java.util.Collections.unmodifiableList(rowDatas_); - bitField0_ = (bitField0_ & ~0x00000010); - } - result.rowDatas_ = rowDatas_; - } else { - result.rowDatas_ = rowDatasBuilder_.build(); - } - if (propsBuilder_ == null) { - if (((bitField0_ & 0x00000020) == 0x00000020)) { - props_ = java.util.Collections.unmodifiableList(props_); - bitField0_ = (bitField0_ & ~0x00000020); - } - result.props_ = props_; - } else { - result.props_ = propsBuilder_.build(); - } - if (((from_bitField0_ & 0x00000040) == 0x00000040)) { - to_bitField0_ |= 0x00000010; - } - result.ddlSchemaName_ = ddlSchemaName_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.RowChange) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.RowChange) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.RowChange other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.RowChange.getDefaultInstance()) return this; - if (other.hasTableId()) { - setTableId(other.getTableId()); - } - if (other.hasEventType()) { - setEventType(other.getEventType()); - } - if (other.hasIsDdl()) { - setIsDdl(other.getIsDdl()); - } - if (other.hasSql()) { - bitField0_ |= 0x00000008; - sql_ = other.sql_; - onChanged(); - } - if (rowDatasBuilder_ == null) { - if (!other.rowDatas_.isEmpty()) { - if (rowDatas_.isEmpty()) { - rowDatas_ = other.rowDatas_; - bitField0_ = (bitField0_ & ~0x00000010); - } else { - ensureRowDatasIsMutable(); - rowDatas_.addAll(other.rowDatas_); - } - onChanged(); - } - } else { - if (!other.rowDatas_.isEmpty()) { - if (rowDatasBuilder_.isEmpty()) { - rowDatasBuilder_.dispose(); - rowDatasBuilder_ = null; - rowDatas_ = other.rowDatas_; - bitField0_ = (bitField0_ & ~0x00000010); - rowDatasBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getRowDatasFieldBuilder() : null; - } else { - rowDatasBuilder_.addAllMessages(other.rowDatas_); - } - } - } - if (propsBuilder_ == null) { - if (!other.props_.isEmpty()) { - if (props_.isEmpty()) { - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000020); - } else { - ensurePropsIsMutable(); - props_.addAll(other.props_); - } - onChanged(); - } - } else { - if (!other.props_.isEmpty()) { - if (propsBuilder_.isEmpty()) { - propsBuilder_.dispose(); - propsBuilder_ = null; - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000020); - propsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; - } else { - propsBuilder_.addAllMessages(other.props_); - } - } - } - if (other.hasDdlSchemaName()) { - bitField0_ |= 0x00000040; - ddlSchemaName_ = other.ddlSchemaName_; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.RowChange parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.RowChange) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private long tableId_; - - /** - * optional int64 tableId = 1; - * - *
      -             * *tableId,由数据库产生*
      -             * 
      - */ - public boolean hasTableId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 tableId = 1; - * - *
      -             * *tableId,由数据库产生*
      -             * 
      - */ - public long getTableId() { - return tableId_; - } - - /** - * optional int64 tableId = 1; - * - *
      -             * *tableId,由数据库产生*
      -             * 
      - */ - public Builder setTableId(long value) { - bitField0_ |= 0x00000001; - tableId_ = value; - onChanged(); - return this; - } - - /** - * optional int64 tableId = 1; - * - *
      -             * *tableId,由数据库产生*
      -             * 
      - */ - public Builder clearTableId() { - bitField0_ = (bitField0_ & ~0x00000001); - tableId_ = 0L; - onChanged(); - return this; - } - - private com.alibaba.otter.canal.protocol.CanalEntry.EventType eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - return eventType_; - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public Builder setEventType(com.alibaba.otter.canal.protocol.CanalEntry.EventType value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - eventType_ = value; - onChanged(); - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * - *
      -             * *数据变更类型*
      -             * 
      - */ - public Builder clearEventType() { - bitField0_ = (bitField0_ & ~0x00000002); - eventType_ = com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE; - onChanged(); - return this; - } - - private boolean isDdl_; - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -             * * 标识是否是ddl语句  *
      -             * 
      - */ - public boolean hasIsDdl() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -             * * 标识是否是ddl语句  *
      -             * 
      - */ - public boolean getIsDdl() { - return isDdl_; - } - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -             * * 标识是否是ddl语句  *
      -             * 
      - */ - public Builder setIsDdl(boolean value) { - bitField0_ |= 0x00000004; - isDdl_ = value; - onChanged(); - return this; - } - - /** - * optional bool isDdl = 10 [default = false]; - * - *
      -             * * 标识是否是ddl语句  *
      -             * 
      - */ - public Builder clearIsDdl() { - bitField0_ = (bitField0_ & ~0x00000004); - isDdl_ = false; - onChanged(); - return this; - } - - private java.lang.Object sql_ = ""; - - /** - * optional string sql = 11; - * - *
      -             * * ddl/query的sql语句  *
      -             * 
      - */ - public boolean hasSql() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional string sql = 11; - * - *
      -             * * ddl/query的sql语句  *
      -             * 
      - */ - public java.lang.String getSql() { - java.lang.Object ref = sql_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - sql_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string sql = 11; - * - *
      -             * * ddl/query的sql语句  *
      -             * 
      - */ - public com.google.protobuf.ByteString getSqlBytes() { - java.lang.Object ref = sql_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - sql_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string sql = 11; - * - *
      -             * * ddl/query的sql语句  *
      -             * 
      - */ - public Builder setSql(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000008; - sql_ = value; - onChanged(); - return this; - } - - /** - * optional string sql = 11; - * - *
      -             * * ddl/query的sql语句  *
      -             * 
      - */ - public Builder clearSql() { - bitField0_ = (bitField0_ & ~0x00000008); - sql_ = getDefaultInstance().getSql(); - onChanged(); - return this; - } - - /** - * optional string sql = 11; - * - *
      -             * * ddl/query的sql语句  *
      -             * 
      - */ - public Builder setSqlBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000008; - sql_ = value; - onChanged(); - return this; - } - - private java.util.List rowDatas_ = java.util.Collections.emptyList(); - - private void ensureRowDatasIsMutable() { - if (!((bitField0_ & 0x00000010) == 0x00000010)) { - rowDatas_ = new java.util.ArrayList(rowDatas_); - bitField0_ |= 0x00000010; - } - } - - private com.google.protobuf.RepeatedFieldBuilder rowDatasBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public java.util.List getRowDatasList() { - if (rowDatasBuilder_ == null) { - return java.util.Collections.unmodifiableList(rowDatas_); - } else { - return rowDatasBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public int getRowDatasCount() { - if (rowDatasBuilder_ == null) { - return rowDatas_.size(); - } else { - return rowDatasBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.RowData getRowDatas(int index) { - if (rowDatasBuilder_ == null) { - return rowDatas_.get(index); - } else { - return rowDatasBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder setRowDatas(int index, com.alibaba.otter.canal.protocol.CanalEntry.RowData value) { - if (rowDatasBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureRowDatasIsMutable(); - rowDatas_.set(index, value); - onChanged(); - } else { - rowDatasBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder setRowDatas(int index, - com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder builderForValue) { - if (rowDatasBuilder_ == null) { - ensureRowDatasIsMutable(); - rowDatas_.set(index, builderForValue.build()); - onChanged(); - } else { - rowDatasBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder addRowDatas(com.alibaba.otter.canal.protocol.CanalEntry.RowData value) { - if (rowDatasBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureRowDatasIsMutable(); - rowDatas_.add(value); - onChanged(); - } else { - rowDatasBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder addRowDatas(int index, com.alibaba.otter.canal.protocol.CanalEntry.RowData value) { - if (rowDatasBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensureRowDatasIsMutable(); - rowDatas_.add(index, value); - onChanged(); - } else { - rowDatasBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder addRowDatas(com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder builderForValue) { - if (rowDatasBuilder_ == null) { - ensureRowDatasIsMutable(); - rowDatas_.add(builderForValue.build()); - onChanged(); - } else { - rowDatasBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder addRowDatas(int index, - com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder builderForValue) { - if (rowDatasBuilder_ == null) { - ensureRowDatasIsMutable(); - rowDatas_.add(index, builderForValue.build()); - onChanged(); - } else { - rowDatasBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder addAllRowDatas(java.lang.Iterable values) { - if (rowDatasBuilder_ == null) { - ensureRowDatasIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, rowDatas_); - onChanged(); - } else { - rowDatasBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder clearRowDatas() { - if (rowDatasBuilder_ == null) { - rowDatas_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000010); - onChanged(); - } else { - rowDatasBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public Builder removeRowDatas(int index) { - if (rowDatasBuilder_ == null) { - ensureRowDatasIsMutable(); - rowDatas_.remove(index); - onChanged(); - } else { - rowDatasBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder getRowDatasBuilder(int index) { - return getRowDatasFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder getRowDatasOrBuilder(int index) { - if (rowDatasBuilder_ == null) { - return rowDatas_.get(index); - } else { - return rowDatasBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public java.util.List getRowDatasOrBuilderList() { - if (rowDatasBuilder_ != null) { - return rowDatasBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(rowDatas_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder addRowDatasBuilder() { - return getRowDatasFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder addRowDatasBuilder(int index) { - return getRowDatasFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * - *
      -             * * 一次数据库变更可能存在多行  *
      -             * 
      - */ - public java.util.List getRowDatasBuilderList() { - return getRowDatasFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getRowDatasFieldBuilder() { - if (rowDatasBuilder_ == null) { - rowDatasBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(rowDatas_, - ((bitField0_ & 0x00000010) == 0x00000010), - getParentForChildren(), - isClean()); - rowDatas_ = null; - } - return rowDatasBuilder_; - } - - private java.util.List props_ = java.util.Collections.emptyList(); - - private void ensurePropsIsMutable() { - if (!((bitField0_ & 0x00000020) == 0x00000020)) { - props_ = new java.util.ArrayList(props_); - bitField0_ |= 0x00000020; - } - } - - private com.google.protobuf.RepeatedFieldBuilder propsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsList() { - if (propsBuilder_ == null) { - return java.util.Collections.unmodifiableList(props_); - } else { - return propsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public int getPropsCount() { - if (propsBuilder_ == null) { - return props_.size(); - } else { - return propsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.set(index, value); - onChanged(); - } else { - propsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.set(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(value); - onChanged(); - } else { - propsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(index, value); - onChanged(); - } else { - propsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addAllProps(java.lang.Iterable values) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, props_); - onChanged(); - } else { - propsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder clearProps() { - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000020); - onChanged(); - } else { - propsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder removeProps(int index) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.remove(index); - onChanged(); - } else { - propsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder(int index) { - return getPropsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsOrBuilderList() { - if (propsBuilder_ != null) { - return propsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(props_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { - return getPropsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder(int index) { - return getPropsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsBuilderList() { - return getPropsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getPropsFieldBuilder() { - if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(props_, - ((bitField0_ & 0x00000020) == 0x00000020), - getParentForChildren(), - isClean()); - props_ = null; - } - return propsBuilder_; - } - - private java.lang.Object ddlSchemaName_ = ""; - - /** - * optional string ddlSchemaName = 14; - * - *
      -             * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -             * 
      - */ - public boolean hasDdlSchemaName() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - - /** - * optional string ddlSchemaName = 14; - * - *
      -             * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -             * 
      - */ - public java.lang.String getDdlSchemaName() { - java.lang.Object ref = ddlSchemaName_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - ddlSchemaName_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string ddlSchemaName = 14; - * - *
      -             * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -             * 
      - */ - public com.google.protobuf.ByteString getDdlSchemaNameBytes() { - java.lang.Object ref = ddlSchemaName_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - ddlSchemaName_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string ddlSchemaName = 14; - * - *
      -             * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -             * 
      - */ - public Builder setDdlSchemaName(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000040; - ddlSchemaName_ = value; - onChanged(); - return this; - } - - /** - * optional string ddlSchemaName = 14; - * - *
      -             * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -             * 
      - */ - public Builder clearDdlSchemaName() { - bitField0_ = (bitField0_ & ~0x00000040); - ddlSchemaName_ = getDefaultInstance().getDdlSchemaName(); - onChanged(); - return this; - } - - /** - * optional string ddlSchemaName = 14; - * - *
      -             * * ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -             * 
      - */ - public Builder setDdlSchemaNameBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000040; - ddlSchemaName_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.RowChange) + RowChangeOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable + .ensureFieldAccessorsInitialized( + RowChange.class, Builder.class); + } + + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.RowChange.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getRowDatasFieldBuilder(); + getPropsFieldBuilder(); } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new RowChange(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + tableId_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + eventType_ = EventType.UPDATE; + bitField0_ = (bitField0_ & ~0x00000002); + isDdl_ = false; + bitField0_ = (bitField0_ & ~0x00000004); + sql_ = ""; + bitField0_ = (bitField0_ & ~0x00000008); + if (rowDatasBuilder_ == null) { + rowDatas_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000010); + } else { + rowDatasBuilder_.clear(); } + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000020); + } else { + propsBuilder_.clear(); + } + ddlSchemaName_ = ""; + bitField0_ = (bitField0_ & ~0x00000040); + return this; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowChange) + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; + } + + public RowChange getDefaultInstanceForType() { + return RowChange.getDefaultInstance(); + } + + public RowChange build() { + RowChange result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public RowChange buildPartial() { + RowChange result = new RowChange(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.tableId_ = tableId_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.eventType_ = eventType_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.isDdl_ = isDdl_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.sql_ = sql_; + if (rowDatasBuilder_ == null) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { + rowDatas_ = java.util.Collections.unmodifiableList(rowDatas_); + bitField0_ = (bitField0_ & ~0x00000010); + } + result.rowDatas_ = rowDatas_; + } else { + result.rowDatas_ = rowDatasBuilder_.build(); + } + if (propsBuilder_ == null) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { + props_ = java.util.Collections.unmodifiableList(props_); + bitField0_ = (bitField0_ & ~0x00000020); + } + result.props_ = props_; + } else { + result.props_ = propsBuilder_.build(); + } + if (((from_bitField0_ & 0x00000040) == 0x00000040)) { + to_bitField0_ |= 0x00000010; + } + result.ddlSchemaName_ = ddlSchemaName_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof RowChange) { + return mergeFrom((RowChange)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(RowChange other) { + if (other == RowChange.getDefaultInstance()) return this; + if (other.hasTableId()) { + setTableId(other.getTableId()); + } + if (other.hasEventType()) { + setEventType(other.getEventType()); + } + if (other.hasIsDdl()) { + setIsDdl(other.getIsDdl()); + } + if (other.hasSql()) { + bitField0_ |= 0x00000008; + sql_ = other.sql_; + onChanged(); + } + if (rowDatasBuilder_ == null) { + if (!other.rowDatas_.isEmpty()) { + if (rowDatas_.isEmpty()) { + rowDatas_ = other.rowDatas_; + bitField0_ = (bitField0_ & ~0x00000010); + } else { + ensureRowDatasIsMutable(); + rowDatas_.addAll(other.rowDatas_); + } + onChanged(); + } + } else { + if (!other.rowDatas_.isEmpty()) { + if (rowDatasBuilder_.isEmpty()) { + rowDatasBuilder_.dispose(); + rowDatasBuilder_ = null; + rowDatas_ = other.rowDatas_; + bitField0_ = (bitField0_ & ~0x00000010); + rowDatasBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getRowDatasFieldBuilder() : null; + } else { + rowDatasBuilder_.addAllMessages(other.rowDatas_); + } + } + } + if (propsBuilder_ == null) { + if (!other.props_.isEmpty()) { + if (props_.isEmpty()) { + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000020); + } else { + ensurePropsIsMutable(); + props_.addAll(other.props_); + } + onChanged(); + } + } else { + if (!other.props_.isEmpty()) { + if (propsBuilder_.isEmpty()) { + propsBuilder_.dispose(); + propsBuilder_ = null; + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000020); + propsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPropsFieldBuilder() : null; + } else { + propsBuilder_.addAllMessages(other.props_); + } + } + } + if (other.hasDdlSchemaName()) { + bitField0_ |= 0x00000040; + ddlSchemaName_ = other.ddlSchemaName_; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + RowChange parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (RowChange) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private long tableId_ ; + /** + * optional int64 tableId = 1; + * + *
      +       **tableId,由数据库产生*
      +       * 
      + */ + public boolean hasTableId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 tableId = 1; + * + *
      +       **tableId,由数据库产生*
      +       * 
      + */ + public long getTableId() { + return tableId_; + } + /** + * optional int64 tableId = 1; + * + *
      +       **tableId,由数据库产生*
      +       * 
      + */ + public Builder setTableId(long value) { + bitField0_ |= 0x00000001; + tableId_ = value; + onChanged(); + return this; + } + /** + * optional int64 tableId = 1; + * + *
      +       **tableId,由数据库产生*
      +       * 
      + */ + public Builder clearTableId() { + bitField0_ = (bitField0_ & ~0x00000001); + tableId_ = 0L; + onChanged(); + return this; + } + + private EventType eventType_ = EventType.UPDATE; + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public boolean hasEventType() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public EventType getEventType() { + return eventType_; + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public Builder setEventType(EventType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + eventType_ = value; + onChanged(); + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * + *
      +       **数据变更类型*
      +       * 
      + */ + public Builder clearEventType() { + bitField0_ = (bitField0_ & ~0x00000002); + eventType_ = EventType.UPDATE; + onChanged(); + return this; + } + + private boolean isDdl_ ; + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +       ** 标识是否是ddl语句  *
      +       * 
      + */ + public boolean hasIsDdl() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +       ** 标识是否是ddl语句  *
      +       * 
      + */ + public boolean getIsDdl() { + return isDdl_; + } + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +       ** 标识是否是ddl语句  *
      +       * 
      + */ + public Builder setIsDdl(boolean value) { + bitField0_ |= 0x00000004; + isDdl_ = value; + onChanged(); + return this; + } + /** + * optional bool isDdl = 10 [default = false]; + * + *
      +       ** 标识是否是ddl语句  *
      +       * 
      + */ + public Builder clearIsDdl() { + bitField0_ = (bitField0_ & ~0x00000004); + isDdl_ = false; + onChanged(); + return this; + } + + private Object sql_ = ""; + /** + * optional string sql = 11; + * + *
      +       ** ddl/query的sql语句  *
      +       * 
      + */ + public boolean hasSql() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional string sql = 11; + * + *
      +       ** ddl/query的sql语句  *
      +       * 
      + */ + public String getSql() { + Object ref = sql_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + sql_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string sql = 11; + * + *
      +       ** ddl/query的sql语句  *
      +       * 
      + */ + public com.google.protobuf.ByteString + getSqlBytes() { + Object ref = sql_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + sql_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string sql = 11; + * + *
      +       ** ddl/query的sql语句  *
      +       * 
      + */ + public Builder setSql( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + sql_ = value; + onChanged(); + return this; + } + /** + * optional string sql = 11; + * + *
      +       ** ddl/query的sql语句  *
      +       * 
      + */ + public Builder clearSql() { + bitField0_ = (bitField0_ & ~0x00000008); + sql_ = getDefaultInstance().getSql(); + onChanged(); + return this; + } + /** + * optional string sql = 11; + * + *
      +       ** ddl/query的sql语句  *
      +       * 
      + */ + public Builder setSqlBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + sql_ = value; + onChanged(); + return this; + } + + private java.util.List rowDatas_ = + java.util.Collections.emptyList(); + private void ensureRowDatasIsMutable() { + if (!((bitField0_ & 0x00000010) == 0x00000010)) { + rowDatas_ = new java.util.ArrayList(rowDatas_); + bitField0_ |= 0x00000010; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + RowData, RowData.Builder, RowDataOrBuilder> rowDatasBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public java.util.List getRowDatasList() { + if (rowDatasBuilder_ == null) { + return java.util.Collections.unmodifiableList(rowDatas_); + } else { + return rowDatasBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public int getRowDatasCount() { + if (rowDatasBuilder_ == null) { + return rowDatas_.size(); + } else { + return rowDatasBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public RowData getRowDatas(int index) { + if (rowDatasBuilder_ == null) { + return rowDatas_.get(index); + } else { + return rowDatasBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder setRowDatas( + int index, RowData value) { + if (rowDatasBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureRowDatasIsMutable(); + rowDatas_.set(index, value); + onChanged(); + } else { + rowDatasBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder setRowDatas( + int index, RowData.Builder builderForValue) { + if (rowDatasBuilder_ == null) { + ensureRowDatasIsMutable(); + rowDatas_.set(index, builderForValue.build()); + onChanged(); + } else { + rowDatasBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder addRowDatas(RowData value) { + if (rowDatasBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureRowDatasIsMutable(); + rowDatas_.add(value); + onChanged(); + } else { + rowDatasBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder addRowDatas( + int index, RowData value) { + if (rowDatasBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensureRowDatasIsMutable(); + rowDatas_.add(index, value); + onChanged(); + } else { + rowDatasBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder addRowDatas( + RowData.Builder builderForValue) { + if (rowDatasBuilder_ == null) { + ensureRowDatasIsMutable(); + rowDatas_.add(builderForValue.build()); + onChanged(); + } else { + rowDatasBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder addRowDatas( + int index, RowData.Builder builderForValue) { + if (rowDatasBuilder_ == null) { + ensureRowDatasIsMutable(); + rowDatas_.add(index, builderForValue.build()); + onChanged(); + } else { + rowDatasBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder addAllRowDatas( + Iterable values) { + if (rowDatasBuilder_ == null) { + ensureRowDatasIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, rowDatas_); + onChanged(); + } else { + rowDatasBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder clearRowDatas() { + if (rowDatasBuilder_ == null) { + rowDatas_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000010); + onChanged(); + } else { + rowDatasBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public Builder removeRowDatas(int index) { + if (rowDatasBuilder_ == null) { + ensureRowDatasIsMutable(); + rowDatas_.remove(index); + onChanged(); + } else { + rowDatasBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public RowData.Builder getRowDatasBuilder( + int index) { + return getRowDatasFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public RowDataOrBuilder getRowDatasOrBuilder( + int index) { + if (rowDatasBuilder_ == null) { + return rowDatas_.get(index); } else { + return rowDatasBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public java.util.List + getRowDatasOrBuilderList() { + if (rowDatasBuilder_ != null) { + return rowDatasBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(rowDatas_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public RowData.Builder addRowDatasBuilder() { + return getRowDatasFieldBuilder().addBuilder( + RowData.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public RowData.Builder addRowDatasBuilder( + int index) { + return getRowDatasFieldBuilder().addBuilder( + index, RowData.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; + * + *
      +       ** 一次数据库变更可能存在多行  *
      +       * 
      + */ + public java.util.List + getRowDatasBuilderList() { + return getRowDatasFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + RowData, RowData.Builder, RowDataOrBuilder> + getRowDatasFieldBuilder() { + if (rowDatasBuilder_ == null) { + rowDatasBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + RowData, RowData.Builder, RowDataOrBuilder>( + rowDatas_, + ((bitField0_ & 0x00000010) == 0x00000010), + getParentForChildren(), + isClean()); + rowDatas_ = null; + } + return rowDatasBuilder_; + } + + private java.util.List props_ = + java.util.Collections.emptyList(); + private void ensurePropsIsMutable() { + if (!((bitField0_ & 0x00000020) == 0x00000020)) { + props_ = new java.util.ArrayList(props_); + bitField0_ |= 0x00000020; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List getPropsList() { + if (propsBuilder_ == null) { + return java.util.Collections.unmodifiableList(props_); + } else { + return propsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public int getPropsCount() { + if (propsBuilder_ == null) { + return props_.size(); + } else { + return propsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair getProps(int index) { + if (propsBuilder_ == null) { + return props_.get(index); + } else { + return propsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.set(index, value); + onChanged(); + } else { + propsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.set(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps(Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(value); + onChanged(); + } else { + propsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(index, value); + onChanged(); + } else { + propsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addAllProps( + Iterable values) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, props_); + onChanged(); + } else { + propsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder clearProps() { + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000020); + onChanged(); + } else { + propsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder removeProps(int index) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.remove(index); + onChanged(); + } else { + propsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder getPropsBuilder( + int index) { + return getPropsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + if (propsBuilder_ == null) { + return props_.get(index); } else { + return propsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsOrBuilderList() { + if (propsBuilder_ != null) { + return propsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(props_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder() { + return getPropsFieldBuilder().addBuilder( + Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder( + int index) { + return getPropsFieldBuilder().addBuilder( + index, Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsBuilderList() { + return getPropsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> + getPropsFieldBuilder() { + if (propsBuilder_ == null) { + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder>( + props_, + ((bitField0_ & 0x00000020) == 0x00000020), + getParentForChildren(), + isClean()); + props_ = null; + } + return propsBuilder_; + } + + private Object ddlSchemaName_ = ""; + /** + * optional string ddlSchemaName = 14; + * + *
      +       ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +       * 
      + */ + public boolean hasDdlSchemaName() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional string ddlSchemaName = 14; + * + *
      +       ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +       * 
      + */ + public String getDdlSchemaName() { + Object ref = ddlSchemaName_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + ddlSchemaName_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string ddlSchemaName = 14; + * + *
      +       ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +       * 
      + */ + public com.google.protobuf.ByteString + getDdlSchemaNameBytes() { + Object ref = ddlSchemaName_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + ddlSchemaName_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string ddlSchemaName = 14; + * + *
      +       ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +       * 
      + */ + public Builder setDdlSchemaName( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000040; + ddlSchemaName_ = value; + onChanged(); + return this; + } + /** + * optional string ddlSchemaName = 14; + * + *
      +       ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +       * 
      + */ + public Builder clearDdlSchemaName() { + bitField0_ = (bitField0_ & ~0x00000040); + ddlSchemaName_ = getDefaultInstance().getDdlSchemaName(); + onChanged(); + return this; + } + /** + * optional string ddlSchemaName = 14; + * + *
      +       ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      +       * 
      + */ + public Builder setDdlSchemaNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000040; + ddlSchemaName_ = value; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.RowChange) } - public interface TransactionBeginOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.TransactionBegin) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - boolean hasExecuteTime(); - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - long getExecuteTime(); - - /** - * optional string transactionId = 2; - * - *
      -         * *已废弃,Begin里不提供事务id*
      -         * 
      - */ - boolean hasTransactionId(); - - /** - * optional string transactionId = 2; - * - *
      -         * *已废弃,Begin里不提供事务id*
      -         * 
      - */ - java.lang.String getTransactionId(); - - /** - * optional string transactionId = 2; - * - *
      -         * *已废弃,Begin里不提供事务id*
      -         * 
      - */ - com.google.protobuf.ByteString getTransactionIdBytes(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - int getPropsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index); - - /** - * optional int64 threadId = 4; - * - *
      -         * *执行的thread Id*
      -         * 
      - */ - boolean hasThreadId(); - - /** - * optional int64 threadId = 4; - * - *
      -         * *执行的thread Id*
      -         * 
      - */ - long getThreadId(); + static { + defaultInstance = new RowChange(true); + defaultInstance.initFields(); } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowChange) + } + + public interface TransactionBeginOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.TransactionBegin) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + boolean hasExecuteTime(); + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + long getExecuteTime(); + + /** + * optional string transactionId = 2; + * + *
      +     **已废弃,Begin里不提供事务id*
      +     * 
      + */ + boolean hasTransactionId(); + /** + * optional string transactionId = 2; + * + *
      +     **已废弃,Begin里不提供事务id*
      +     * 
      + */ + String getTransactionId(); + /** + * optional string transactionId = 2; + * + *
      +     **已废弃,Begin里不提供事务id*
      +     * 
      + */ + com.google.protobuf.ByteString + getTransactionIdBytes(); + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + Pair getProps(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + int getPropsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + PairOrBuilder getPropsOrBuilder(int index); + + /** + * optional int64 threadId = 4; + * + *
      +     **执行的thread Id*
      +     * 
      + */ + boolean hasThreadId(); + /** + * optional int64 threadId = 4; + * + *
      +     **执行的thread Id*
      +     * 
      + */ + long getThreadId(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionBegin} + * + *
      +   **开始事务的一些信息*
      +   * 
      + */ + public static final class TransactionBegin extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.TransactionBegin) + TransactionBeginOrBuilder { + // Use TransactionBegin.newBuilder() to construct. + private TransactionBegin(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private TransactionBegin(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final TransactionBegin defaultInstance; + public static TransactionBegin getDefaultInstance() { + return defaultInstance; + } + + public TransactionBegin getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private TransactionBegin( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + executeTime_ = input.readInt64(); + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + transactionId_ = bs; + break; + } + case 26: { + if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + props_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; + } + props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + break; + } + case 32: { + bitField0_ |= 0x00000004; + threadId_ = input.readInt64(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + props_ = java.util.Collections.unmodifiableList(props_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable + .ensureFieldAccessorsInitialized( + TransactionBegin.class, Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public TransactionBegin parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TransactionBegin(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + public static final int EXECUTETIME_FIELD_NUMBER = 1; + private long executeTime_; + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + public boolean hasExecuteTime() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + public long getExecuteTime() { + return executeTime_; + } + + public static final int TRANSACTIONID_FIELD_NUMBER = 2; + private Object transactionId_; + /** + * optional string transactionId = 2; + * + *
      +     **已废弃,Begin里不提供事务id*
      +     * 
      + */ + public boolean hasTransactionId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string transactionId = 2; + * + *
      +     **已废弃,Begin里不提供事务id*
      +     * 
      + */ + public String getTransactionId() { + Object ref = transactionId_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + transactionId_ = s; + } + return s; + } + } + /** + * optional string transactionId = 2; + * + *
      +     **已废弃,Begin里不提供事务id*
      +     * 
      + */ + public com.google.protobuf.ByteString + getTransactionIdBytes() { + Object ref = transactionId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + transactionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PROPS_FIELD_NUMBER = 3; + private java.util.List props_; + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List getPropsList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List + getPropsOrBuilderList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public int getPropsCount() { + return props_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public Pair getProps(int index) { + return props_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + return props_.get(index); + } + + public static final int THREADID_FIELD_NUMBER = 4; + private long threadId_; + /** + * optional int64 threadId = 4; + * + *
      +     **执行的thread Id*
      +     * 
      + */ + public boolean hasThreadId() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 threadId = 4; + * + *
      +     **执行的thread Id*
      +     * 
      + */ + public long getThreadId() { + return threadId_; + } + + private void initFields() { + executeTime_ = 0L; + transactionId_ = ""; + props_ = java.util.Collections.emptyList(); + threadId_ = 0L; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt64(1, executeTime_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getTransactionIdBytes()); + } + for (int i = 0; i < props_.size(); i++) { + output.writeMessage(3, props_.get(i)); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt64(4, threadId_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, executeTime_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getTransactionIdBytes()); + } + for (int i = 0; i < props_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, props_.get(i)); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(4, threadId_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static TransactionBegin parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static TransactionBegin parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static TransactionBegin parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static TransactionBegin parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static TransactionBegin parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static TransactionBegin parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static TransactionBegin parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static TransactionBegin parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static TransactionBegin parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static TransactionBegin parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(TransactionBegin prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionBegin} * *
      -     * *开始事务的一些信息*
      +     **开始事务的一些信息*
            * 
      */ - public static final class TransactionBegin extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.TransactionBegin) - TransactionBeginOrBuilder { - - // Use TransactionBegin.newBuilder() to construct. - private TransactionBegin(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private TransactionBegin(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final TransactionBegin defaultInstance; - - public static TransactionBegin getDefaultInstance() { - return defaultInstance; - } - - public TransactionBegin getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private TransactionBegin(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - executeTime_ = input.readInt64(); - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - transactionId_ = bs; - break; - } - case 26: { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - props_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, - extensionRegistry)); - break; - } - case 32: { - bitField0_ |= 0x00000004; - threadId_ = input.readInt64(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = java.util.Collections.unmodifiableList(props_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.class, - com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public TransactionBegin parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new TransactionBegin(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int EXECUTETIME_FIELD_NUMBER = 1; - private long executeTime_; - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - public long getExecuteTime() { - return executeTime_; - } - - public static final int TRANSACTIONID_FIELD_NUMBER = 2; - private java.lang.Object transactionId_; - - /** - * optional string transactionId = 2; - * - *
      -         * *已废弃,Begin里不提供事务id*
      -         * 
      - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string transactionId = 2; - * - *
      -         * *已废弃,Begin里不提供事务id*
      -         * 
      - */ - public java.lang.String getTransactionId() { - java.lang.Object ref = transactionId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } - return s; - } - } - - /** - * optional string transactionId = 2; - * - *
      -         * *已废弃,Begin里不提供事务id*
      -         * 
      - */ - public com.google.protobuf.ByteString getTransactionIdBytes() { - java.lang.Object ref = transactionId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - transactionId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int PROPS_FIELD_NUMBER = 3; - private java.util.List props_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsOrBuilderList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public int getPropsCount() { - return props_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - return props_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - return props_.get(index); - } - - public static final int THREADID_FIELD_NUMBER = 4; - private long threadId_; - - /** - * optional int64 threadId = 4; - * - *
      -         * *执行的thread Id*
      -         * 
      - */ - public boolean hasThreadId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 threadId = 4; - * - *
      -         * *执行的thread Id*
      -         * 
      - */ - public long getThreadId() { - return threadId_; - } - - private void initFields() { - executeTime_ = 0L; - transactionId_ = ""; - props_ = java.util.Collections.emptyList(); - threadId_ = 0L; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt64(1, executeTime_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getTransactionIdBytes()); - } - for (int i = 0; i < props_.size(); i++) { - output.writeMessage(3, props_.get(i)); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt64(4, threadId_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(1, executeTime_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getTransactionIdBytes()); - } - for (int i = 0; i < props_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(3, props_.get(i)); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(4, threadId_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type - * {@code com.alibaba.otter.canal.protocol.TransactionBegin} - * - *
      -         * *开始事务的一些信息*
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.TransactionBegin) - com.alibaba.otter.canal.protocol.CanalEntry.TransactionBeginOrBuilder { + TransactionBeginOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; - } + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable + .ensureFieldAccessorsInitialized( + TransactionBegin.class, Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.class, - com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getPropsFieldBuilder(); - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - executeTime_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - transactionId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - } else { - propsBuilder_.clear(); - } - threadId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000008); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin build() { - com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin result = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.executeTime_ = executeTime_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.transactionId_ = transactionId_; - if (propsBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = java.util.Collections.unmodifiableList(props_); - bitField0_ = (bitField0_ & ~0x00000004); - } - result.props_ = props_; - } else { - result.props_ = propsBuilder_.build(); - } - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000004; - } - result.threadId_ = threadId_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.getDefaultInstance()) return this; - if (other.hasExecuteTime()) { - setExecuteTime(other.getExecuteTime()); - } - if (other.hasTransactionId()) { - bitField0_ |= 0x00000002; - transactionId_ = other.transactionId_; - onChanged(); - } - if (propsBuilder_ == null) { - if (!other.props_.isEmpty()) { - if (props_.isEmpty()) { - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000004); - } else { - ensurePropsIsMutable(); - props_.addAll(other.props_); - } - onChanged(); - } - } else { - if (!other.props_.isEmpty()) { - if (propsBuilder_.isEmpty()) { - propsBuilder_.dispose(); - propsBuilder_ = null; - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000004); - propsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; - } else { - propsBuilder_.addAllMessages(other.props_); - } - } - } - if (other.hasThreadId()) { - setThreadId(other.getThreadId()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private long executeTime_; - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public long getExecuteTime() { - return executeTime_; - } - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public Builder setExecuteTime(long value) { - bitField0_ |= 0x00000001; - executeTime_ = value; - onChanged(); - return this; - } - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public Builder clearExecuteTime() { - bitField0_ = (bitField0_ & ~0x00000001); - executeTime_ = 0L; - onChanged(); - return this; - } - - private java.lang.Object transactionId_ = ""; - - /** - * optional string transactionId = 2; - * - *
      -             * *已废弃,Begin里不提供事务id*
      -             * 
      - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string transactionId = 2; - * - *
      -             * *已废弃,Begin里不提供事务id*
      -             * 
      - */ - public java.lang.String getTransactionId() { - java.lang.Object ref = transactionId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string transactionId = 2; - * - *
      -             * *已废弃,Begin里不提供事务id*
      -             * 
      - */ - public com.google.protobuf.ByteString getTransactionIdBytes() { - java.lang.Object ref = transactionId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - transactionId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string transactionId = 2; - * - *
      -             * *已废弃,Begin里不提供事务id*
      -             * 
      - */ - public Builder setTransactionId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - transactionId_ = value; - onChanged(); - return this; - } - - /** - * optional string transactionId = 2; - * - *
      -             * *已废弃,Begin里不提供事务id*
      -             * 
      - */ - public Builder clearTransactionId() { - bitField0_ = (bitField0_ & ~0x00000002); - transactionId_ = getDefaultInstance().getTransactionId(); - onChanged(); - return this; - } - - /** - * optional string transactionId = 2; - * - *
      -             * *已废弃,Begin里不提供事务id*
      -             * 
      - */ - public Builder setTransactionIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - transactionId_ = value; - onChanged(); - return this; - } - - private java.util.List props_ = java.util.Collections.emptyList(); - - private void ensurePropsIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(props_); - bitField0_ |= 0x00000004; - } - } - - private com.google.protobuf.RepeatedFieldBuilder propsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsList() { - if (propsBuilder_ == null) { - return java.util.Collections.unmodifiableList(props_); - } else { - return propsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public int getPropsCount() { - if (propsBuilder_ == null) { - return props_.size(); - } else { - return propsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.set(index, value); - onChanged(); - } else { - propsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.set(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(value); - onChanged(); - } else { - propsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(index, value); - onChanged(); - } else { - propsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addAllProps(java.lang.Iterable values) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, props_); - onChanged(); - } else { - propsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder clearProps() { - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - } else { - propsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder removeProps(int index) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.remove(index); - onChanged(); - } else { - propsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder(int index) { - return getPropsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsOrBuilderList() { - if (propsBuilder_ != null) { - return propsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(props_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { - return getPropsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder(int index) { - return getPropsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsBuilderList() { - return getPropsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getPropsFieldBuilder() { - if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(props_, - ((bitField0_ & 0x00000004) == 0x00000004), - getParentForChildren(), - isClean()); - props_ = null; - } - return propsBuilder_; - } - - private long threadId_; - - /** - * optional int64 threadId = 4; - * - *
      -             * *执行的thread Id*
      -             * 
      - */ - public boolean hasThreadId() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional int64 threadId = 4; - * - *
      -             * *执行的thread Id*
      -             * 
      - */ - public long getThreadId() { - return threadId_; - } - - /** - * optional int64 threadId = 4; - * - *
      -             * *执行的thread Id*
      -             * 
      - */ - public Builder setThreadId(long value) { - bitField0_ |= 0x00000008; - threadId_ = value; - onChanged(); - return this; - } - - /** - * optional int64 threadId = 4; - * - *
      -             * *执行的thread Id*
      -             * 
      - */ - public Builder clearThreadId() { - bitField0_ = (bitField0_ & ~0x00000008); - threadId_ = 0L; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.TransactionBegin) + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getPropsFieldBuilder(); } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new TransactionBegin(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + executeTime_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + transactionId_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + } else { + propsBuilder_.clear(); } + threadId_ = 0L; + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionBegin) + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; + } + + public TransactionBegin getDefaultInstanceForType() { + return TransactionBegin.getDefaultInstance(); + } + + public TransactionBegin build() { + TransactionBegin result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public TransactionBegin buildPartial() { + TransactionBegin result = new TransactionBegin(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.executeTime_ = executeTime_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.transactionId_ = transactionId_; + if (propsBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { + props_ = java.util.Collections.unmodifiableList(props_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.props_ = props_; + } else { + result.props_ = propsBuilder_.build(); + } + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000004; + } + result.threadId_ = threadId_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof TransactionBegin) { + return mergeFrom((TransactionBegin)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(TransactionBegin other) { + if (other == TransactionBegin.getDefaultInstance()) return this; + if (other.hasExecuteTime()) { + setExecuteTime(other.getExecuteTime()); + } + if (other.hasTransactionId()) { + bitField0_ |= 0x00000002; + transactionId_ = other.transactionId_; + onChanged(); + } + if (propsBuilder_ == null) { + if (!other.props_.isEmpty()) { + if (props_.isEmpty()) { + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensurePropsIsMutable(); + props_.addAll(other.props_); + } + onChanged(); + } + } else { + if (!other.props_.isEmpty()) { + if (propsBuilder_.isEmpty()) { + propsBuilder_.dispose(); + propsBuilder_ = null; + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000004); + propsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPropsFieldBuilder() : null; + } else { + propsBuilder_.addAllMessages(other.props_); + } + } + } + if (other.hasThreadId()) { + setThreadId(other.getThreadId()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + TransactionBegin parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (TransactionBegin) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private long executeTime_ ; + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public boolean hasExecuteTime() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public long getExecuteTime() { + return executeTime_; + } + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public Builder setExecuteTime(long value) { + bitField0_ |= 0x00000001; + executeTime_ = value; + onChanged(); + return this; + } + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public Builder clearExecuteTime() { + bitField0_ = (bitField0_ & ~0x00000001); + executeTime_ = 0L; + onChanged(); + return this; + } + + private Object transactionId_ = ""; + /** + * optional string transactionId = 2; + * + *
      +       **已废弃,Begin里不提供事务id*
      +       * 
      + */ + public boolean hasTransactionId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string transactionId = 2; + * + *
      +       **已废弃,Begin里不提供事务id*
      +       * 
      + */ + public String getTransactionId() { + Object ref = transactionId_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + transactionId_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string transactionId = 2; + * + *
      +       **已废弃,Begin里不提供事务id*
      +       * 
      + */ + public com.google.protobuf.ByteString + getTransactionIdBytes() { + Object ref = transactionId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + transactionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string transactionId = 2; + * + *
      +       **已废弃,Begin里不提供事务id*
      +       * 
      + */ + public Builder setTransactionId( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + transactionId_ = value; + onChanged(); + return this; + } + /** + * optional string transactionId = 2; + * + *
      +       **已废弃,Begin里不提供事务id*
      +       * 
      + */ + public Builder clearTransactionId() { + bitField0_ = (bitField0_ & ~0x00000002); + transactionId_ = getDefaultInstance().getTransactionId(); + onChanged(); + return this; + } + /** + * optional string transactionId = 2; + * + *
      +       **已废弃,Begin里不提供事务id*
      +       * 
      + */ + public Builder setTransactionIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + transactionId_ = value; + onChanged(); + return this; + } + + private java.util.List props_ = + java.util.Collections.emptyList(); + private void ensurePropsIsMutable() { + if (!((bitField0_ & 0x00000004) == 0x00000004)) { + props_ = new java.util.ArrayList(props_); + bitField0_ |= 0x00000004; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List getPropsList() { + if (propsBuilder_ == null) { + return java.util.Collections.unmodifiableList(props_); + } else { + return propsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public int getPropsCount() { + if (propsBuilder_ == null) { + return props_.size(); + } else { + return propsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair getProps(int index) { + if (propsBuilder_ == null) { + return props_.get(index); + } else { + return propsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.set(index, value); + onChanged(); + } else { + propsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.set(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps(Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(value); + onChanged(); + } else { + propsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(index, value); + onChanged(); + } else { + propsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addAllProps( + Iterable values) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, props_); + onChanged(); + } else { + propsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder clearProps() { + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + } else { + propsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder removeProps(int index) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.remove(index); + onChanged(); + } else { + propsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder getPropsBuilder( + int index) { + return getPropsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + if (propsBuilder_ == null) { + return props_.get(index); } else { + return propsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsOrBuilderList() { + if (propsBuilder_ != null) { + return propsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(props_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder() { + return getPropsFieldBuilder().addBuilder( + Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder( + int index) { + return getPropsFieldBuilder().addBuilder( + index, Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsBuilderList() { + return getPropsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> + getPropsFieldBuilder() { + if (propsBuilder_ == null) { + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder>( + props_, + ((bitField0_ & 0x00000004) == 0x00000004), + getParentForChildren(), + isClean()); + props_ = null; + } + return propsBuilder_; + } + + private long threadId_ ; + /** + * optional int64 threadId = 4; + * + *
      +       **执行的thread Id*
      +       * 
      + */ + public boolean hasThreadId() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional int64 threadId = 4; + * + *
      +       **执行的thread Id*
      +       * 
      + */ + public long getThreadId() { + return threadId_; + } + /** + * optional int64 threadId = 4; + * + *
      +       **执行的thread Id*
      +       * 
      + */ + public Builder setThreadId(long value) { + bitField0_ |= 0x00000008; + threadId_ = value; + onChanged(); + return this; + } + /** + * optional int64 threadId = 4; + * + *
      +       **执行的thread Id*
      +       * 
      + */ + public Builder clearThreadId() { + bitField0_ = (bitField0_ & ~0x00000008); + threadId_ = 0L; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.TransactionBegin) } - public interface TransactionEndOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.TransactionEnd) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - boolean hasExecuteTime(); - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - long getExecuteTime(); - - /** - * optional string transactionId = 2; - * - *
      -         * *事务号*
      -         * 
      - */ - boolean hasTransactionId(); - - /** - * optional string transactionId = 2; - * - *
      -         * *事务号*
      -         * 
      - */ - java.lang.String getTransactionId(); - - /** - * optional string transactionId = 2; - * - *
      -         * *事务号*
      -         * 
      - */ - com.google.protobuf.ByteString getTransactionIdBytes(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - int getPropsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - java.util.List getPropsOrBuilderList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index); + static { + defaultInstance = new TransactionBegin(true); + defaultInstance.initFields(); } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionBegin) + } + + public interface TransactionEndOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.TransactionEnd) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + boolean hasExecuteTime(); + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + long getExecuteTime(); + + /** + * optional string transactionId = 2; + * + *
      +     **事务号*
      +     * 
      + */ + boolean hasTransactionId(); + /** + * optional string transactionId = 2; + * + *
      +     **事务号*
      +     * 
      + */ + String getTransactionId(); + /** + * optional string transactionId = 2; + * + *
      +     **事务号*
      +     * 
      + */ + com.google.protobuf.ByteString + getTransactionIdBytes(); + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + Pair getProps(int index); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + int getPropsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + java.util.List + getPropsOrBuilderList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + PairOrBuilder getPropsOrBuilder(int index); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionEnd} + * + *
      +   **结束事务的一些信息*
      +   * 
      + */ + public static final class TransactionEnd extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.TransactionEnd) + TransactionEndOrBuilder { + // Use TransactionEnd.newBuilder() to construct. + private TransactionEnd(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private TransactionEnd(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final TransactionEnd defaultInstance; + public static TransactionEnd getDefaultInstance() { + return defaultInstance; + } + + public TransactionEnd getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private TransactionEnd( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + bitField0_ |= 0x00000001; + executeTime_ = input.readInt64(); + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + transactionId_ = bs; + break; + } + case 26: { + if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + props_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; + } + props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + props_ = java.util.Collections.unmodifiableList(props_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable + .ensureFieldAccessorsInitialized( + TransactionEnd.class, Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public TransactionEnd parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TransactionEnd(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + public static final int EXECUTETIME_FIELD_NUMBER = 1; + private long executeTime_; + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + public boolean hasExecuteTime() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 executeTime = 1; + * + *
      +     **已废弃,请使用header里的executeTime*
      +     * 
      + */ + public long getExecuteTime() { + return executeTime_; + } + + public static final int TRANSACTIONID_FIELD_NUMBER = 2; + private Object transactionId_; + /** + * optional string transactionId = 2; + * + *
      +     **事务号*
      +     * 
      + */ + public boolean hasTransactionId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string transactionId = 2; + * + *
      +     **事务号*
      +     * 
      + */ + public String getTransactionId() { + Object ref = transactionId_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + transactionId_ = s; + } + return s; + } + } + /** + * optional string transactionId = 2; + * + *
      +     **事务号*
      +     * 
      + */ + public com.google.protobuf.ByteString + getTransactionIdBytes() { + Object ref = transactionId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + transactionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PROPS_FIELD_NUMBER = 3; + private java.util.List props_; + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List getPropsList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public java.util.List + getPropsOrBuilderList() { + return props_; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public int getPropsCount() { + return props_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public Pair getProps(int index) { + return props_.get(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +     **预留扩展*
      +     * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + return props_.get(index); + } + + private void initFields() { + executeTime_ = 0L; + transactionId_ = ""; + props_ = java.util.Collections.emptyList(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt64(1, executeTime_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getTransactionIdBytes()); + } + for (int i = 0; i < props_.size(); i++) { + output.writeMessage(3, props_.get(i)); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, executeTime_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getTransactionIdBytes()); + } + for (int i = 0; i < props_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, props_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static TransactionEnd parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static TransactionEnd parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static TransactionEnd parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static TransactionEnd parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static TransactionEnd parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static TransactionEnd parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static TransactionEnd parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static TransactionEnd parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static TransactionEnd parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static TransactionEnd parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(TransactionEnd prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionEnd} * *
      -     * *结束事务的一些信息*
      +     **结束事务的一些信息*
            * 
      */ - public static final class TransactionEnd extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.TransactionEnd) - TransactionEndOrBuilder { - - // Use TransactionEnd.newBuilder() to construct. - private TransactionEnd(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private TransactionEnd(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final TransactionEnd defaultInstance; - - public static TransactionEnd getDefaultInstance() { - return defaultInstance; - } - - public TransactionEnd getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private TransactionEnd(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - executeTime_ = input.readInt64(); - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - transactionId_ = bs; - break; - } - case 26: { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - props_.add(input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, - extensionRegistry)); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = java.util.Collections.unmodifiableList(props_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.class, - com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public TransactionEnd parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new TransactionEnd(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int EXECUTETIME_FIELD_NUMBER = 1; - private long executeTime_; - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 executeTime = 1; - * - *
      -         * *已废弃,请使用header里的executeTime*
      -         * 
      - */ - public long getExecuteTime() { - return executeTime_; - } - - public static final int TRANSACTIONID_FIELD_NUMBER = 2; - private java.lang.Object transactionId_; - - /** - * optional string transactionId = 2; - * - *
      -         * *事务号*
      -         * 
      - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string transactionId = 2; - * - *
      -         * *事务号*
      -         * 
      - */ - public java.lang.String getTransactionId() { - java.lang.Object ref = transactionId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } - return s; - } - } - - /** - * optional string transactionId = 2; - * - *
      -         * *事务号*
      -         * 
      - */ - public com.google.protobuf.ByteString getTransactionIdBytes() { - java.lang.Object ref = transactionId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - transactionId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int PROPS_FIELD_NUMBER = 3; - private java.util.List props_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public java.util.List getPropsOrBuilderList() { - return props_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public int getPropsCount() { - return props_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - return props_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -         * *预留扩展*
      -         * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - return props_.get(index); - } - - private void initFields() { - executeTime_ = 0L; - transactionId_ = ""; - props_ = java.util.Collections.emptyList(); - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt64(1, executeTime_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getTransactionIdBytes()); - } - for (int i = 0; i < props_.size(); i++) { - output.writeMessage(3, props_.get(i)); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(1, executeTime_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getTransactionIdBytes()); - } - for (int i = 0; i < props_.size(); i++) { - size += com.google.protobuf.CodedOutputStream.computeMessageSize(3, props_.get(i)); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionEnd} - * - *
      -         * *结束事务的一些信息*
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.TransactionEnd) - com.alibaba.otter.canal.protocol.CanalEntry.TransactionEndOrBuilder { + TransactionEndOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; - } + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable + .ensureFieldAccessorsInitialized( + TransactionEnd.class, Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.class, - com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getPropsFieldBuilder(); - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - executeTime_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - transactionId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - } else { - propsBuilder_.clear(); - } - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd build() { - com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd result = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.executeTime_ = executeTime_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.transactionId_ = transactionId_; - if (propsBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = java.util.Collections.unmodifiableList(props_); - bitField0_ = (bitField0_ & ~0x00000004); - } - result.props_ = props_; - } else { - result.props_ = propsBuilder_.build(); - } - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.getDefaultInstance()) return this; - if (other.hasExecuteTime()) { - setExecuteTime(other.getExecuteTime()); - } - if (other.hasTransactionId()) { - bitField0_ |= 0x00000002; - transactionId_ = other.transactionId_; - onChanged(); - } - if (propsBuilder_ == null) { - if (!other.props_.isEmpty()) { - if (props_.isEmpty()) { - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000004); - } else { - ensurePropsIsMutable(); - props_.addAll(other.props_); - } - onChanged(); - } - } else { - if (!other.props_.isEmpty()) { - if (propsBuilder_.isEmpty()) { - propsBuilder_.dispose(); - propsBuilder_ = null; - props_ = other.props_; - bitField0_ = (bitField0_ & ~0x00000004); - propsBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; - } else { - propsBuilder_.addAllMessages(other.props_); - } - } - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private long executeTime_; - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public long getExecuteTime() { - return executeTime_; - } - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public Builder setExecuteTime(long value) { - bitField0_ |= 0x00000001; - executeTime_ = value; - onChanged(); - return this; - } - - /** - * optional int64 executeTime = 1; - * - *
      -             * *已废弃,请使用header里的executeTime*
      -             * 
      - */ - public Builder clearExecuteTime() { - bitField0_ = (bitField0_ & ~0x00000001); - executeTime_ = 0L; - onChanged(); - return this; - } - - private java.lang.Object transactionId_ = ""; - - /** - * optional string transactionId = 2; - * - *
      -             * *事务号*
      -             * 
      - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string transactionId = 2; - * - *
      -             * *事务号*
      -             * 
      - */ - public java.lang.String getTransactionId() { - java.lang.Object ref = transactionId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string transactionId = 2; - * - *
      -             * *事务号*
      -             * 
      - */ - public com.google.protobuf.ByteString getTransactionIdBytes() { - java.lang.Object ref = transactionId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - transactionId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string transactionId = 2; - * - *
      -             * *事务号*
      -             * 
      - */ - public Builder setTransactionId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - transactionId_ = value; - onChanged(); - return this; - } - - /** - * optional string transactionId = 2; - * - *
      -             * *事务号*
      -             * 
      - */ - public Builder clearTransactionId() { - bitField0_ = (bitField0_ & ~0x00000002); - transactionId_ = getDefaultInstance().getTransactionId(); - onChanged(); - return this; - } - - /** - * optional string transactionId = 2; - * - *
      -             * *事务号*
      -             * 
      - */ - public Builder setTransactionIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - transactionId_ = value; - onChanged(); - return this; - } - - private java.util.List props_ = java.util.Collections.emptyList(); - - private void ensurePropsIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(props_); - bitField0_ |= 0x00000004; - } - } - - private com.google.protobuf.RepeatedFieldBuilder propsBuilder_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsList() { - if (propsBuilder_ == null) { - return java.util.Collections.unmodifiableList(props_); - } else { - return propsBuilder_.getMessageList(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public int getPropsCount() { - if (propsBuilder_ == null) { - return props_.size(); - } else { - return propsBuilder_.getCount(); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessage(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.set(index, value); - onChanged(); - } else { - propsBuilder_.setMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder setProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.set(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.setMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(value); - onChanged(); - } else { - propsBuilder_.addMessage(value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { - if (propsBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - ensurePropsIsMutable(); - props_.add(index, value); - onChanged(); - } else { - propsBuilder_.addMessage(index, value); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addProps(int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.add(index, builderForValue.build()); - onChanged(); - } else { - propsBuilder_.addMessage(index, builderForValue.build()); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder addAllProps(java.lang.Iterable values) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, props_); - onChanged(); - } else { - propsBuilder_.addAllMessages(values); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder clearProps() { - if (propsBuilder_ == null) { - props_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - } else { - propsBuilder_.clear(); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public Builder removeProps(int index) { - if (propsBuilder_ == null) { - ensurePropsIsMutable(); - props_.remove(index); - onChanged(); - } else { - propsBuilder_.remove(index); - } - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder(int index) { - return getPropsFieldBuilder().getBuilder(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder(int index) { - if (propsBuilder_ == null) { - return props_.get(index); - } else { - return propsBuilder_.getMessageOrBuilder(index); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsOrBuilderList() { - if (propsBuilder_ != null) { - return propsBuilder_.getMessageOrBuilderList(); - } else { - return java.util.Collections.unmodifiableList(props_); - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { - return getPropsFieldBuilder().addBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder(int index) { - return getPropsFieldBuilder().addBuilder(index, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * - *
      -             * *预留扩展*
      -             * 
      - */ - public java.util.List getPropsBuilderList() { - return getPropsFieldBuilder().getBuilderList(); - } - - private com.google.protobuf.RepeatedFieldBuilder getPropsFieldBuilder() { - if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder(props_, - ((bitField0_ & 0x00000004) == 0x00000004), - getParentForChildren(), - isClean()); - props_ = null; - } - return propsBuilder_; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.TransactionEnd) + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getPropsFieldBuilder(); } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new TransactionEnd(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + executeTime_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + transactionId_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + } else { + propsBuilder_.clear(); } + return this; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionEnd) + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; + } + + public TransactionEnd getDefaultInstanceForType() { + return TransactionEnd.getDefaultInstance(); + } + + public TransactionEnd build() { + TransactionEnd result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public TransactionEnd buildPartial() { + TransactionEnd result = new TransactionEnd(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.executeTime_ = executeTime_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.transactionId_ = transactionId_; + if (propsBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { + props_ = java.util.Collections.unmodifiableList(props_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.props_ = props_; + } else { + result.props_ = propsBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof TransactionEnd) { + return mergeFrom((TransactionEnd)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(TransactionEnd other) { + if (other == TransactionEnd.getDefaultInstance()) return this; + if (other.hasExecuteTime()) { + setExecuteTime(other.getExecuteTime()); + } + if (other.hasTransactionId()) { + bitField0_ |= 0x00000002; + transactionId_ = other.transactionId_; + onChanged(); + } + if (propsBuilder_ == null) { + if (!other.props_.isEmpty()) { + if (props_.isEmpty()) { + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensurePropsIsMutable(); + props_.addAll(other.props_); + } + onChanged(); + } + } else { + if (!other.props_.isEmpty()) { + if (propsBuilder_.isEmpty()) { + propsBuilder_.dispose(); + propsBuilder_ = null; + props_ = other.props_; + bitField0_ = (bitField0_ & ~0x00000004); + propsBuilder_ = + com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + getPropsFieldBuilder() : null; + } else { + propsBuilder_.addAllMessages(other.props_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + TransactionEnd parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (TransactionEnd) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private long executeTime_ ; + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public boolean hasExecuteTime() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public long getExecuteTime() { + return executeTime_; + } + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public Builder setExecuteTime(long value) { + bitField0_ |= 0x00000001; + executeTime_ = value; + onChanged(); + return this; + } + /** + * optional int64 executeTime = 1; + * + *
      +       **已废弃,请使用header里的executeTime*
      +       * 
      + */ + public Builder clearExecuteTime() { + bitField0_ = (bitField0_ & ~0x00000001); + executeTime_ = 0L; + onChanged(); + return this; + } + + private Object transactionId_ = ""; + /** + * optional string transactionId = 2; + * + *
      +       **事务号*
      +       * 
      + */ + public boolean hasTransactionId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string transactionId = 2; + * + *
      +       **事务号*
      +       * 
      + */ + public String getTransactionId() { + Object ref = transactionId_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + transactionId_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string transactionId = 2; + * + *
      +       **事务号*
      +       * 
      + */ + public com.google.protobuf.ByteString + getTransactionIdBytes() { + Object ref = transactionId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + transactionId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string transactionId = 2; + * + *
      +       **事务号*
      +       * 
      + */ + public Builder setTransactionId( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + transactionId_ = value; + onChanged(); + return this; + } + /** + * optional string transactionId = 2; + * + *
      +       **事务号*
      +       * 
      + */ + public Builder clearTransactionId() { + bitField0_ = (bitField0_ & ~0x00000002); + transactionId_ = getDefaultInstance().getTransactionId(); + onChanged(); + return this; + } + /** + * optional string transactionId = 2; + * + *
      +       **事务号*
      +       * 
      + */ + public Builder setTransactionIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + transactionId_ = value; + onChanged(); + return this; + } + + private java.util.List props_ = + java.util.Collections.emptyList(); + private void ensurePropsIsMutable() { + if (!((bitField0_ & 0x00000004) == 0x00000004)) { + props_ = new java.util.ArrayList(props_); + bitField0_ |= 0x00000004; + } + } + + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List getPropsList() { + if (propsBuilder_ == null) { + return java.util.Collections.unmodifiableList(props_); + } else { + return propsBuilder_.getMessageList(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public int getPropsCount() { + if (propsBuilder_ == null) { + return props_.size(); + } else { + return propsBuilder_.getCount(); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair getProps(int index) { + if (propsBuilder_ == null) { + return props_.get(index); + } else { + return propsBuilder_.getMessage(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.set(index, value); + onChanged(); + } else { + propsBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder setProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.set(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps(Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(value); + onChanged(); + } else { + propsBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair value) { + if (propsBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePropsIsMutable(); + props_.add(index, value); + onChanged(); + } else { + propsBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addProps( + int index, Pair.Builder builderForValue) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.add(index, builderForValue.build()); + onChanged(); + } else { + propsBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder addAllProps( + Iterable values) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, props_); + onChanged(); + } else { + propsBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder clearProps() { + if (propsBuilder_ == null) { + props_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + } else { + propsBuilder_.clear(); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Builder removeProps(int index) { + if (propsBuilder_ == null) { + ensurePropsIsMutable(); + props_.remove(index); + onChanged(); + } else { + propsBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder getPropsBuilder( + int index) { + return getPropsFieldBuilder().getBuilder(index); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public PairOrBuilder getPropsOrBuilder( + int index) { + if (propsBuilder_ == null) { + return props_.get(index); } else { + return propsBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsOrBuilderList() { + if (propsBuilder_ != null) { + return propsBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(props_); + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder() { + return getPropsFieldBuilder().addBuilder( + Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public Pair.Builder addPropsBuilder( + int index) { + return getPropsFieldBuilder().addBuilder( + index, Pair.getDefaultInstance()); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; + * + *
      +       **预留扩展*
      +       * 
      + */ + public java.util.List + getPropsBuilderList() { + return getPropsFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder> + getPropsFieldBuilder() { + if (propsBuilder_ == null) { + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< + Pair, Pair.Builder, PairOrBuilder>( + props_, + ((bitField0_ & 0x00000004) == 0x00000004), + getParentForChildren(), + isClean()); + props_ = null; + } + return propsBuilder_; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.TransactionEnd) } - public interface PairOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Pair) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string key = 1; - */ - boolean hasKey(); - - /** - * optional string key = 1; - */ - java.lang.String getKey(); - - /** - * optional string key = 1; - */ - com.google.protobuf.ByteString getKeyBytes(); - - /** - * optional string value = 2; - */ - boolean hasValue(); - - /** - * optional string value = 2; - */ - java.lang.String getValue(); - - /** - * optional string value = 2; - */ - com.google.protobuf.ByteString getValueBytes(); + static { + defaultInstance = new TransactionEnd(true); + defaultInstance.initFields(); } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionEnd) + } + + public interface PairOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Pair) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string key = 1; + */ + boolean hasKey(); + /** + * optional string key = 1; + */ + String getKey(); + /** + * optional string key = 1; + */ + com.google.protobuf.ByteString + getKeyBytes(); + + /** + * optional string value = 2; + */ + boolean hasValue(); + /** + * optional string value = 2; + */ + String getValue(); + /** + * optional string value = 2; + */ + com.google.protobuf.ByteString + getValueBytes(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.Pair} + * + *
      +   **预留扩展*
      +   * 
      + */ + public static final class Pair extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Pair) + PairOrBuilder { + // Use Pair.newBuilder() to construct. + private Pair(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private Pair(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final Pair defaultInstance; + public static Pair getDefaultInstance() { + return defaultInstance; + } + + public Pair getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Pair( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + key_ = bs; + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + value_ = bs; + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; + } + + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Pair.class, Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public Pair parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Pair(input, extensionRegistry); + } + }; + + @Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + public static final int KEY_FIELD_NUMBER = 1; + private Object key_; + /** + * optional string key = 1; + */ + public boolean hasKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string key = 1; + */ + public String getKey() { + Object ref = key_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + key_ = s; + } + return s; + } + } + /** + * optional string key = 1; + */ + public com.google.protobuf.ByteString + getKeyBytes() { + Object ref = key_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + key_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int VALUE_FIELD_NUMBER = 2; + private Object value_; + /** + * optional string value = 2; + */ + public boolean hasValue() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string value = 2; + */ + public String getValue() { + Object ref = value_; + if (ref instanceof String) { + return (String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + value_ = s; + } + return s; + } + } + /** + * optional string value = 2; + */ + public com.google.protobuf.ByteString + getValueBytes() { + Object ref = value_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + value_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private void initFields() { + key_ = ""; + value_ = ""; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, getKeyBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getValueBytes()); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, getKeyBytes()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getValueBytes()); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @Override + protected Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static Pair parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Pair parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Pair parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static Pair parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static Pair parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Pair parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static Pair parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static Pair parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static Pair parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static Pair parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(Pair prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @Override + protected Builder newBuilderForType( + BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Pair} * *
      -     * *预留扩展*
      +     **预留扩展*
            * 
      */ - public static final class Pair extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Pair) - PairOrBuilder { - - // Use Pair.newBuilder() to construct. - private Pair(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Pair(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Pair defaultInstance; - - public static Pair getDefaultInstance() { - return defaultInstance; - } - - public Pair getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Pair(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - key_ = bs; - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - value_ = bs; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Pair.class, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Pair parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Pair(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int KEY_FIELD_NUMBER = 1; - private java.lang.Object key_; - - /** - * optional string key = 1; - */ - public boolean hasKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string key = 1; - */ - public java.lang.String getKey() { - java.lang.Object ref = key_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - key_ = s; - } - return s; - } - } - - /** - * optional string key = 1; - */ - public com.google.protobuf.ByteString getKeyBytes() { - java.lang.Object ref = key_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - key_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int VALUE_FIELD_NUMBER = 2; - private java.lang.Object value_; - - /** - * optional string value = 2; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string value = 2; - */ - public java.lang.String getValue() { - java.lang.Object ref = value_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } - return s; - } - } - - /** - * optional string value = 2; - */ - public com.google.protobuf.ByteString getValueBytes() { - java.lang.Object ref = value_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - value_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - key_ = ""; - value_ = ""; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getKeyBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getValueBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getKeyBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getValueBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Pair} - * - *
      -         * *预留扩展*
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Pair) - com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder { + PairOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; - } + protected FieldAccessorTable + internalGetFieldAccessorTable() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable + .ensureFieldAccessorsInitialized( + Pair.class, Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalEntry.Pair.class, - com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Pair.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalEntry.Pair.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - key_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - value_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Pair getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Pair build() { - com.alibaba.otter.canal.protocol.CanalEntry.Pair result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalEntry.Pair buildPartial() { - com.alibaba.otter.canal.protocol.CanalEntry.Pair result = new com.alibaba.otter.canal.protocol.CanalEntry.Pair(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.key_ = key_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.value_ = value_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Pair) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Pair) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Pair other) { - if (other == com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()) return this; - if (other.hasKey()) { - bitField0_ |= 0x00000001; - key_ = other.key_; - onChanged(); - } - if (other.hasValue()) { - bitField0_ |= 0x00000002; - value_ = other.value_; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalEntry.Pair parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Pair) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object key_ = ""; - - /** - * optional string key = 1; - */ - public boolean hasKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string key = 1; - */ - public java.lang.String getKey() { - java.lang.Object ref = key_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - key_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string key = 1; - */ - public com.google.protobuf.ByteString getKeyBytes() { - java.lang.Object ref = key_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - key_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string key = 1; - */ - public Builder setKey(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - key_ = value; - onChanged(); - return this; - } - - /** - * optional string key = 1; - */ - public Builder clearKey() { - bitField0_ = (bitField0_ & ~0x00000001); - key_ = getDefaultInstance().getKey(); - onChanged(); - return this; - } - - /** - * optional string key = 1; - */ - public Builder setKeyBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - key_ = value; - onChanged(); - return this; - } - - private java.lang.Object value_ = ""; - - /** - * optional string value = 2; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string value = 2; - */ - public java.lang.String getValue() { - java.lang.Object ref = value_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string value = 2; - */ - public com.google.protobuf.ByteString getValueBytes() { - java.lang.Object ref = value_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - value_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string value = 2; - */ - public Builder setValue(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - value_ = value; - onChanged(); - return this; - } - - /** - * optional string value = 2; - */ - public Builder clearValue() { - bitField0_ = (bitField0_ & ~0x00000002); - value_ = getDefaultInstance().getValue(); - onChanged(); - return this; - } - - /** - * optional string value = 2; - */ - public Builder setValueBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - value_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Pair) + private Builder( + BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { } + } + private static Builder create() { + return new Builder(); + } - static { - defaultInstance = new Pair(true); - defaultInstance.initFields(); + public Builder clear() { + super.clear(); + key_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + value_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; + } + + public Pair getDefaultInstanceForType() { + return Pair.getDefaultInstance(); + } + + public Pair build() { + Pair result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Pair) + public Pair buildPartial() { + Pair result = new Pair(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.key_ = key_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.value_ = value_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof Pair) { + return mergeFrom((Pair)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(Pair other) { + if (other == Pair.getDefaultInstance()) return this; + if (other.hasKey()) { + bitField0_ |= 0x00000001; + key_ = other.key_; + onChanged(); + } + if (other.hasValue()) { + bitField0_ |= 0x00000002; + value_ = other.value_; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + Pair parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (Pair) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private Object key_ = ""; + /** + * optional string key = 1; + */ + public boolean hasKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string key = 1; + */ + public String getKey() { + Object ref = key_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + key_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string key = 1; + */ + public com.google.protobuf.ByteString + getKeyBytes() { + Object ref = key_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + key_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string key = 1; + */ + public Builder setKey( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + key_ = value; + onChanged(); + return this; + } + /** + * optional string key = 1; + */ + public Builder clearKey() { + bitField0_ = (bitField0_ & ~0x00000001); + key_ = getDefaultInstance().getKey(); + onChanged(); + return this; + } + /** + * optional string key = 1; + */ + public Builder setKeyBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + key_ = value; + onChanged(); + return this; + } + + private Object value_ = ""; + /** + * optional string value = 2; + */ + public boolean hasValue() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string value = 2; + */ + public String getValue() { + Object ref = value_; + if (!(ref instanceof String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + value_ = s; + } + return s; + } else { + return (String) ref; + } + } + /** + * optional string value = 2; + */ + public com.google.protobuf.ByteString + getValueBytes() { + Object ref = value_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (String) ref); + value_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string value = 2; + */ + public Builder setValue( + String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + value_ = value; + onChanged(); + return this; + } + /** + * optional string value = 2; + */ + public Builder clearValue() { + bitField0_ = (bitField0_ & ~0x00000002); + value_ = getDefaultInstance().getValue(); + onChanged(); + return this; + } + /** + * optional string value = 2; + */ + public Builder setValueBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + value_ = value; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Pair) } - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable; - - public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { - return descriptor; - } - - private static com.google.protobuf.Descriptors.FileDescriptor descriptor; static { - java.lang.String[] descriptorData = { - "\n\023EntryProtocol.proto\022 com.alibaba.otter" - + ".canal.protocol\"\236\001\n\005Entry\0228\n\006header\030\001 \001(" - + "\0132(.com.alibaba.otter.canal.protocol.Hea" - + "der\022G\n\tentryType\030\002 \001(\0162+.com.alibaba.ott" - + "er.canal.protocol.EntryType:\007ROWDATA\022\022\n\n" - + "storeValue\030\003 \001(\014\"\221\003\n\006Header\022\022\n\007version\030\001" - + " \001(\005:\0011\022\023\n\013logfileName\030\002 \001(\t\022\025\n\rlogfileO" - + "ffset\030\003 \001(\003\022\020\n\010serverId\030\004 \001(\003\022\024\n\014servere" - + "nCode\030\005 \001(\t\022\023\n\013executeTime\030\006 \001(\003\022A\n\nsour" - + "ceType\030\007 \001(\0162&.com.alibaba.otter.canal.p", - "rotocol.Type:\005MYSQL\022\022\n\nschemaName\030\010 \001(\t\022" - + "\021\n\ttableName\030\t \001(\t\022\023\n\013eventLength\030\n \001(\003\022" - + "F\n\teventType\030\013 \001(\0162+.com.alibaba.otter.c" - + "anal.protocol.EventType:\006UPDATE\0225\n\005props" - + "\030\014 \003(\0132&.com.alibaba.otter.canal.protoco" - + "l.Pair\022\014\n\004gtid\030\r \001(\t\"\326\001\n\006Column\022\r\n\005index" - + "\030\001 \001(\005\022\017\n\007sqlType\030\002 \001(\005\022\014\n\004name\030\003 \001(\t\022\r\n" - + "\005isKey\030\004 \001(\010\022\017\n\007updated\030\005 \001(\010\022\025\n\006isNull\030" - + "\006 \001(\010:\005false\0225\n\005props\030\007 \003(\0132&.com.alibab" - + "a.otter.canal.protocol.Pair\022\r\n\005value\030\010 \001", - "(\t\022\016\n\006length\030\t \001(\005\022\021\n\tmysqlType\030\n \001(\t\"\301\001" - + "\n\007RowData\022?\n\rbeforeColumns\030\001 \003(\0132(.com.a" - + "libaba.otter.canal.protocol.Column\022>\n\014af" - + "terColumns\030\002 \003(\0132(.com.alibaba.otter.can" - + "al.protocol.Column\0225\n\005props\030\003 \003(\0132&.com." - + "alibaba.otter.canal.protocol.Pair\"\222\002\n\tRo" - + "wChange\022\017\n\007tableId\030\001 \001(\003\022F\n\teventType\030\002 " - + "\001(\0162+.com.alibaba.otter.canal.protocol.E" - + "ventType:\006UPDATE\022\024\n\005isDdl\030\n \001(\010:\005false\022\013" - + "\n\003sql\030\013 \001(\t\022;\n\010rowDatas\030\014 \003(\0132).com.alib", - "aba.otter.canal.protocol.RowData\0225\n\005prop" + "s\030\r \003(\0132&.com.alibaba.otter.canal.protoc" - + "ol.Pair\022\025\n\rddlSchemaName\030\016 \001(\t\"\207\001\n\020Trans" - + "actionBegin\022\023\n\013executeTime\030\001 \001(\003\022\025\n\rtran" - + "sactionId\030\002 \001(\t\0225\n\005props\030\003 \003(\0132&.com.ali" - + "baba.otter.canal.protocol.Pair\022\020\n\010thread" - + "Id\030\004 \001(\003\"s\n\016TransactionEnd\022\023\n\013executeTim" - + "e\030\001 \001(\003\022\025\n\rtransactionId\030\002 \001(\t\0225\n\005props\030" - + "\003 \003(\0132&.com.alibaba.otter.canal.protocol" - + ".Pair\"\"\n\004Pair\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(", - "\t*^\n\tEntryType\022\024\n\020TRANSACTIONBEGIN\020\001\022\013\n\007" - + "ROWDATA\020\002\022\022\n\016TRANSACTIONEND\020\003\022\r\n\tHEARTBE" - + "AT\020\004\022\013\n\007GTIDLOG\020\005*\316\001\n\tEventType\022\n\n\006INSER" - + "T\020\001\022\n\n\006UPDATE\020\002\022\n\n\006DELETE\020\003\022\n\n\006CREATE\020\004\022" - + "\t\n\005ALTER\020\005\022\t\n\005ERASE\020\006\022\t\n\005QUERY\020\007\022\014\n\010TRUN" - + "CATE\020\010\022\n\n\006RENAME\020\t\022\n\n\006CINDEX\020\n\022\n\n\006DINDEX" - + "\020\013\022\010\n\004GTID\020\014\022\013\n\007XASTART\020\r\022\t\n\005XAEND\020\016\022\014\n\010" - + "XACOMMIT\020\017\022\016\n\nXAROLLBACK\020\020*(\n\004Type\022\n\n\006OR" - + "ACLE\020\001\022\t\n\005MYSQL\020\002\022\t\n\005PGSQL\020\003B0\n com.alib" - + "aba.otter.canal.protocolB\nCanalEntryH\001" }; - com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { - - public com.google.protobuf.ExtensionRegistry assignDescriptors(com.google.protobuf.Descriptors.FileDescriptor root) { - descriptor = root; - return null; - } - }; - com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom(descriptorData, - new com.google.protobuf.Descriptors.FileDescriptor[] {}, - assigner); - internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor = getDescriptor().getMessageTypes().get(0); - internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor, - new java.lang.String[] { "Header", "EntryType", "StoreValue", }); - internal_static_com_alibaba_otter_canal_protocol_Header_descriptor = getDescriptor().getMessageTypes().get(1); - internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Header_descriptor, - new java.lang.String[] { "Version", "LogfileName", "LogfileOffset", "ServerId", "ServerenCode", - "ExecuteTime", "SourceType", "SchemaName", "TableName", "EventLength", "EventType", "Props", "Gtid", }); - internal_static_com_alibaba_otter_canal_protocol_Column_descriptor = getDescriptor().getMessageTypes().get(2); - internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Column_descriptor, - new java.lang.String[] { "Index", "SqlType", "Name", "IsKey", "Updated", "IsNull", "Props", "Value", - "Length", "MysqlType", }); - internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor = getDescriptor().getMessageTypes().get(3); - internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor, - new java.lang.String[] { "BeforeColumns", "AfterColumns", "Props", }); - internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor = getDescriptor().getMessageTypes() - .get(4); - internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor, - new java.lang.String[] { "TableId", "EventType", "IsDdl", "Sql", "RowDatas", "Props", "DdlSchemaName", }); - internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor = getDescriptor().getMessageTypes() - .get(5); - internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor, - new java.lang.String[] { "ExecuteTime", "TransactionId", "Props", "ThreadId", }); - internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor = getDescriptor().getMessageTypes() - .get(6); - internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor, - new java.lang.String[] { "ExecuteTime", "TransactionId", "Props", }); - internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor = getDescriptor().getMessageTypes().get(7); - internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor, - new java.lang.String[] { "Key", "Value", }); + defaultInstance = new Pair(true); + defaultInstance.initFields(); } - // @@protoc_insertion_point(outer_class_scope) + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Pair) + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + String[] descriptorData = { + "\n\023EntryProtocol.proto\022 com.alibaba.otter" + + ".canal.protocol\"\236\001\n\005Entry\0228\n\006header\030\001 \001(" + + "\0132(.com.alibaba.otter.canal.protocol.Hea" + + "der\022G\n\tentryType\030\002 \001(\0162+.com.alibaba.ott" + + "er.canal.protocol.EntryType:\007ROWDATA\022\022\n\n" + + "storeValue\030\003 \001(\014\"\221\003\n\006Header\022\022\n\007version\030\001" + + " \001(\005:\0011\022\023\n\013logfileName\030\002 \001(\t\022\025\n\rlogfileO" + + "ffset\030\003 \001(\003\022\020\n\010serverId\030\004 \001(\003\022\024\n\014servere" + + "nCode\030\005 \001(\t\022\023\n\013executeTime\030\006 \001(\003\022A\n\nsour" + + "ceType\030\007 \001(\0162&.com.alibaba.otter.canal.p", + "rotocol.Type:\005MYSQL\022\022\n\nschemaName\030\010 \001(\t\022" + + "\021\n\ttableName\030\t \001(\t\022\023\n\013eventLength\030\n \001(\003\022" + + "F\n\teventType\030\013 \001(\0162+.com.alibaba.otter.c" + + "anal.protocol.EventType:\006UPDATE\0225\n\005props" + + "\030\014 \003(\0132&.com.alibaba.otter.canal.protoco" + + "l.Pair\022\014\n\004gtid\030\r \001(\t\"\326\001\n\006Column\022\r\n\005index" + + "\030\001 \001(\005\022\017\n\007sqlType\030\002 \001(\005\022\014\n\004name\030\003 \001(\t\022\r\n" + + "\005isKey\030\004 \001(\010\022\017\n\007updated\030\005 \001(\010\022\025\n\006isNull\030" + + "\006 \001(\010:\005false\0225\n\005props\030\007 \003(\0132&.com.alibab" + + "a.otter.canal.protocol.Pair\022\r\n\005value\030\010 \001", + "(\t\022\016\n\006length\030\t \001(\005\022\021\n\tmysqlType\030\n \001(\t\"\301\001" + + "\n\007RowData\022?\n\rbeforeColumns\030\001 \003(\0132(.com.a" + + "libaba.otter.canal.protocol.Column\022>\n\014af" + + "terColumns\030\002 \003(\0132(.com.alibaba.otter.can" + + "al.protocol.Column\0225\n\005props\030\003 \003(\0132&.com." + + "alibaba.otter.canal.protocol.Pair\"\222\002\n\tRo" + + "wChange\022\017\n\007tableId\030\001 \001(\003\022F\n\teventType\030\002 " + + "\001(\0162+.com.alibaba.otter.canal.protocol.E" + + "ventType:\006UPDATE\022\024\n\005isDdl\030\n \001(\010:\005false\022\013" + + "\n\003sql\030\013 \001(\t\022;\n\010rowDatas\030\014 \003(\0132).com.alib", + "aba.otter.canal.protocol.RowData\0225\n\005prop" + + "s\030\r \003(\0132&.com.alibaba.otter.canal.protoc" + + "ol.Pair\022\025\n\rddlSchemaName\030\016 \001(\t\"\207\001\n\020Trans" + + "actionBegin\022\023\n\013executeTime\030\001 \001(\003\022\025\n\rtran" + + "sactionId\030\002 \001(\t\0225\n\005props\030\003 \003(\0132&.com.ali" + + "baba.otter.canal.protocol.Pair\022\020\n\010thread" + + "Id\030\004 \001(\003\"s\n\016TransactionEnd\022\023\n\013executeTim" + + "e\030\001 \001(\003\022\025\n\rtransactionId\030\002 \001(\t\0225\n\005props\030" + + "\003 \003(\0132&.com.alibaba.otter.canal.protocol" + + ".Pair\"\"\n\004Pair\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(", + "\t*^\n\tEntryType\022\024\n\020TRANSACTIONBEGIN\020\001\022\013\n\007" + + "ROWDATA\020\002\022\022\n\016TRANSACTIONEND\020\003\022\r\n\tHEARTBE" + + "AT\020\004\022\013\n\007GTIDLOG\020\005*\306\001\n\tEventType\022\n\n\006INSER" + + "T\020\001\022\n\n\006UPDATE\020\002\022\n\n\006DELETE\020\003\022\n\n\006CREATE\020\004\022" + + "\t\n\005ALTER\020\005\022\t\n\005ERASE\020\006\022\t\n\005QUERY\020\007\022\014\n\010TRUN" + + "CATE\020\010\022\n\n\006RENAME\020\t\022\n\n\006CINDEX\020\n\022\n\n\006DINDEX" + + "\020\013\022\010\n\004GTID\020\014\022\014\n\010XACOMMIT\020\r\022\016\n\nXAROLLBACK" + + "\020\016\022\016\n\nMHEARTBEAT\020\017*(\n\004Type\022\n\n\006ORACLE\020\001\022\t" + + "\n\005MYSQL\020\002\022\t\n\005PGSQL\020\003B0\n com.alibaba.otte" + + "r.canal.protocolB\nCanalEntryH\001" + }; + com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = + new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { + public com.google.protobuf.ExtensionRegistry assignDescriptors( + com.google.protobuf.Descriptors.FileDescriptor root) { + descriptor = root; + return null; + } + }; + com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }, assigner); + internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor, + new String[] { "Header", "EntryType", "StoreValue", }); + internal_static_com_alibaba_otter_canal_protocol_Header_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Header_descriptor, + new String[] { "Version", "LogfileName", "LogfileOffset", "ServerId", "ServerenCode", "ExecuteTime", "SourceType", "SchemaName", "TableName", "EventLength", "EventType", "Props", "Gtid", }); + internal_static_com_alibaba_otter_canal_protocol_Column_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Column_descriptor, + new String[] { "Index", "SqlType", "Name", "IsKey", "Updated", "IsNull", "Props", "Value", "Length", "MysqlType", }); + internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor = + getDescriptor().getMessageTypes().get(3); + internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor, + new String[] { "BeforeColumns", "AfterColumns", "Props", }); + internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor = + getDescriptor().getMessageTypes().get(4); + internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor, + new String[] { "TableId", "EventType", "IsDdl", "Sql", "RowDatas", "Props", "DdlSchemaName", }); + internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor = + getDescriptor().getMessageTypes().get(5); + internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor, + new String[] { "ExecuteTime", "TransactionId", "Props", "ThreadId", }); + internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor = + getDescriptor().getMessageTypes().get(6); + internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor, + new String[] { "ExecuteTime", "TransactionId", "Props", }); + internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor = + getDescriptor().getMessageTypes().get(7); + internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor, + new String[] { "Key", "Value", }); + } + + // @@protoc_insertion_point(outer_class_scope) } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto b/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto index b6d2ae76..526db41f 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto @@ -194,6 +194,8 @@ enum EventType { /** XA **/ XACOMMIT = 13; XAROLLBACK = 14; + /** MASTER HEARTBEAT **/ + MHEARTBEAT = 15; } /**数据库类型**/ @@ -201,4 +203,4 @@ enum Type { ORACLE = 1; MYSQL = 2; PGSQL = 3; -} \ No newline at end of file +} diff --git a/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java b/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java index 07e9fead..3fed5251 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java @@ -1,6 +1,7 @@ package com.alibaba.otter.canal.store.model; import java.io.Serializable; +import java.util.List; import org.apache.commons.lang.builder.ToStringBuilder; @@ -31,6 +32,7 @@ public class Event implements Serializable { private EventType eventType; private String gtid; private long rawLength; + private int rowsCount; public Event(){ } @@ -47,6 +49,17 @@ public class Event implements Serializable { // build raw this.rawEntry = entry.toByteString(); this.rawLength = rawEntry.size(); + if (entryType == EntryType.ROWDATA) { + List props = entry.getHeader().getPropsList(); + if (props != null) { + for (CanalEntry.Pair p : props) { + if ("rowsCount".equals(p.getKey())) { + rowsCount = Integer.parseInt(p.getValue()); + break; + } + } + } + } } public LogIdentity getLogIdentity() { @@ -129,7 +142,16 @@ public class Event implements Serializable { this.eventType = eventType; } + public int getRowsCount() { + return rowsCount; + } + + public void setRowsCount(int rowsCount) { + this.rowsCount = rowsCount; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } + } From d3f52c6d3d683dd4865ae2ac83431982f3d2a5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 13 Aug 2018 10:16:28 +0800 Subject: [PATCH 067/360] fixed issue #727 , support rds binlog dump --- .../tddl/dbsync/binlog/FileLogFetcher.java | 4 +- deployer/src/main/resources/canal.properties | 5 +- .../resources/example/instance.properties | 5 + .../resources/example/rds_instance.properties | 32 -- .../main/resources/spring/base-instance.xml | 39 +++ .../resources/spring/default-instance.xml | 23 +- .../main/resources/spring/file-instance.xml | 23 +- .../main/resources/spring/group-instance.xml | 25 +- .../main/resources/spring/local-instance.xml | 147 -------- .../main/resources/spring/memory-instance.xml | 23 +- .../exception/ServerIdNotMatchException.java | 2 + .../parse/inbound/AbstractEventParser.java | 14 +- .../parse/inbound/MultiStageCoprocessor.java | 3 +- .../mysql/AbstractMysqlEventParser.java | 8 +- .../inbound/mysql/LocalBinLogConnection.java | 57 +-- .../parse/inbound/mysql/MysqlConnection.java | 25 +- .../parse/inbound/mysql/MysqlEventParser.java | 47 +-- .../mysql/MysqlMultiStageCoprocessor.java | 57 +-- .../inbound/mysql/dbsync/LogEventConvert.java | 22 +- .../inbound/mysql/local/BinLogFileQueue.java | 62 +++- .../mysql/rds/BinlogDownloadQueue.java | 99 +++++- .../parse/inbound/mysql/rds/HttpHelper.java | 1 + .../mysql/rds/RdsBinlogEventParserProxy.java | 161 +++++---- .../inbound/mysql/rds/RdsBinlogOpenApi.java | 330 ++---------------- .../mysql/rds/RdsLocalBinlogEventParser.java | 33 +- .../inbound/mysql/rds/data/BinlogFile.java | 10 +- .../rds/data/DescribeBinlogFileResult.java | 11 +- .../mysql/rds/data/RdsBackupPolicy.java | 19 +- .../parse/inbound/mysql/rds/data/RdsItem.java | 7 + .../mysql/rds/request/AbstractRequest.java | 69 ++-- .../request/DescribeBackupPolicyRequest.java | 7 +- .../request/DescribeBinlogFilesRequest.java | 15 +- .../inbound/mysql/tsdb/MemoryTableMeta.java | 3 +- .../mysql/RdsBinlogEventParserProxyTest.java | 21 +- .../inbound/mysql/RdsBinlogOpenApiTest.java | 27 +- 35 files changed, 553 insertions(+), 883 deletions(-) delete mode 100644 deployer/src/main/resources/example/rds_instance.properties create mode 100644 deployer/src/main/resources/spring/base-instance.xml delete mode 100644 deployer/src/main/resources/spring/local-instance.xml diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java index 0d586cd4..cdb1f685 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java @@ -168,7 +168,9 @@ public final class FileLogFetcher extends LogFetcher { * @see com.taobao.tddl.dbsync.binlog.LogFetcher#close() */ public void close() throws IOException { - if (fin != null) fin.close(); + if (fin != null) { + fin.close(); + } fin = null; } diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 2bff29ae..9083477d 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -66,6 +66,10 @@ canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MO canal.instance.tsdb.dbUsername=canal canal.instance.tsdb.dbPassword=canal +# rds oss binlog account +canal.instance.rds.accesskey = +canal.instance.rds.secretkey = + ################################################# ######### destinations ############# ################################################# @@ -82,7 +86,6 @@ canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml canal.instance.global.mode = spring canal.instance.global.lazy = false #canal.instance.global.manager.address = 127.0.0.1:1099 -#canal.instance.global.spring.xml = classpath:spring/local-instance.xml #canal.instance.global.spring.xml = classpath:spring/memory-instance.xml canal.instance.global.spring.xml = classpath:spring/file-instance.xml #canal.instance.global.spring.xml = classpath:spring/default-instance.xml diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index e0cf0064..f72b65fe 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -12,6 +12,11 @@ canal.instance.master.position= canal.instance.master.timestamp= canal.instance.master.gtid= +# rds oss binlog +canal.instance.rds.accesskey= +canal.instance.rds.secretkey= +canal.instance.rds.instanceId= + # table meta tsdb info canal.instance.tsdb.enable=true #canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb diff --git a/deployer/src/main/resources/example/rds_instance.properties b/deployer/src/main/resources/example/rds_instance.properties deleted file mode 100644 index f04b3832..00000000 --- a/deployer/src/main/resources/example/rds_instance.properties +++ /dev/null @@ -1,32 +0,0 @@ -################################################# -# rds openapi binlog -canal.instance.rds.open.url=https://rds.aliyuncs.com/ -canal.instance.rds.open.accesskey= -canal.instance.rds.open.secretkey= -canal.instance.rds.instanceId= -canal.instance.rds.startTime= -canal.instance.rds.endTime= - -# local binlog dir -canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog -# position info -canal.instance.master.address=127.0.0.1:3306 - -# table meta tsdb info -canal.instance.tsdb.enable=true -canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:} -canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; -#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb -#canal.instance.tsdb.dbUsername=canal -#canal.instance.tsdb.dbPassword=canal - -# username/password -canal.instance.dbUsername=canal -canal.instance.dbPassword=canal -canal.instance.defaultDatabaseName=test -canal.instance.connectionCharset=UTF-8 -# table regex -canal.instance.filter.regex=test\\..* -# table black regex -canal.instance.filter.black.regex= -################################################# \ No newline at end of file diff --git a/deployer/src/main/resources/spring/base-instance.xml b/deployer/src/main/resources/spring/base-instance.xml new file mode 100644 index 00000000..e6c500b5 --- /dev/null +++ b/deployer/src/main/resources/spring/base-instance.xml @@ -0,0 +1,39 @@ + + + + + + + + + + classpath:canal.properties + classpath:${canal.instance.destination:}/instance.properties + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index 6b455c83..07b9109d 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -84,7 +65,7 @@ - + diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 377428a9..58dd32c9 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -70,7 +51,7 @@ - + diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index 44f1ab3b..47d3ba33 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -76,7 +57,7 @@ - + @@ -174,7 +155,7 @@ - + diff --git a/deployer/src/main/resources/spring/local-instance.xml b/deployer/src/main/resources/spring/local-instance.xml deleted file mode 100644 index 930ecb7b..00000000 --- a/deployer/src/main/resources/spring/local-instance.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index cd387b8f..428454db 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -67,7 +48,7 @@ - + diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java index 35a6b790..05baee02 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java @@ -8,6 +8,8 @@ import com.alibaba.otter.canal.common.CanalException; */ public class ServerIdNotMatchException extends CanalException{ + private static final long serialVersionUID = -6124989280379293953L; + public ServerIdNotMatchException(String errorCode) { super(errorCode); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index cd50afa4..fcae1317 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -8,8 +8,6 @@ import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; - import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.math.RandomUtils; @@ -23,6 +21,7 @@ import com.alibaba.otter.canal.filter.CanalEventFilter; import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import com.alibaba.otter.canal.parse.exception.CanalParseException; +import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException; import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback; import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; @@ -97,10 +96,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle protected int parallelBufferSize = 256; // 必须为2的幂 protected MultiStageCoprocessor multiStageCoprocessor; protected ParserExceptionHandler parserExceptionHandler; - protected long serverId; - - - + protected long serverId; protected abstract BinlogParser buildParser(); @@ -179,7 +175,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle erosaConnection.connect();// 链接 long queryServerId = erosaConnection.queryServerId(); - if (queryServerId != 0){ + if (queryServerId != 0) { serverId = queryServerId; } // 4. 获取最后的位置信息 @@ -289,7 +285,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle runningInfo.getAddress().toString()), e); sendAlarm(destination, ExceptionUtils.getFullStackTrace(e)); } - if (parserExceptionHandler!=null){ + if (parserExceptionHandler != null) { parserExceptionHandler.handle(e); } } finally { @@ -441,7 +437,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle return logPosition; } - protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, long startPosition) { + protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, Long startPosition) { if (lastPosition != null) { logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]", lastPosition.getPostion()), diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java index 96e063e9..4ab68e39 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.parse.inbound; import com.alibaba.otter.canal.common.CanalLifeCycle; import com.taobao.tddl.dbsync.binlog.LogBuffer; +import com.taobao.tddl.dbsync.binlog.LogEvent; /** * 针对解析器提供一个多阶段协同的处理 @@ -23,7 +24,7 @@ public interface MultiStageCoprocessor extends CanalLifeCycle { */ public boolean publish(LogBuffer buffer); - public boolean publish(LogBuffer buffer, String binlogFileName); + public boolean publish(LogEvent event); public void reset(); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index db21a9e1..941f5ebc 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -134,10 +134,10 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { protected MultiStageCoprocessor buildMultiStageCoprocessor() { MysqlMultiStageCoprocessor mysqlMultiStageCoprocessor = new MysqlMultiStageCoprocessor(parallelBufferSize, - parallelThreadSize, - (LogEventConvert) binlogParser, - transactionBuffer, - destination); + parallelThreadSize, + (LogEventConvert) binlogParser, + transactionBuffer, + destination); mysqlMultiStageCoprocessor.setEventsPublishBlockingTime(eventsPublishBlockingTime); return mysqlMultiStageCoprocessor; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java index 0358dfda..5cf5bb9d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java @@ -4,25 +4,24 @@ import java.io.File; import java.io.IOException; import java.util.List; -import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; +import com.alibaba.otter.canal.parse.exception.CanalParseException; +import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException; import com.alibaba.otter.canal.parse.inbound.ErosaConnection; import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor; import com.alibaba.otter.canal.parse.inbound.SinkFunction; import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue; import com.taobao.tddl.dbsync.binlog.FileLogFetcher; -import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogContext; import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogPosition; import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent; /** * local bin log connection (not real connection) @@ -40,7 +39,6 @@ public class LocalBinLogConnection implements ErosaConnection { private long serverId; private FileParserListener parserListener; - public LocalBinLogConnection(){ } @@ -92,16 +90,13 @@ public class LocalBinLogConnection implements ErosaConnection { while (running) { boolean needContinue = true; LogEvent event = null; - // 处理一下binlog文件名 - event = new RotateLogEvent(context.getLogPosition().getFileName(), 4); - func.sink(event); while (fetcher.fetch()) { event = decoder.decode(fetcher, context); if (event == null) { continue; } - if (serverId != 0 && event.getServerId() != serverId){ - throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); } if (!func.sink(event)) { @@ -168,9 +163,8 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogEvent event = decoder.decode(fetcher, context); if (event != null) { - - if (serverId != 0 && event.getServerId() != serverId){ - throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); } if (event.getWhen() > timestampSeconds) { @@ -189,6 +183,9 @@ public class LocalBinLogConnection implements ErosaConnection { } else if (LogEvent.XID_EVENT == event.getHeader().getType()) { lastXidLogFilename = current.getName(); lastXidLogFileOffset = event.getLogPos(); + } else if (LogEvent.FORMAT_DESCRIPTION_EVENT == event.getHeader().getType()) { + lastXidLogFilename = current.getName(); + lastXidLogFileOffset = event.getLogPos(); } } } @@ -225,17 +222,29 @@ public class LocalBinLogConnection implements ErosaConnection { @Override public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException { File current = new File(directory, binlogfilename); + if (!current.exists()) { + throw new CanalParseException("binlog:" + binlogfilename + " is not found"); + } FileLogFetcher fetcher = new FileLogFetcher(bufferSize); + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogContext context = new LogContext(); try { fetcher.open(current, binlogPosition); + context.setLogPosition(new LogPosition(binlogfilename, binlogPosition)); while (running) { boolean needContinue = true; + LogEvent event = null; while (fetcher.fetch()) { - LogBuffer buffer = fetcher.duplicate(); - fetcher.consume(fetcher.limit()); - // set filename - if (!coprocessor.publish(buffer, binlogfilename)) { + event = decoder.decode(fetcher, context); + if (event == null) { + continue; + } + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); + } + + if (!coprocessor.publish(event)) { needContinue = false; break; } @@ -244,7 +253,6 @@ public class LocalBinLogConnection implements ErosaConnection { fetcher.close(); // 关闭上一个文件 parserFinish(binlogfilename); if (needContinue) {// 读取下一个 - File nextFile; if (needWait) { nextFile = binlogs.waitForNextFile(current); @@ -272,8 +280,8 @@ public class LocalBinLogConnection implements ErosaConnection { } } - private void parserFinish(String fileName){ - if (parserListener != null){ + private void parserFinish(String fileName) { + if (parserListener != null) { parserListener.onFinish(fileName); } } @@ -306,9 +314,8 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogEvent event = decoder.decode(fetcher, context); if (event != null) { - - if (serverId != 0 && event.getServerId() != serverId){ - throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); } if (event.getWhen() > timestampSeconds) { @@ -327,6 +334,9 @@ public class LocalBinLogConnection implements ErosaConnection { } else if (LogEvent.XID_EVENT == event.getHeader().getType()) { lastXidLogFilename = current.getName(); lastXidLogFileOffset = event.getLogPos(); + } else if (LogEvent.FORMAT_DESCRIPTION_EVENT == event.getHeader().getType()) { + lastXidLogFilename = current.getName(); + lastXidLogFileOffset = event.getLogPos(); } } } @@ -410,7 +420,8 @@ public class LocalBinLogConnection implements ErosaConnection { this.parserListener = parserListener; } - public interface FileParserListener{ + public interface FileParserListener { + void onFinish(String fileName); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index 54483e1f..eb3c0e31 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -13,7 +13,6 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.util.CollectionUtils; import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector; import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor; @@ -40,20 +39,20 @@ import com.taobao.tddl.dbsync.binlog.LogEvent; public class MysqlConnection implements ErosaConnection { - private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class); + 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; + 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秒 - protected int soTimeout = 60 * 60 * 1000; // 1小时 + private AuthenticationInfo authInfo; + protected int connTimeout = 5 * 1000; // 5秒 + protected int soTimeout = 60 * 60 * 1000; // 1小时 // dump binlog bytes, 暂不包括meta与TSDB - private AtomicLong receivedBinlogBytes; + private AtomicLong receivedBinlogBytes; public MysqlConnection(){ } @@ -338,7 +337,7 @@ public class MysqlConnection implements ErosaConnection { public long queryServerId() throws IOException { ResultSetPacket resultSetPacket = query("show variables like 'server_id'"); List fieldValues = resultSetPacket.getFieldValues(); - if (fieldValues == null || fieldValues.size() != 2){ + if (fieldValues == null || fieldValues.size() != 2) { return 0; } return NumberUtils.toLong(fieldValues.get(1)); @@ -478,8 +477,6 @@ public class MysqlConnection implements ErosaConnection { } } - - public static enum BinlogFormat { STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED"); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index fefb1cbe..b585d9ab 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -47,32 +47,32 @@ import com.taobao.tddl.dbsync.binlog.LogEvent; */ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalEventParser, CanalHASwitchable { - private CanalHAController haController = null; + private CanalHAController haController = null; - private int defaultConnectionTimeoutInSeconds = 30; // sotimeout - private int receiveBufferSize = 64 * 1024; - private int sendBufferSize = 64 * 1024; + private int defaultConnectionTimeoutInSeconds = 30; // sotimeout + private int receiveBufferSize = 64 * 1024; + private int sendBufferSize = 64 * 1024; // 数据库信息 - protected AuthenticationInfo masterInfo; // 主库 - protected AuthenticationInfo standbyInfo; // 备库 + protected AuthenticationInfo masterInfo; // 主库 + protected AuthenticationInfo standbyInfo; // 备库 // binlog信息 protected EntryPosition masterPosition; protected EntryPosition standbyPosition; - private long slaveId; // 链接到mysql的slave + private long slaveId; // 链接到mysql的slave // 心跳检查信息 - private String detectingSQL; // 心跳sql - private MysqlConnection metaConnection; // 查询meta信息的链接 - private TableMetaCache tableMetaCache; // 对应meta - private int fallbackIntervalInSeconds = 60; // 切换回退时间 - private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 - private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 + private String detectingSQL; // 心跳sql + private MysqlConnection metaConnection; // 查询meta信息的链接 + private TableMetaCache tableMetaCache; // 对应meta + private int fallbackIntervalInSeconds = 60; // 切换回退时间 + private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 + private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 // update by yishun.chen,特殊异常处理参数 - private int dumpErrorCount = 0; // binlogDump失败异常计数 - private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 + private int dumpErrorCount = 0; // binlogDump失败异常计数 + private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 // instance received binlog bytes - private final AtomicLong receivedBinlogBytes = new AtomicLong(0L); + private final AtomicLong receivedBinlogBytes = new AtomicLong(0L); protected ErosaConnection buildErosaConnection() { return buildMysqlConnection(this.runningInfo); @@ -756,16 +756,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE Long logposTimestamp = entry.getHeader().getExecuteTime(); Long serverId = entry.getHeader().getServerId(); - if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType()) - || CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) { - if (logger.isDebugEnabled()) { - logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] { - logfilename, logfileoffset, logposTimestamp, startTimestamp }); - } - // 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出 - if (logposTimestamp >= startTimestamp) { - return false; - } + // 如果最小的一条记录都不满足条件,可直接退出 + if (logposTimestamp >= startTimestamp) { + return false; } if (StringUtils.equals(endPosition.getJournalName(), logfilename) @@ -914,8 +907,6 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE this.dumpErrorCountThreshold = dumpErrorCountThreshold; } - - public AtomicLong getReceivedBinlogBytes() { return this.receivedBinlogBytes; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 31dfc595..63b0f0eb 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -5,8 +5,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; -import org.apache.commons.lang.StringUtils; - import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; import com.alibaba.otter.canal.common.utils.NamedThreadFactory; import com.alibaba.otter.canal.parse.exception.CanalParseException; @@ -32,7 +30,6 @@ import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogContext; import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogEvent; -import com.taobao.tddl.dbsync.binlog.LogPosition; import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; @@ -53,7 +50,7 @@ import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; */ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor { - private static final int maxFullTimes = 10; + private static final int maxFullTimes = 10; private LogEventConvert logEventConvert; private EventTransactionBuffer transactionBuffer; private ErosaConnection connection; @@ -141,14 +138,18 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement super.stop(); } + public boolean publish(LogBuffer buffer) { + return this.publish(buffer, null); + } + /** * 网络数据投递 */ - public boolean publish(LogBuffer buffer) { - return publish(buffer, null); + public boolean publish(LogEvent event) { + return this.publish(null, event); } - public boolean publish(LogBuffer buffer, String binlogFileName) { + private boolean publish(LogBuffer buffer, LogEvent event) { if (!isStart()) { if (exception != null) { throw exception; @@ -169,10 +170,11 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement do { try { long next = disruptorMsgBuffer.tryNext(); - MessageEvent event = disruptorMsgBuffer.get(next); - event.setBuffer(buffer); - if (binlogFileName != null) { - event.setBinlogFileName(binlogFileName); + MessageEvent data = disruptorMsgBuffer.get(next); + if (buffer != null) { + data.setBuffer(buffer); + } else { + data.setEvent(event); } disruptorMsgBuffer.publish(next); if (fullTimes > 0) { @@ -184,7 +186,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement blockingStart = System.nanoTime(); } // park - //LockSupport.parkNanos(1L); + // LockSupport.parkNanos(1L); applyWait(++fullTimes); interupted = Thread.interrupted(); if (fullTimes % 1000 == 0) { @@ -217,7 +219,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement start(); } - private class SimpleParserStage implements EventHandler, LifecycleAware { private LogDecoder decoder; @@ -230,23 +231,13 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception { try { - LogBuffer buffer = event.getBuffer(); - if (StringUtils.isNotEmpty(event.getBinlogFileName()) - && (context.getLogPosition() == null || !context.getLogPosition() - .getFileName() - .equals(event.getBinlogFileName()))) { - // set roate binlog file name - if (context.getLogPosition() == null) { - context.setLogPosition(new LogPosition(event.getBinlogFileName(), 0)); - } else { - context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition() - .getPosition())); - } + LogEvent logEvent = event.getEvent(); + if (logEvent == null) { + LogBuffer buffer = event.getBuffer(); + logEvent = decoder.decode(buffer, context); + event.setEvent(logEvent); } - LogEvent logEvent = decoder.decode(buffer, context); - event.setEvent(logEvent); - int eventType = logEvent.getHeader().getType(); TableMeta tableMeta = null; boolean needDmlParse = false; @@ -347,7 +338,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement // clear for gc event.setBuffer(null); - event.setBinlogFileName(null); event.setEvent(null); event.setTable(null); event.setEntry(null); @@ -371,21 +361,12 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement class MessageEvent { - private String binlogFileName; // for local binlog parse private LogBuffer buffer; private CanalEntry.Entry entry; private boolean needDmlParse = false; private TableMeta table; private LogEvent event; - public String getBinlogFileName() { - return binlogFileName; - } - - public void setBinlogFileName(String binlogFileName) { - this.binlogFileName = binlogFileName; - } - public LogBuffer getBuffer() { return buffer; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index f5ff93ab..e161e34e 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -10,7 +10,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; -import com.taobao.tddl.dbsync.binlog.event.*; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; @@ -41,7 +40,23 @@ import com.alibaba.otter.canal.protocol.CanalEntry.Type; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.google.protobuf.ByteString; import com.taobao.tddl.dbsync.binlog.LogEvent; +import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent; +import com.taobao.tddl.dbsync.binlog.event.HeartbeatLogEvent; +import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent; +import com.taobao.tddl.dbsync.binlog.event.LogHeader; +import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RandLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer; +import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent; +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent.ColumnInfo; +import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent; +import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent; +import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.XidLogEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; /** @@ -872,11 +887,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) { try { return tableMetaCache.getTableMeta(dbName, tbName, useCache, position); - } catch (Exception e) { + } catch (Throwable e) { String message = ExceptionUtils.getRootCauseMessage(e); if (filterTableError) { if (StringUtils.contains(message, "errorNumber=1146") && StringUtils.contains(message, "doesn't exist")) { return null; + } else if (StringUtils.contains(message, "errorNumber=1142") + && StringUtils.contains(message, "command denied")) { + return null; } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java index 224852cc..eb227303 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java @@ -25,13 +25,14 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException; */ public class BinLogFileQueue { - private String baseName = "mysql-bin."; - private List binlogs = new ArrayList(); - private File directory; - private ReentrantLock lock = new ReentrantLock(); - private Condition nextCondition = lock.newCondition(); - private Timer timer = new Timer(true); - private long reloadInterval = 10 * 1000L; // 10秒 + private String baseName = "mysql-bin."; + private List binlogs = new ArrayList(); + private File directory; + private ReentrantLock lock = new ReentrantLock(); + private Condition nextCondition = lock.newCondition(); + private Timer timer = new Timer(true); + private long reloadInterval = 10 * 1000L; // 10秒 + private CanalParseException exception = null; public BinLogFileQueue(String directory){ this(new File(directory)); @@ -52,9 +53,24 @@ public class BinLogFileQueue { timer.scheduleAtFixedRate(new TimerTask() { public void run() { - List files = listBinlogFiles(); - for (File file : files) { - offer(file); + try { + // File errorFile = new File(BinLogFileQueue.this.directory, + // errorFileName); + // if (errorFile.isFile() && errorFile.exists()) { + // String text = StringUtils.join(IOUtils.readLines(new + // FileInputStream(errorFile)), "\n"); + // exception = new CanalParseException(text); + // } + List files = listBinlogFiles(); + for (File file : files) { + offer(file); + } + } catch (Throwable e) { + exception = new CanalParseException(e); + } + + if (exception != null) { + offer(null); } } }, reloadInterval, reloadInterval); @@ -69,6 +85,10 @@ public class BinLogFileQueue { public File getNextFile(File pre) { try { lock.lockInterruptibly(); + if (exception != null) { + throw exception; + } + if (binlogs.size() == 0) { return null; } else { @@ -94,6 +114,10 @@ public class BinLogFileQueue { public File getBefore(File file) { try { lock.lockInterruptibly(); + if (exception != null) { + throw exception; + } + if (binlogs.size() == 0) { return null; } else { @@ -130,6 +154,9 @@ public class BinLogFileQueue { nextCondition.await();// 等待新文件 } + if (exception != null) { + throw exception; + } if (pre == null) {// 第一次 return binlogs.get(0); } else { @@ -170,13 +197,16 @@ public class BinLogFileQueue { private boolean offer(File file) { try { lock.lockInterruptibly(); - if (!binlogs.contains(file)) { - binlogs.add(file); - nextCondition.signalAll();// 唤醒 - return true; - } else { - return false; + if (file != null) { + if (!binlogs.contains(file)) { + binlogs.add(file); + nextCondition.signalAll();// 唤醒 + return true; + } } + + nextCondition.signalAll();// 唤醒 + return false; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java index d1716e18..e132514b 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java @@ -7,6 +7,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -14,6 +16,9 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import javax.net.ssl.SSLContext; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; @@ -23,8 +28,16 @@ import org.apache.commons.lang.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.TrustStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +55,7 @@ public class BinlogDownloadQueue { private LinkedBlockingQueue downloadQueue = new LinkedBlockingQueue(); private LinkedBlockingQueue taskQueue = new LinkedBlockingQueue(); private LinkedList binlogList; - private final int batchSize; + private final int batchFileSize; private Thread downloadThread; public boolean running = true; private final String destDir; @@ -50,9 +63,9 @@ public class BinlogDownloadQueue { private int currentSize; private String lastDownload; - public BinlogDownloadQueue(List downloadQueue, int batchSize, String destDir) throws IOException{ + public BinlogDownloadQueue(List downloadQueue, int batchFileSize, String destDir) throws IOException{ this.binlogList = new LinkedList(downloadQueue); - this.batchSize = batchSize; + this.batchFileSize = batchFileSize; this.destDir = destDir; this.currentSize = 0; prepareBinlogList(); @@ -83,11 +96,12 @@ public class BinlogDownloadQueue { if (downloadThread != null) { return; } - downloadThread = new Thread(new DownloadThread()); + downloadThread = new Thread(new DownloadThread(), "download-" + destDir); + downloadThread.setDaemon(true); downloadThread.start(); } - public BinlogFile tryOne() throws IOException { + public BinlogFile tryOne() throws Throwable { BinlogFile binlogFile = binlogList.poll(); download(binlogFile); hostId = binlogFile.getHostInstanceID(); @@ -121,7 +135,7 @@ public class BinlogDownloadQueue { } public void prepare() throws InterruptedException { - for (int i = this.currentSize; i < batchSize && !binlogList.isEmpty(); i++) { + for (int i = this.currentSize; i < batchFileSize && !binlogList.isEmpty(); i++) { BinlogFile binlogFile = null; while (!binlogList.isEmpty()) { binlogFile = binlogList.poll(); @@ -150,11 +164,37 @@ public class BinlogDownloadQueue { downloadQueue.clear(); } - private void download(BinlogFile binlogFile) throws IOException { + private void download(BinlogFile binlogFile) throws Throwable { String downloadLink = binlogFile.getDownloadLink(); String fileName = binlogFile.getFileName(); + + downloadLink = downloadLink.trim(); + CloseableHttpClient httpClient = null; + if (downloadLink.startsWith("https")) { + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setMaxConnPerRoute(50); + builder.setMaxConnTotal(100); + // 创建支持忽略证书的https + final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { + + @Override + public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + return true; + } + }).build(); + + httpClient = HttpClientBuilder.create() + .setSSLContext(sslContext) + .setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder. create() + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)) + .build())) + .build(); + } else { + httpClient = HttpClientBuilder.create().setMaxConnPerRoute(50).setMaxConnTotal(100).build(); + } + HttpGet httpGet = new HttpGet(downloadLink); - CloseableHttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(50).setMaxConnTotal(100).build(); RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(TIMEOUT) .setConnectionRequestTimeout(TIMEOUT) @@ -187,6 +227,9 @@ public class BinlogDownloadQueue { String name = tarArchiveEntry.getName(); File tarFile = new File(parentFile, name + ".tmp"); logger.info("start to download file " + tarFile.getName()); + if (tarFile.exists()) { + tarFile.delete(); + } BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(tarFile)); @@ -204,6 +247,10 @@ public class BinlogDownloadQueue { tais.close(); } else { File file = new File(parentFile, fileName + ".tmp"); + if (file.exists()) { + file.delete(); + } + if (!file.isFile()) { file.createNewFile(); } @@ -245,17 +292,45 @@ public class BinlogDownloadQueue { @Override public void run() { while (running) { + BinlogFile binlogFile = null; try { - BinlogFile binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS); + binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS); if (binlogFile != null) { - download(binlogFile); + int retry = 1; + while (true) { + try { + download(binlogFile); + break; + } catch (Throwable e) { + if (retry % 10 == 0) { + retry = retry + 1; + try { + logger.warn("download failed + " + binlogFile.toString() + "], retry : " + + retry, e); + // File errorFile = new File(destDir, + // "error.txt"); + // FileWriter writer = new + // FileWriter(errorFile); + // writer.write(ExceptionUtils.getFullStackTrace(e)); + // writer.flush(); + // IOUtils.closeQuietly(writer); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100 * retry)); + } catch (Throwable e1) { + logger.error("write error failed", e1); + } + } else { + retry = retry + 1; + } + } + } } + Runnable runnable = taskQueue.poll(5000, TimeUnit.MILLISECONDS); if (runnable != null) { runnable.run(); } - } catch (Exception e) { - e.printStackTrace(); + } catch (Throwable e) { + logger.error("task process failed", e); } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java index 7b641ade..9ed865fe 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java @@ -155,6 +155,7 @@ public class HttpHelper { .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)) .build())) .build(); + // ---------------- 创建支持https 的client成功--------- URI uri = new URIBuilder(url).build(); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java index 88caca53..3c38ed0c 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java @@ -4,93 +4,109 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import org.apache.commons.lang.StringUtils; + import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; import com.alibaba.otter.canal.parse.inbound.ParserExceptionHandler; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; /** + * aliyun rds的binlog parser支持 + * + *
      + * 注意点:aliyun的binlog会有定期清理并备份到oss上, 这里实现了一份自动下载oss+rds binlog的机制
      + * 
      + * * @author chengjin.lyf on 2018/7/20 上午10:52 * @since 1.0.25 */ public class RdsBinlogEventParserProxy extends MysqlEventParser { - private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址 - private String accesskey; // 云账号的ak - private String secretkey; // 云账号sk - private String instanceId; // rds实例id - private Long startTime; - private Long endTime; - private String directory; // binlog - // 目录 - private int batchSize = 4; // 最多下载的binlog文件数量 + private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址 + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id + private String directory; // binlog目录 + private int batchFileSize = 4; // 最多下载的binlog文件数量 - private RdsLocalBinlogEventParser rdsBinlogEventParser = new RdsLocalBinlogEventParser(); - private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { + private RdsLocalBinlogEventParser rdsLocalBinlogEventParser = new RdsLocalBinlogEventParser(); + private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "rds-binlog-daemon-thread"); - t.setDaemon(true); - return t; - } - }); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, + "rds-binlog-daemon-thread"); + t.setDaemon(true); + return t; + } + }); @Override public void start() { - final ParserExceptionHandler targetHandler = this.getParserExceptionHandler(); - rdsBinlogEventParser.setLogPositionManager(this.getLogPositionManager()); - rdsBinlogEventParser.setDestination(destination); - rdsBinlogEventParser.setAlarmHandler(this.getAlarmHandler()); - rdsBinlogEventParser.setConnectionCharset(this.connectionCharset); - rdsBinlogEventParser.setConnectionCharsetNumber(this.connectionCharsetNumber); - rdsBinlogEventParser.setEnableTsdb(this.enableTsdb); - rdsBinlogEventParser.setEventBlackFilter(this.eventBlackFilter); - rdsBinlogEventParser.setFilterQueryDcl(this.filterQueryDcl); - rdsBinlogEventParser.setFilterQueryDdl(this.filterQueryDdl); - rdsBinlogEventParser.setFilterQueryDml(this.filterQueryDml); - rdsBinlogEventParser.setFilterRows(this.filterRows); - rdsBinlogEventParser.setFilterTableError(this.filterTableError); - rdsBinlogEventParser.setIsGTIDMode(this.isGTIDMode); - rdsBinlogEventParser.setMasterInfo(this.masterInfo); - rdsBinlogEventParser.setEventFilter(this.eventFilter); - rdsBinlogEventParser.setMasterPosition(this.masterPosition); - rdsBinlogEventParser.setTransactionSize(this.transactionSize); - rdsBinlogEventParser.setUrl(this.rdsOpenApiUrl); - rdsBinlogEventParser.setAccesskey(this.accesskey); - rdsBinlogEventParser.setSecretkey(this.secretkey); - rdsBinlogEventParser.setInstanceId(this.instanceId); - rdsBinlogEventParser.setEventSink(eventSink); - rdsBinlogEventParser.setDirectory(directory); - rdsBinlogEventParser.setBatchSize(batchSize); - rdsBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() { - - @Override - public void onFinish() { - executorService.execute(new Runnable() { - - @Override - public void run() { - rdsBinlogEventParser.stop(); - RdsBinlogEventParserProxy.this.start(); - } - }); - + if (StringUtils.isNotEmpty(accesskey) && StringUtils.isNotEmpty(secretkey) + && StringUtils.isNotEmpty(instanceId)) { + final ParserExceptionHandler targetHandler = this.getParserExceptionHandler(); + if (directory == null) { + directory = System.getProperty("java.io.tmpdir", "/tmp") + "/" + destination; } - }); - this.setParserExceptionHandler(new ParserExceptionHandler() { + rdsLocalBinlogEventParser.setLogPositionManager(this.getLogPositionManager()); + rdsLocalBinlogEventParser.setDestination(destination); + rdsLocalBinlogEventParser.setAlarmHandler(this.getAlarmHandler()); + rdsLocalBinlogEventParser.setConnectionCharset(this.connectionCharset); + rdsLocalBinlogEventParser.setConnectionCharsetNumber(this.connectionCharsetNumber); + rdsLocalBinlogEventParser.setEnableTsdb(this.enableTsdb); + rdsLocalBinlogEventParser.setEventBlackFilter(this.eventBlackFilter); + rdsLocalBinlogEventParser.setFilterQueryDcl(this.filterQueryDcl); + rdsLocalBinlogEventParser.setFilterQueryDdl(this.filterQueryDdl); + rdsLocalBinlogEventParser.setFilterQueryDml(this.filterQueryDml); + rdsLocalBinlogEventParser.setFilterRows(this.filterRows); + rdsLocalBinlogEventParser.setFilterTableError(this.filterTableError); + // rdsLocalBinlogEventParser.setIsGTIDMode(this.isGTIDMode); + rdsLocalBinlogEventParser.setMasterInfo(this.masterInfo); + rdsLocalBinlogEventParser.setEventFilter(this.eventFilter); + rdsLocalBinlogEventParser.setMasterPosition(this.masterPosition); + rdsLocalBinlogEventParser.setTransactionSize(this.transactionSize); + rdsLocalBinlogEventParser.setUrl(this.rdsOpenApiUrl); + rdsLocalBinlogEventParser.setAccesskey(this.accesskey); + rdsLocalBinlogEventParser.setSecretkey(this.secretkey); + rdsLocalBinlogEventParser.setInstanceId(this.instanceId); + rdsLocalBinlogEventParser.setEventSink(eventSink); + rdsLocalBinlogEventParser.setDirectory(directory); + rdsLocalBinlogEventParser.setBatchFileSize(batchFileSize); + rdsLocalBinlogEventParser.setParallel(this.parallel); + rdsLocalBinlogEventParser.setParallelBufferSize(this.parallelBufferSize); + rdsLocalBinlogEventParser.setParallelThreadSize(this.parallelThreadSize); + rdsLocalBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() { + + @Override + public void onFinish() { + executorService.execute(new Runnable() { + + @Override + public void run() { + rdsLocalBinlogEventParser.stop(); + RdsBinlogEventParserProxy.this.start(); + } + }); - @Override - public void handle(Throwable e) { - handleMysqlParserException(e); - if (targetHandler != null) { - targetHandler.handle(e); } - } - }); + }); + this.setParserExceptionHandler(new ParserExceptionHandler() { + + @Override + public void handle(Throwable e) { + handleMysqlParserException(e); + if (targetHandler != null) { + targetHandler.handle(e); + } + } + }); + } + super.start(); } - public void handleMysqlParserException(Throwable throwable) { + private void handleMysqlParserException(Throwable throwable) { if (throwable instanceof PositionNotFoundException) { logger.info("remove rds not found position, try download rds binlog!"); executorService.execute(new Runnable() { @@ -101,12 +117,12 @@ public class RdsBinlogEventParserProxy extends MysqlEventParser { logger.info("stop mysql parser!"); RdsBinlogEventParserProxy rdsBinlogEventParserProxy = RdsBinlogEventParserProxy.this; long serverId = rdsBinlogEventParserProxy.getServerId(); - rdsBinlogEventParser.setServerId(serverId); + rdsLocalBinlogEventParser.setServerId(serverId); rdsBinlogEventParserProxy.stop(); logger.info("start rds mysql binlog parser!"); - rdsBinlogEventParser.start(); - } catch (Exception e) { - e.printStackTrace(); + rdsLocalBinlogEventParser.start(); + } catch (Throwable e) { + logger.info("handle exception failed", e); } } }); @@ -143,7 +159,8 @@ public class RdsBinlogEventParserProxy extends MysqlEventParser { this.directory = directory; } - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; + public void setBatchFileSize(int batchFileSize) { + this.batchFileSize = batchFileSize; } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java index 356a3520..2e79c536 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java @@ -1,51 +1,20 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds; -import io.netty.handler.codec.http.HttpResponseStatus; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import java.util.TreeMap; -import java.util.UUID; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsItem; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBackupPolicyRequest; import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFilesRequest; /** @@ -54,13 +23,7 @@ import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFil */ public class RdsBinlogOpenApi { - protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class); - private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - private static final int TIMEOUT = 10000; - private static final String ENCODING = "UTF-8"; - private static final String MAC_NAME = "HmacSHA1"; - private static final String API_VERSION = "2014-08-15"; - private static final String SIGNATURE_VERSION = "1.0"; + protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class); public static List listBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, Date endTime) { @@ -106,279 +69,32 @@ public class RdsBinlogOpenApi { return Collections.EMPTY_LIST; } - public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, - Date endTime, File destDir) throws Throwable { - int pageSize = 100; - int pageNumber = 0; - int pageRecordCount = 1; - String hostInstanceID = null; - while (pageRecordCount > 0 && pageRecordCount <= pageSize) { - pageNumber += 1; - String result = describeBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime, pageSize, pageNumber); - JSONObject jsobObj = JSON.parseObject(result); - pageRecordCount = jsobObj.getInteger("PageRecordCount"); - - if (pageRecordCount > 0) { - FileUtils.forceMkdir(destDir); - File hostIdFile = new File(destDir, "hostId"); - if (hostIdFile.exists()) { - List lines = IOUtils.readLines(new FileInputStream(hostIdFile)); - hostInstanceID = StringUtils.join(lines, "\n"); - } - - String itemStr = jsobObj.getString("Items"); - JSONObject binLogFileObj = JSONObject.parseObject(itemStr); - JSONArray items = binLogFileObj.getJSONArray("BinLogFile"); - if (items == null || items.isEmpty()) { - continue; - } - for (int i = 0; i < items.size(); i++) { - JSONObject item = (JSONObject) items.get(i); - String oneHostInstanceID = item.getString("HostInstanceID"); - if (hostInstanceID == null) { - hostInstanceID = oneHostInstanceID; - FileOutputStream hostIdFileOut = null; - try { - hostIdFileOut = new FileOutputStream(hostIdFile); - hostIdFileOut.write(oneHostInstanceID.getBytes()); - hostIdFileOut.flush(); - } finally { - IOUtils.closeQuietly(hostIdFileOut); - } - } - - if (hostInstanceID.equals(oneHostInstanceID)) { // 只选择一个host下载 - String downloadLink = item.getString("DownloadLink"); - String fileName = StringUtils.substringBetween(downloadLink, "mysql-bin.", ".tar"); - if (StringUtils.isNotEmpty(fileName)) { - File currentFile = new File(destDir, "mysql-bin." + fileName); - if (currentFile.isFile() && currentFile.exists()) { - // 检查一下文件是否存在,存在就就没必要下载了 - continue; - } - } - - HttpGet httpGet = new HttpGet(downloadLink); - CloseableHttpClient httpClient = HttpClientBuilder.create() - .setMaxConnPerRoute(50) - .setMaxConnTotal(100) - .build(); - RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(TIMEOUT) - .setConnectionRequestTimeout(TIMEOUT) - .setSocketTimeout(TIMEOUT) - .build(); - httpGet.setConfig(requestConfig); - HttpResponse response = httpClient.execute(httpGet); - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode != HttpResponseStatus.OK.code()) { - throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:" - + statusCode); - } - saveFile(destDir, response); - } - } + public static RdsBackupPolicy queryBinlogBackupPolicy(String url, String ak, String sk, String dbInstanceId) { + DescribeBackupPolicyRequest request = new DescribeBackupPolicyRequest(); + if (StringUtils.isNotEmpty(url)) { + try { + URI uri = new URI(url); + request.setEndPoint(uri.getHost()); + } catch (URISyntaxException e) { + logger.error("resolve url host failed, will use default rds endpoint!"); } } - } - - private static void saveFile(File parentFile, HttpResponse response) throws IOException { - InputStream is = response.getEntity().getContent(); - long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue()); - String fileName = response.getFirstHeader("Content-Disposition").getValue(); - fileName = StringUtils.substringAfter(fileName, "filename="); - boolean isTar = StringUtils.endsWith(fileName, ".tar"); - FileUtils.forceMkdir(parentFile); - FileOutputStream fos = null; - try { - if (isTar) { - TarArchiveInputStream tais = new TarArchiveInputStream(is); - TarArchiveEntry tarArchiveEntry = null; - while ((tarArchiveEntry = tais.getNextTarEntry()) != null) { - String name = tarArchiveEntry.getName(); - File tarFile = new File(parentFile, name); - logger.info("start to download file " + tarFile.getName()); - BufferedOutputStream bos = null; - try { - bos = new BufferedOutputStream(new FileOutputStream(tarFile)); - int read = -1; - byte[] buffer = new byte[1024]; - while ((read = tais.read(buffer)) != -1) { - bos.write(buffer, 0, read); - } - logger.info("download file " + tarFile.getName() + " end!"); - } finally { - IOUtils.closeQuietly(bos); - } - } - tais.close(); - } else { - File file = new File(parentFile, fileName); - if (!file.isFile()) { - file.createNewFile(); + request.setRdsInstanceId(dbInstanceId); + request.setAccessKeyId(ak); + request.setAccessKeySecret(sk); + int retryTime = 3; + while (true) { + try { + return request.doAction(); + } catch (Exception e) { + if (retryTime-- <= 0) { + throw new RuntimeException(e); } try { - fos = new FileOutputStream(file); - byte[] buffer = new byte[1024]; - int len; - long copySize = 0; - long nextPrintProgress = 0; - logger.info("start to download file " + file.getName()); - while ((len = is.read(buffer)) != -1) { - fos.write(buffer, 0, len); - copySize += len; - long progress = copySize * 100 / totalSize; - if (progress >= nextPrintProgress) { - logger.info("download " + file.getName() + " progress : " + progress - + "% , download size : " + copySize + ", total size : " + totalSize); - nextPrintProgress += 10; - } - } - logger.info("download file " + file.getName() + " end!"); - fos.flush(); - } finally { - IOUtils.closeQuietly(fos); + Thread.sleep(100L); + } catch (InterruptedException e1) { } } - } finally { - IOUtils.closeQuietly(fos); } } - - public static String describeBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, - Date endTime, int pageSize, int pageNumber) throws Exception { - Map paramMap = new HashMap(); - paramMap.put("Action", "DescribeBinlogFiles"); - paramMap.put("DBInstanceId", dbInstanceId); // rds实例id - paramMap.put("StartTime", formatIso8601Date(startTime)); - paramMap.put("EndTime", formatIso8601Date(endTime)); - paramMap.put("PageSize", String.valueOf(pageSize)); - paramMap.put("PageNumber", String.valueOf(pageNumber)); - return doRequest(url, paramMap, ak, sk); - } - - private static String doRequest(String domin, Map param, String ak, String sk) throws Exception { - param.put("AccessKeyId", ak); - param.put("SignatureMethod", "HMAC-SHA1"); - param.put("SignatureVersion", SIGNATURE_VERSION); - param.put("Version", API_VERSION); - param.put("SignatureNonce", UUID.randomUUID().toString()); - param.put("Format", "JSON"); - param.put("Timestamp", formatIso8601Date(new Date())); - String signStr = generate("POST", param, sk); - param.put("Signature", signStr); - String request = concatQueryString(param); - String url = domin + "?" + request; - String result = HttpHelper.post(url, null, Collections.EMPTY_MAP, TIMEOUT); - return result; - } - - public static String concatQueryString(Map parameters) throws UnsupportedEncodingException { - if (null == parameters) { - return null; - } - StringBuilder urlBuilder = new StringBuilder(""); - for (Map.Entry entry : parameters.entrySet()) { - String key = entry.getKey(); - String val = entry.getValue(); - urlBuilder.append(encode(key)); - if (val != null) { - urlBuilder.append("=").append(encode(val)); - } - urlBuilder.append("&"); - } - int strIndex = urlBuilder.length(); - if (parameters.size() > 0) { - urlBuilder.deleteCharAt(strIndex - 1); - } - return urlBuilder.toString(); - } - - public static String encode(String value) throws UnsupportedEncodingException { - return URLEncoder.encode(value, "UTF-8"); - } - - private static String formatIso8601Date(Date date) { - SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT); - df.setTimeZone(TimeZone.getTimeZone("GMT")); - return df.format(date); - } - - /** - * 使用 HMAC-SHA1 签名方法对对encryptText进行签名 - * - * @param encryptText 被签名的字符串 - * @param encryptKey 密钥 - * @return - * @throws Exception - */ - public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { - byte[] data = encryptKey.getBytes(ENCODING); - // 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称 - SecretKey secretKey = new SecretKeySpec(data, MAC_NAME); - // 生成一个指定 Mac 算法 的 Mac 对象 - Mac mac = Mac.getInstance(MAC_NAME); - // 用给定密钥初始化 Mac 对象 - mac.init(secretKey); - - byte[] text = encryptText.getBytes(ENCODING); - // 完成 Mac 操作 - return mac.doFinal(text); - } - - private static String base64(byte input[]) throws UnsupportedEncodingException { - return new String(Base64.encodeBase64(input), ENCODING); - } - - /** 对参数名称和参数值进行URL编码 **/ - public static String generate(String method, Map parameter, String accessKeySecret) - throws Exception { - String signString = generateSignString(method, parameter); - byte[] signBytes = HmacSHA1Encrypt(signString, accessKeySecret + "&"); - String signature = base64(signBytes); - if ("POST".equals(method)) { - return signature; - } - return URLEncoder.encode(signature, "UTF-8"); - } - - private static String generateQueryString(TreeMap treeMap) { - StringBuilder canonicalizedQueryString = new StringBuilder(); - boolean first = true; - for (String key : treeMap.navigableKeySet()) { - String value = treeMap.get(key); - if (!first) { - canonicalizedQueryString.append("&"); - } - first = false; - canonicalizedQueryString.append(percentEncode(key)).append("=").append(percentEncode(value)); - } - return canonicalizedQueryString.toString(); - } - - public static String generateSignString(String httpMethod, Map parameter) throws IOException { - TreeMap sortParameter = new TreeMap(); - sortParameter.putAll(parameter); - String canonicalizedQueryString = generateQueryString(sortParameter); - if (null == httpMethod) { - throw new RuntimeException("httpMethod can not be empty"); - } - /** 构造待签名的字符串* */ - StringBuilder stringToSign = new StringBuilder(); - stringToSign.append(httpMethod).append("&"); - stringToSign.append(percentEncode("/")).append("&"); - stringToSign.append(percentEncode(canonicalizedQueryString)); - return stringToSign.toString(); - } - - public static String percentEncode(String value) { - try { - return value == null ? null : URLEncoder.encode(value, ENCODING) - .replaceAll("\\+", "%20") - .replaceAll("\\*", "%2A") - .replaceAll("%7E", "~"); - } catch (Exception e) { - } - return ""; - } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java index 05a1935e..aac14d99 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java @@ -28,15 +28,15 @@ import com.alibaba.otter.canal.protocol.position.LogPosition; */ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser, LocalBinLogConnection.FileParserListener { - private String url = "https://rds.aliyuncs.com/"; // openapi地址 - private String accesskey; // 云账号的ak - private String secretkey; // 云账号sk - private String instanceId; // rds实例id + private String url; // openapi地址 + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id private Long startTime; private Long endTime; private BinlogDownloadQueue binlogDownloadQueue; private ParseFinishListener finishListener; - private int batchSize; + private int batchFileSize; public RdsLocalBinlogEventParser(){ } @@ -65,10 +65,9 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements instanceId, new Date(startTime), new Date(endTime)); - binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchSize, directory); + binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchFileSize, directory); binlogDownloadQueue.silenceDownload(); needWait = true; - parallel = false; // try to download one file,use to test server id binlogDownloadQueue.tryOne(); } catch (Throwable e) { @@ -91,10 +90,12 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements binlogDownloadQueue.notifyNotMatch(); try { binlogDownloadQueue.cleanDir(); + binlogDownloadQueue.tryOne(); binlogDownloadQueue.prepare(); - } catch (Exception e) { + } catch (Throwable e) { throw new RuntimeException(e); } + try { binlogDownloadQueue.execute(new Runnable() { @@ -163,8 +164,10 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements } // 处理下logManager位点问题 LogPosition logPosition = logPositionManager.getLatestIndexBy(destination); - EntryPosition position = logPosition.getPostion(); - if (position != null) { + Long timestamp = 0L; + if (logPosition != null && logPosition.getPostion() != null) { + timestamp = logPosition.getPostion().getTimestamp(); + EntryPosition position = logPosition.getPostion(); LogPosition newLogPosition = new LogPosition(); String journalName = position.getJournalName(); int sepIdx = journalName.indexOf("."); @@ -181,9 +184,13 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements } if (binlogDownloadQueue.isLastFile(fileName)) { - logger.info("all file parse complete, switch to mysql parser!"); + logger.warn("last file : " + fileName + " , timestamp : " + timestamp + + " , all file parse complete, switch to mysql parser!"); finishListener.onFinish(); return; + } else { + logger.warn("parse local binlog file : " + fileName + " , timestamp : " + timestamp + + " , try the next binlog !"); } binlogDownloadQueue.prepare(); } catch (Exception e) { @@ -207,7 +214,7 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements void onFinish(); } - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; + public void setBatchFileSize(int batchFileSize) { + this.batchFileSize = batchFileSize; } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java index e6b354d7..bee87521 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java @@ -6,7 +6,7 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; */ public class BinlogFile { - private Long FileSize; + private Long FileSize; private String LogBeginTime; private String LogEndTime; private String DownloadLink; @@ -69,4 +69,12 @@ public class BinlogFile { public void setFileName(String fileName) { this.fileName = fileName; } + + @Override + public String toString() { + return "BinlogFile [FileSize=" + FileSize + ", LogBeginTime=" + LogBeginTime + ", LogEndTime=" + LogEndTime + + ", DownloadLink=" + DownloadLink + ", HostInstanceID=" + HostInstanceID + ", LinkExpiredTime=" + + LinkExpiredTime + ", fileName=" + fileName + "]"; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java index d8d54e44..d5b6820d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java @@ -5,12 +5,13 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; * @since 1.0.25 */ public class DescribeBinlogFileResult { + private RdsItem Items; - private long PageNumber; - private long TotalRecordCount; - private long TotalFileSize; - private String RequestId; - private long PageRecordCount; + private long PageNumber; + private long TotalRecordCount; + private long TotalFileSize; + private String RequestId; + private long PageRecordCount; public RdsItem getItems() { return Items; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java index b6ca7c53..e007671a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java @@ -9,15 +9,16 @@ public class RdsBackupPolicy { /** * 数据备份保留天数(7到730天)。 */ - private String BackupRetentionPeriod; + private String BackupRetentionPeriod; /** * 数据备份时间,格式:HH:mmZ- HH:mm Z。 */ - private String PreferredBackupTime; + private String PreferredBackupTime; /** - * 数据备份周期。Monday:周一;Tuesday:周二;Wednesday:周三;Thursday:周四;Friday:周五;Saturday:周六;Sunday:周日。 + * 数据备份周期。Monday:周一;Tuesday:周二;Wednesday:周三;Thursday:周四;Friday:周五;Saturday: + * 周六;Sunday:周日。 */ - private String PreferredBackupPeriod; + private String PreferredBackupPeriod; /** * 日志备份状态。Enable:开启;Disabled:关闭。 */ @@ -25,7 +26,7 @@ public class RdsBackupPolicy { /** * 日志备份保留天数(7到730天)。 */ - private int LogBackupRetentionPeriod; + private int LogBackupRetentionPeriod; public String getBackupRetentionPeriod() { return BackupRetentionPeriod; @@ -66,4 +67,12 @@ public class RdsBackupPolicy { public void setLogBackupRetentionPeriod(int logBackupRetentionPeriod) { LogBackupRetentionPeriod = logBackupRetentionPeriod; } + + @Override + public String toString() { + return "RdsBackupPolicy [BackupRetentionPeriod=" + BackupRetentionPeriod + ", PreferredBackupTime=" + + PreferredBackupTime + ", PreferredBackupPeriod=" + PreferredBackupPeriod + ", BackupLog=" + BackupLog + + ", LogBackupRetentionPeriod=" + LogBackupRetentionPeriod + "]"; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java index a2cfaffe..81c484e3 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java @@ -7,6 +7,7 @@ import java.util.List; * @since 1.0.25 */ public class RdsItem { + private List BinLogFile; public List getBinLogFile() { @@ -16,4 +17,10 @@ public class RdsItem { public void setBinLogFile(List binLogFile) { BinLogFile = binLogFile; } + + @Override + public String toString() { + return "RdsItem [BinLogFile=" + BinLogFile + "]"; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java index 976968f6..f43523b5 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java @@ -1,12 +1,18 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; +import io.netty.handler.codec.http.HttpResponseStatus; + import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.crypto.Mac; @@ -32,8 +38,6 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; -import io.netty.handler.codec.http.HttpResponseStatus; - /** * @author chengjin.lyf on 2018/8/7 下午2:26 * @since 1.0.25 @@ -49,42 +53,38 @@ public abstract class AbstractRequest { */ private static final String MAC_NAME = "HmacSHA1"; - private String accessKeyId; + private String accessKeyId; - private String accessKeySecret; + private String accessKeySecret; /** - * api 版本 - * + * api 版本 */ - private String version; + private String version; - private String endPoint = "rds.aliyuncs.com"; + private String endPoint = "rds.aliyuncs.com"; - private String protocol = "http"; + private String protocol = "http"; public void setProtocol(String protocol) { this.protocol = protocol; } - private int timeout = (int) TimeUnit.MINUTES.toMillis(1); - + private int timeout = (int) TimeUnit.MINUTES.toMillis(1); private Map treeMap = new TreeMap(); - public void putQueryString(String name, String value){ - if (StringUtils.isBlank(name) || StringUtils.isBlank(value)){ + public void putQueryString(String name, String value) { + if (StringUtils.isBlank(name) || StringUtils.isBlank(value)) { return; } treeMap.put(name, value); } - public void setVersion(String version) { this.version = version; } - public void setEndPoint(String endPoint) { this.endPoint = endPoint; } @@ -169,7 +169,7 @@ public abstract class AbstractRequest { p.put("Format", "JSON"); p.put("Version", version); p.put("AccessKeyId", accessKeyId); - p.put("SignatureMethod", "HMAC-SHA1"); //此处不能用变量 MAC_NAME + p.put("SignatureMethod", "HMAC-SHA1"); // 此处不能用变量 MAC_NAME p.put("Timestamp", formatUTCTZ(new Date())); p.put("SignatureVersion", "1.0"); p.put("SignatureNonce", UUID.randomUUID().toString()); @@ -193,6 +193,7 @@ public abstract class AbstractRequest { * @return * @throws IOException */ + @SuppressWarnings("deprecation") private final HttpResponse executeHttpRequest(HttpGet getMethod, String host) throws Exception { SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() { @@ -202,24 +203,24 @@ public abstract class AbstractRequest { } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, - new String[] { "TLSv1" }, - null, - SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + new String[] { "TLSv1" }, + null, + SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); Registry registry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.INSTANCE) - .register("https", sslsf) - .build(); + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", sslsf) + .build(); HttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry); CloseableHttpClient httpClient = HttpClientBuilder.create() - .setMaxConnPerRoute(50) - .setMaxConnTotal(100) - .setConnectionManager(httpClientConnectionManager) - .build(); + .setMaxConnPerRoute(50) + .setMaxConnTotal(100) + .setConnectionManager(httpClientConnectionManager) + .build(); RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(timeout) - .setConnectionRequestTimeout(timeout) - .setSocketTimeout(timeout) - .build(); + .setConnectTimeout(timeout) + .setConnectionRequestTimeout(timeout) + .setSocketTimeout(timeout) + .build(); getMethod.setConfig(requestConfig); HttpResponse response = httpClient.execute(getMethod); int statusCode = response.getStatusLine().getStatusCode(); @@ -232,14 +233,14 @@ public abstract class AbstractRequest { protected abstract T processResult(HttpResponse response) throws Exception; - protected void processBefore(){ + protected void processBefore() { } - public final T doAction() throws Exception { + public final T doAction() throws Exception { processBefore(); String requestStr = makeRequestString(treeMap); - HttpGet httpGet = new HttpGet(protocol + "://" +endPoint + "?" + requestStr); + HttpGet httpGet = new HttpGet(protocol + "://" + endPoint + "?" + requestStr); HttpResponse response = executeHttpRequest(httpGet, endPoint); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { String result = EntityUtils.toString(response.getEntity()); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java index 0fbdfc47..cf51961a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java @@ -1,27 +1,26 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; import org.apache.http.HttpResponse; import org.apache.http.util.EntityUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; /** * rds 备份策略查询 + * * @author chengjin.lyf on 2018/8/7 下午3:41 * @since 1.0.25 */ public class DescribeBackupPolicyRequest extends AbstractRequest { - - public DescribeBackupPolicyRequest() { + public DescribeBackupPolicyRequest(){ setVersion("2014-08-15"); putQueryString("Action", "DescribeBackupPolicy"); } - public void setRdsInstanceId(String rdsInstanceId) { putQueryString("DBInstanceId", rdsInstanceId); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java index 1c91c255..8aa90569 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java @@ -2,12 +2,12 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; import java.util.Date; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; import org.apache.http.HttpResponse; import org.apache.http.util.EntityUtils; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; /** * @author chengjin.lyf on 2018/8/7 下午3:41 @@ -15,11 +15,9 @@ import com.alibaba.fastjson.TypeReference; */ public class DescribeBinlogFilesRequest extends AbstractRequest { - - public DescribeBinlogFilesRequest() { + public DescribeBinlogFilesRequest(){ setVersion("2014-08-15"); putQueryString("Action", "DescribeBinlogFiles"); - } public void setRdsInstanceId(String rdsInstanceId) { @@ -35,11 +33,11 @@ public class DescribeBinlogFilesRequest extends AbstractRequest() { - }); + DescribeBinlogFileResult describeBinlogFileResult = JSONObject.parseObject(result, + new TypeReference() { + }); return describeBinlogFileResult; } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java index 9a886336..904b55a9 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java @@ -68,7 +68,8 @@ public class MemoryTableMeta implements TableMetaTSDB { try { // druid暂时flush privileges语法解析有问题 - if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush")) { + if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush") + && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "grant")) { repository.console(ddl); } } catch (Throwable e) { diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java index 986d4d6a..546ee58c 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java @@ -8,6 +8,7 @@ import java.util.concurrent.atomic.AtomicLong; import org.junit.Assert; import org.junit.Test; +import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.parse.helper.TimeoutChecker; import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy; import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager; @@ -28,10 +29,9 @@ public class RdsBinlogEventParserProxyTest { private static final String MYSQL_ADDRESS = ""; private static final String USERNAME = ""; private static final String PASSWORD = ""; - public static final String DBNAME = ""; - public static final String TBNAME = ""; - public static final String DDL = ""; - + public static final String DBNAME = ""; + public static final String TBNAME = ""; + public static final String DDL = ""; @Test public void test_timestamp() throws InterruptedException { @@ -41,7 +41,7 @@ public class RdsBinlogEventParserProxyTest { final RdsBinlogEventParserProxy controller = new RdsBinlogEventParserProxy(); Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.DAY_OF_YEAR, -1); + calendar.add(Calendar.HOUR_OF_DAY, -24 * 4); final EntryPosition defaultPosition = buildPosition(null, null, calendar.getTimeInMillis()); controller.setSlaveId(3344L); controller.setDetectingEnable(false); @@ -51,13 +51,15 @@ public class RdsBinlogEventParserProxyTest { controller.setInstanceId(""); controller.setAccesskey(""); controller.setSecretkey(""); - controller.setBatchSize(4); -// controller.setRdsOpenApiUrl("https://rds.aliyuncs.com/"); + controller.setDirectory("/tmp/binlog"); + controller.setEventBlackFilter(new AviaterRegexFilter("mysql\\.*")); + controller.setFilterTableError(true); + controller.setBatchFileSize(4); controller.setEventSink(new AbstractCanalEventSinkTest>() { @Override public boolean sink(List entrys, InetSocketAddress remoteAddress, String destination) - throws CanalSinkException { + throws CanalSinkException { for (CanalEntry.Entry entry : entrys) { if (entry.getEntryType() != CanalEntry.EntryType.HEARTBEAT) { entryCount.incrementAndGet(); @@ -79,8 +81,8 @@ public class RdsBinlogEventParserProxyTest { controller.setLogPositionManager(new AbstractLogPositionManager() { private LogPosition logPosition; + public void persistLogPosition(String destination, LogPosition logPosition) { - System.out.println(logPosition); this.logPosition = logPosition; } @@ -105,7 +107,6 @@ public class RdsBinlogEventParserProxyTest { Assert.assertTrue(entryPosition.getTimestamp() <= defaultPosition.getTimestamp()); } - // ======================== helper method ======================= private EntryPosition buildPosition(String binlogFile, Long offest, Long timestamp) { diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java index 596847ae..25a14284 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java @@ -1,11 +1,14 @@ package com.alibaba.otter.canal.parse.inbound.mysql; -import java.io.File; import java.util.Date; +import java.util.List; import org.apache.commons.lang.time.DateUtils; +import org.junit.Test; import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; /** * @author agapple 2017年10月15日 下午2:14:34 @@ -13,15 +16,19 @@ import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi; */ public class RdsBinlogOpenApiTest { + @Test public void testSimple() throws Throwable { - Date startTime = DateUtils.parseDate("2017-10-13 20:56:58", new String[] { "yyyy-MM-dd HH:mm:ss" }); - Date endTime = DateUtils.parseDate("2017-10-14 02:57:59", new String[] { "yyyy-MM-dd HH:mm:ss" }); - RdsBinlogOpenApi.downloadBinlogFiles("https://rds.aliyuncs.com/", - "", - "", - "rm-bp180v4mfjnm157es", - startTime, - endTime, - new File("/tmp/binlog/")); + Date startTime = DateUtils.parseDate("2018-08-10 12:00:00", new String[] { "yyyy-MM-dd HH:mm:ss" }); + Date endTime = DateUtils.parseDate("2018-08-11 12:00:00", new String[] { "yyyy-MM-dd HH:mm:ss" }); + String url = "https://rds.aliyuncs.com/"; + String ak = ""; + String sk = ""; + String dbInstanceId = ""; + + RdsBackupPolicy backupPolicy = RdsBinlogOpenApi.queryBinlogBackupPolicy(url, ak, sk, dbInstanceId); + System.out.println(backupPolicy); + + List binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime); + System.out.println(binlogFiles); } } From a3bf1d59e64441394b0f91cae933a6f846846615 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Mon, 13 Aug 2018 21:03:30 +0800 Subject: [PATCH 068/360] Add size label for store metrics. --- deployer/src/main/resources/canal.properties | 1 + .../canal/prometheus/impl/ParserCollector.java | 1 - .../otter/canal/prometheus/impl/StoreCollector.java | 6 ++++-- .../store/memory/MemoryEventStoreWithBuffer.java | 13 ++++++++----- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 2bff29ae..0fc65c9b 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -4,6 +4,7 @@ canal.id= 1 canal.ip= canal.port=11111 +canal.metrics.pull.port=11112 canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java index 73102795..c1883658 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java @@ -21,7 +21,6 @@ import java.util.concurrent.atomic.AtomicLong; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; -import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * @author Chuanyi Li diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index 3be87c3e..d345602c 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -40,7 +40,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance"; private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); - private final List storeLabelsList = Arrays.asList(DEST, "batchMode"); + private final List storeLabelsList = Arrays.asList(DEST, "batchMode", "size"); private StoreCollector() {} @@ -100,7 +100,8 @@ public class StoreCollector extends Collector implements InstanceRegistry { holder.putSeq = memStore.getPutSequence(); holder.ackSeq = memStore.getAckSequence(); holder.destLabelValues = Collections.singletonList(destination); - holder.storeLabelValues = Arrays.asList(destination, memStore.getBatchMode().name()); + holder.size = memStore.getBufferSize(); + holder.storeLabelValues = Arrays.asList(destination, holder.batchMode.name(), Integer.toString(holder.size)); Preconditions.checkNotNull(holder.batchMode); Preconditions.checkNotNull(holder.putSeq); Preconditions.checkNotNull(holder.ackSeq); @@ -128,6 +129,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private BatchMode batchMode; private AtomicLong putMemSize; private AtomicLong ackMemSize; + private int size; private List destLabelValues; private List storeLabelValues; } diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index 00ba2e71..ef97e206 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -545,6 +545,9 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } // ================ setter / getter ================== + public int getBufferSize() { + return this.bufferSize; + } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; @@ -563,22 +566,22 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } public AtomicLong getPutSequence() { - return putSequence; + return this.putSequence; } public AtomicLong getAckSequence() { - return ackSequence; + return this.ackSequence; } public AtomicLong getPutMemSize() { - return putMemSize; + return this.putMemSize; } public AtomicLong getAckMemSize() { - return ackMemSize; + return this.ackMemSize; } public BatchMode getBatchMode() { - return batchMode; + return this.batchMode; } } From e8aee3a276b0c8339f015c1e4f30b93c2c183d09 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Mon, 13 Aug 2018 21:50:50 +0800 Subject: [PATCH 069/360] Configurable metrics server port. --- .../alibaba/otter/canal/deployer/CanalConstants.java | 1 + .../otter/canal/deployer/CanalController.java | 8 ++++++++ instance/spring/src/test/resources/canal.properties | 2 +- .../otter/canal/prometheus/PrometheusService.java | 12 +++++++++--- .../server/embedded/CanalServerWithEmbedded.java | 6 ++++++ .../alibaba/otter/canal/spi/CanalMetricsService.java | 5 +++++ .../otter/canal/spi/NopCanalMetricsService.java | 5 +++++ 7 files changed, 35 insertions(+), 4 deletions(-) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index b328feb7..c7a90d30 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -15,6 +15,7 @@ public class CanalConstants { public static final String CANAL_ID = ROOT + "." + "id"; public static final String CANAL_IP = ROOT + "." + "ip"; public static final String CANAL_PORT = ROOT + "." + "port"; + public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port"; public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers"; public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty"; diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 6b0fc934..53a2b9b5 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -98,6 +98,14 @@ public class CanalController { port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT)); embededCanalServer = CanalServerWithEmbedded.instance(); embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator + try { + int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT)); + embededCanalServer.setMetricsPort(metricsPort); + } catch (NumberFormatException e) { + logger.info("No valid metrics server port found, use default 11112."); + embededCanalServer.setMetricsPort(11112); + } + String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { canalServer = CanalServerWithNetty.instance(); diff --git a/instance/spring/src/test/resources/canal.properties b/instance/spring/src/test/resources/canal.properties index 1887781d..ffb5cf0e 100644 --- a/instance/spring/src/test/resources/canal.properties +++ b/instance/spring/src/test/resources/canal.properties @@ -51,4 +51,4 @@ canal.instance.global.mode = spring canal.instance.global.lazy = false #canal.instance.global.manager.address = 127.0.0.1:1099 canal.instance.global.spring.xml = classpath:spring/memory-instance.xml -#canal.instance.global.spring.xml = classpath:spring/default-instance.xml \ No newline at end of file +#canal.instance.global.spring.xml = classpath:spring/default-instance.xml diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 485f6728..18bf63f7 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -22,6 +22,7 @@ public class PrometheusService implements CanalMetricsService { private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); private final CanalInstanceExports instanceExports; private volatile boolean running = false; + private int port; private HTTPServer server; private final ClientInstanceProfiler clientProfiler; @@ -41,9 +42,9 @@ public class PrometheusService implements CanalMetricsService { @Override public void initialize() { try { - //TODO 1.Configurable port - //TODO 2.Https - server = new HTTPServer(11112); + logger.info("Start prometheus HTTPServer on port {}.", port); + //TODO 2.Https? + server = new HTTPServer(port); } catch (IOException e) { logger.warn("Unable to start prometheus HTTPServer.", e); return; @@ -112,4 +113,9 @@ public class PrometheusService implements CanalMetricsService { logger.info("Unregister metrics for destination {}.", instance.getDestination()); } + @Override + public void setServerPort(int port) { + this.port = port; + } + } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index ccf21020..9ac5a834 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -44,6 +44,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C private Map canalInstances; // private Map lastRollbackPostions; private CanalInstanceGenerator canalInstanceGenerator; + private int metricsPort; private CanalMetricsService metrics = NopCanalMetricsService.NOP; private static class SingletonHolder { @@ -64,6 +65,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C super.start(); // 如果存在provider,则启动metrics service loadCanalMetrics(); + metrics.setServerPort(metricsPort); metrics.initialize(); canalInstances = MigrateMap.makeComputingMap(new Function() { @@ -529,4 +531,8 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C this.canalInstanceGenerator = canalInstanceGenerator; } + public void setMetricsPort(int metricsPort) { + this.metricsPort = metricsPort; + } + } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java index 6d823be8..a4528800 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java @@ -39,4 +39,9 @@ public interface CanalMetricsService { */ void unregister(CanalInstance instance); + /** + * @param port server port for pull + */ + void setServerPort(int port); + } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java index 0d6ad088..45926348 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java @@ -35,4 +35,9 @@ public class NopCanalMetricsService implements CanalMetricsService { public void unregister(CanalInstance instance) { } + + @Override + public void setServerPort(int port) { + + } } From 11d4761bcb89077c6af358d95aa82d870cf069af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 14 Aug 2018 13:23:24 +0800 Subject: [PATCH 070/360] fixed docker build --- .../otter/canal/deployer/CanalConstants.java | 1 - .../otter/canal/deployer/CanalController.java | 8 ---- deployer/src/main/resources/canal.properties | 1 - docker/Dockerfile | 35 +--------------- docker/base/Dockerfile | 41 +++++++++++++++++++ docker/build.sh | 22 +++++----- docker/image/admin/app.sh | 4 +- docker/image/admin/health.sh | 9 +++- .../src/test/resources/canal.properties | 2 +- .../canal/prometheus/PrometheusService.java | 12 ++---- .../prometheus/impl/ParserCollector.java | 1 + .../canal/prometheus/impl/StoreCollector.java | 6 +-- .../embedded/CanalServerWithEmbedded.java | 6 --- .../otter/canal/spi/CanalMetricsService.java | 5 --- .../canal/spi/NopCanalMetricsService.java | 5 --- .../memory/MemoryEventStoreWithBuffer.java | 13 +++--- 16 files changed, 74 insertions(+), 97 deletions(-) create mode 100644 docker/base/Dockerfile diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index c7a90d30..b328feb7 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -15,7 +15,6 @@ public class CanalConstants { public static final String CANAL_ID = ROOT + "." + "id"; public static final String CANAL_IP = ROOT + "." + "ip"; public static final String CANAL_PORT = ROOT + "." + "port"; - public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port"; public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers"; public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty"; diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 53a2b9b5..6b0fc934 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -98,14 +98,6 @@ public class CanalController { port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT)); embededCanalServer = CanalServerWithEmbedded.instance(); embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator - try { - int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT)); - embededCanalServer.setMetricsPort(metricsPort); - } catch (NumberFormatException e) { - logger.info("No valid metrics server port found, use default 11112."); - embededCanalServer.setMetricsPort(11112); - } - String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { canalServer = CanalServerWithNetty.instance(); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 1cefb7d9..9083477d 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -4,7 +4,6 @@ canal.id= 1 canal.ip= canal.port=11111 -canal.metrics.pull.port=11112 canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 diff --git a/docker/Dockerfile b/docker/Dockerfile index 2867b955..835e881a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,32 +1,7 @@ -FROM centos:centos6.7 +FROM canal/osbase:v1 MAINTAINER agapple (jianghang115@gmail.com) -# install system -RUN \ - /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo 'root:Hello1234' | chpasswd && \ - groupadd -r admin && useradd -g admin admin && \ - yum install -y man && \ - yum install -y dstat && \ - yum install -y unzip && \ - yum install -y nc && \ - yum install -y openssh-server && \ - yum install -y tar && \ - yum install -y which && \ - yum install -y wget && \ - yum install -y perl && \ - yum install -y file && \ - ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \ - ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \ - sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \ - sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \ - mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \ - yum install -y cronie && \ - sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \ - yum clean all && \ - true - # install canal COPY image/ /tmp/docker/ COPY canal.deployer-*.tar.gz /home/admin/ @@ -39,14 +14,6 @@ RUN \ cp -R /tmp/docker/admin/* /home/admin/ && \ /bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \ - touch /var/lib/rpm/* && \ - yum -y install /tmp/jdk-8-linux-x64.rpm && \ - /bin/rm -f /tmp/jdk-8-linux-x64.rpm && \ - - echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \ - echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \ - /bin/mv /home/admin/bin/clean_log /etc/cron.d && \ - mkdir -p /home/admin/canal-server && \ tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \ /bin/rm -f /home/admin/canal.deployer-*.tar.gz && \ diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile new file mode 100644 index 00000000..010b97e0 --- /dev/null +++ b/docker/base/Dockerfile @@ -0,0 +1,41 @@ +FROM centos:centos6.10 + +MAINTAINER agapple (jianghang115@gmail.com) + +env DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.rpm" +# install system +RUN \ + /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo 'root:Hello1234' | chpasswd && \ + groupadd -r admin && useradd -g admin admin && \ + yum install -y man && \ + yum install -y dstat && \ + yum install -y unzip && \ + yum install -y nc && \ + yum install -y openssh-server && \ + yum install -y tar && \ + yum install -y which && \ + yum install -y wget && \ + yum install -y perl && \ + yum install -y file && \ + ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \ + ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \ + sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \ + sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \ + mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \ + yum install -y cronie && \ + sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \ + true + +RUN \ + touch /var/lib/rpm/* && \ + wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O /tmp/jdk-8-linux-x64.rpm && \ + yum -y install /tmp/jdk-8-linux-x64.rpm && \ + /bin/rm -f /tmp/jdk-8-linux-x64.rpm && \ + + echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \ + echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \ + yum clean all && \ + true + +CMD ["/bin/bash"] diff --git a/docker/build.sh b/docker/build.sh index 58424fb3..eab5b2b3 100644 --- a/docker/build.sh +++ b/docker/build.sh @@ -14,17 +14,15 @@ case "`uname`" in esac BASE=${bin_abs_path} -if [ ! -f $BASE/jdk*.rpm ] ; then - DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz" - wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm -fi - -cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; - -if [ "$1" == "kafka" ] ; then - cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ - docker build --no-cache -t canal/canal-server $BASE/ +if [ "$1" == "base" ] ; then + docker build --no-cache -t canal/osbase $BASE/base else - cp $BASE/../target/canal.deployer-*.tar.gz $BASE/ - docker build --no-cache -t canal/canal-server $BASE/ + cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; + if [ "$1" == "kafka" ] ; then + cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ + else + cp $BASE/../target/canal.deployer-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ + fi fi diff --git a/docker/image/admin/app.sh b/docker/image/admin/app.sh index 254f7e48..66928ac3 100755 --- a/docker/image/admin/app.sh +++ b/docker/image/admin/app.sh @@ -86,7 +86,9 @@ function start_canal() { echo "multi destination:$destination is not support" exit 1; else - mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination + if [ "$destination" != "" ] && [ "$destination" != "example" ] ; then + mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination + fi fi su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1' sleep 5 diff --git a/docker/image/admin/health.sh b/docker/image/admin/health.sh index 17a89740..c48e6d9d 100755 --- a/docker/image/admin/health.sh +++ b/docker/image/admin/health.sh @@ -1,6 +1,11 @@ #!/bin/sh -CHECK_URL="http://127.0.0.1:8080/metrics" -CHECK_POINT="success" +metrics_port=`perl -le 'print $ENV{"canal.metrics.pull.port"}'` +if [ "$metrics_port" == "" ]; then + metrics_port="11112" +fi + +CHECK_URL="http://127.0.0.1:$metrics_port/metrics" +CHECK_POINT="canal" CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT` if [ $CHECK_COUNT -eq 0 ]; then echo "[FAILED]" diff --git a/instance/spring/src/test/resources/canal.properties b/instance/spring/src/test/resources/canal.properties index ffb5cf0e..1887781d 100644 --- a/instance/spring/src/test/resources/canal.properties +++ b/instance/spring/src/test/resources/canal.properties @@ -51,4 +51,4 @@ canal.instance.global.mode = spring canal.instance.global.lazy = false #canal.instance.global.manager.address = 127.0.0.1:1099 canal.instance.global.spring.xml = classpath:spring/memory-instance.xml -#canal.instance.global.spring.xml = classpath:spring/default-instance.xml +#canal.instance.global.spring.xml = classpath:spring/default-instance.xml \ No newline at end of file diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 18bf63f7..485f6728 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -22,7 +22,6 @@ public class PrometheusService implements CanalMetricsService { private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); private final CanalInstanceExports instanceExports; private volatile boolean running = false; - private int port; private HTTPServer server; private final ClientInstanceProfiler clientProfiler; @@ -42,9 +41,9 @@ public class PrometheusService implements CanalMetricsService { @Override public void initialize() { try { - logger.info("Start prometheus HTTPServer on port {}.", port); - //TODO 2.Https? - server = new HTTPServer(port); + //TODO 1.Configurable port + //TODO 2.Https + server = new HTTPServer(11112); } catch (IOException e) { logger.warn("Unable to start prometheus HTTPServer.", e); return; @@ -113,9 +112,4 @@ public class PrometheusService implements CanalMetricsService { logger.info("Unregister metrics for destination {}.", instance.getDestination()); } - @Override - public void setServerPort(int port) { - this.port = port; - } - } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java index c1883658..73102795 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicLong; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; +import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * @author Chuanyi Li diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index d345602c..3be87c3e 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -40,7 +40,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance"; private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); - private final List storeLabelsList = Arrays.asList(DEST, "batchMode", "size"); + private final List storeLabelsList = Arrays.asList(DEST, "batchMode"); private StoreCollector() {} @@ -100,8 +100,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { holder.putSeq = memStore.getPutSequence(); holder.ackSeq = memStore.getAckSequence(); holder.destLabelValues = Collections.singletonList(destination); - holder.size = memStore.getBufferSize(); - holder.storeLabelValues = Arrays.asList(destination, holder.batchMode.name(), Integer.toString(holder.size)); + holder.storeLabelValues = Arrays.asList(destination, memStore.getBatchMode().name()); Preconditions.checkNotNull(holder.batchMode); Preconditions.checkNotNull(holder.putSeq); Preconditions.checkNotNull(holder.ackSeq); @@ -129,7 +128,6 @@ public class StoreCollector extends Collector implements InstanceRegistry { private BatchMode batchMode; private AtomicLong putMemSize; private AtomicLong ackMemSize; - private int size; private List destLabelValues; private List storeLabelValues; } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index 9ac5a834..ccf21020 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -44,7 +44,6 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C private Map canalInstances; // private Map lastRollbackPostions; private CanalInstanceGenerator canalInstanceGenerator; - private int metricsPort; private CanalMetricsService metrics = NopCanalMetricsService.NOP; private static class SingletonHolder { @@ -65,7 +64,6 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C super.start(); // 如果存在provider,则启动metrics service loadCanalMetrics(); - metrics.setServerPort(metricsPort); metrics.initialize(); canalInstances = MigrateMap.makeComputingMap(new Function() { @@ -531,8 +529,4 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C this.canalInstanceGenerator = canalInstanceGenerator; } - public void setMetricsPort(int metricsPort) { - this.metricsPort = metricsPort; - } - } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java index a4528800..6d823be8 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java @@ -39,9 +39,4 @@ public interface CanalMetricsService { */ void unregister(CanalInstance instance); - /** - * @param port server port for pull - */ - void setServerPort(int port); - } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java index 45926348..0d6ad088 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java @@ -35,9 +35,4 @@ public class NopCanalMetricsService implements CanalMetricsService { public void unregister(CanalInstance instance) { } - - @Override - public void setServerPort(int port) { - - } } diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index ef97e206..00ba2e71 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -545,9 +545,6 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } // ================ setter / getter ================== - public int getBufferSize() { - return this.bufferSize; - } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; @@ -566,22 +563,22 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } public AtomicLong getPutSequence() { - return this.putSequence; + return putSequence; } public AtomicLong getAckSequence() { - return this.ackSequence; + return ackSequence; } public AtomicLong getPutMemSize() { - return this.putMemSize; + return putMemSize; } public AtomicLong getAckMemSize() { - return this.ackMemSize; + return ackMemSize; } public BatchMode getBatchMode() { - return this.batchMode; + return batchMode; } } From 8a74504fe3ec5c149b896734d052bb5798b73462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 14 Aug 2018 13:29:03 +0800 Subject: [PATCH 071/360] preapre for 1.1.0 release --- client/pom.xml | 2 +- common/pom.xml | 2 +- dbsync/pom.xml | 2 +- deployer/pom.xml | 2 +- docker/Dockerfile | 1 - driver/pom.xml | 2 +- example/pom.xml | 2 +- filter/pom.xml | 2 +- instance/core/pom.xml | 2 +- instance/manager/pom.xml | 2 +- instance/pom.xml | 2 +- instance/spring/pom.xml | 2 +- kafka-client/pom.xml | 4 ++-- kafka/pom.xml | 4 ++-- meta/pom.xml | 2 +- parse/pom.xml | 2 +- pom.xml | 2 +- prometheus/pom.xml | 4 ++-- protocol/pom.xml | 2 +- server/pom.xml | 2 +- sink/pom.xml | 2 +- store/pom.xml | 2 +- 22 files changed, 24 insertions(+), 25 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index c736b141..038f2a9f 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/common/pom.xml b/common/pom.xml index 9c57c560..afa3372c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml canal.common diff --git a/dbsync/pom.xml b/dbsync/pom.xml index 1f274e82..17ec6c30 100644 --- a/dbsync/pom.xml +++ b/dbsync/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/deployer/pom.xml b/deployer/pom.xml index 68224ca1..2de1ce1e 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/docker/Dockerfile b/docker/Dockerfile index 835e881a..311ac0b5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,6 @@ MAINTAINER agapple (jianghang115@gmail.com) # install canal COPY image/ /tmp/docker/ COPY canal.deployer-*.tar.gz /home/admin/ -COPY jdk-8-linux-x64.rpm /tmp/ RUN \ cp -R /tmp/docker/alidata /alidata && \ diff --git a/driver/pom.xml b/driver/pom.xml index edd24e57..648e64cc 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/example/pom.xml b/example/pom.xml index 2897e821..4f13d017 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/filter/pom.xml b/filter/pom.xml index 30b53e28..f662f5ea 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/instance/core/pom.xml b/instance/core/pom.xml index cbfab2fd..e3812d4e 100644 --- a/instance/core/pom.xml +++ b/instance/core/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../../pom.xml canal.instance.core diff --git a/instance/manager/pom.xml b/instance/manager/pom.xml index 10f01e43..79340bd0 100644 --- a/instance/manager/pom.xml +++ b/instance/manager/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../../pom.xml com.alibaba.otter diff --git a/instance/pom.xml b/instance/pom.xml index aeec17cb..f45baee1 100644 --- a/instance/pom.xml +++ b/instance/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/instance/spring/pom.xml b/instance/spring/pom.xml index 6a8f172a..116dee77 100644 --- a/instance/spring/pom.xml +++ b/instance/spring/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../../pom.xml com.alibaba.otter diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml index d814be52..5444af5d 100644 --- a/kafka-client/pom.xml +++ b/kafka-client/pom.xml @@ -6,7 +6,7 @@ canal com.alibaba.otter - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter @@ -108,4 +108,4 @@ - \ No newline at end of file + diff --git a/kafka/pom.xml b/kafka/pom.xml index 8d3b35d7..d46a2d03 100644 --- a/kafka/pom.xml +++ b/kafka/pom.xml @@ -6,7 +6,7 @@ canal com.alibaba.otter - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter @@ -137,4 +137,4 @@ - \ No newline at end of file + diff --git a/meta/pom.xml b/meta/pom.xml index 6192d92d..93c4ad7b 100644 --- a/meta/pom.xml +++ b/meta/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/parse/pom.xml b/parse/pom.xml index 994d06f0..fb93fc99 100644 --- a/parse/pom.xml +++ b/parse/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml canal.parse diff --git a/pom.xml b/pom.xml index 56ac821a..3101aca6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ canal pom canal module for otter ${project.version} - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT https://github.com/alibaba/canal org.sonatype.oss diff --git a/prometheus/pom.xml b/prometheus/pom.xml index b814c7e6..7d192834 100644 --- a/prometheus/pom.xml +++ b/prometheus/pom.xml @@ -5,13 +5,13 @@ canal com.alibaba.otter - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT 4.0.0 com.alibaba.otter canal.prometheus - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT canal prometheus module for otter ${project.version} diff --git a/protocol/pom.xml b/protocol/pom.xml index ec7c7867..94715d15 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/server/pom.xml b/server/pom.xml index 1d243981..b7f83d56 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml canal.server diff --git a/sink/pom.xml b/sink/pom.xml index 4ed7e04f..9f136d35 100644 --- a/sink/pom.xml +++ b/sink/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/store/pom.xml b/store/pom.xml index 318c2f9d..9dee6737 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter From 13291c8d7bd76c71cfdf3e1a058409393ee16e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 14 Aug 2018 13:38:01 +0800 Subject: [PATCH 072/360] fixed docker build --- docker/build.sh | 1 + docker/run.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/build.sh b/docker/build.sh index eab5b2b3..6b42e3a4 100644 --- a/docker/build.sh +++ b/docker/build.sh @@ -17,6 +17,7 @@ BASE=${bin_abs_path} if [ "$1" == "base" ] ; then docker build --no-cache -t canal/osbase $BASE/base else + rm -rf $BASE/canal.*.tar.gz ; cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; if [ "$1" == "kafka" ] ; then cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ diff --git a/docker/run.sh b/docker/run.sh index 7c56e2ba..3d5f7659 100644 --- a/docker/run.sh +++ b/docker/run.sh @@ -10,7 +10,7 @@ function usage() { echo " -e canal.instance.connectionCharset=UTF-8 \\" echo " -e canal.instance.tsdb.enable=true \\" echo " -e canal.instance.gtidon=false \\" - echo " -e canal.instance.filter.regex=.*\\..* " + echo " -e canal.instance.filter.regex=.*\\\\\\..* " exit } From c8a32b578c8785601d417b85548c5e6a77ce012b Mon Sep 17 00:00:00 2001 From: yfpeng Date: Tue, 14 Aug 2018 15:59:08 +0800 Subject: [PATCH 073/360] debug memory leak --- .../otter/canal/store/memory/MemoryEventStoreWithBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index 00ba2e71..b276832a 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -417,7 +417,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple if (batchMode.isMemSize()) { ackMemSize.addAndGet(memsize); // 尝试清空buffer中的内存,将ack之前的内存全部释放掉 - for (long index = sequence + 1; index < next; index++) { + for (long index = sequence + 1; index <= next; index++) { entries[getIndex(index)] = null;// 设置为null } } From 679fd464f4b18a25b3a152785da480ec03f93d56 Mon Sep 17 00:00:00 2001 From: winger Date: Tue, 14 Aug 2018 16:09:44 +0800 Subject: [PATCH 074/360] =?UTF-8?q?=E5=8D=87=E7=BA=A7Kafka=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E8=87=B3=202.11=5F1.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kafka-client/pom.xml | 2 +- kafka/pom.xml | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml index d814be52..b589bd16 100644 --- a/kafka-client/pom.xml +++ b/kafka-client/pom.xml @@ -24,7 +24,7 @@ org.apache.kafka kafka-clients - 0.9.0.1 + 1.1.1 diff --git a/kafka/pom.xml b/kafka/pom.xml index 8d3b35d7..f1f099ed 100644 --- a/kafka/pom.xml +++ b/kafka/pom.xml @@ -25,11 +25,10 @@ snakeyaml 1.17 - org.apache.kafka kafka_2.11 - 0.9.0.1 + 1.1.1 org.slf4j @@ -37,6 +36,12 @@ + + + org.jboss.netty + netty + 3.2.2.Final + From efb7adc9675e11df4aed243a8d7ae4f9ade62a6b Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Tue, 14 Aug 2018 22:24:50 +0800 Subject: [PATCH 075/360] Create canal prometheus docs. --- images/publish_blocking.PNG | Bin 0 -> 41387 bytes images/sink_blocking.PNG | Bin 0 -> 39320 bytes prometheus/README.md | 89 ++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) create mode 100644 images/publish_blocking.PNG create mode 100644 images/sink_blocking.PNG create mode 100644 prometheus/README.md diff --git a/images/publish_blocking.PNG b/images/publish_blocking.PNG new file mode 100644 index 0000000000000000000000000000000000000000..aa7d502bf584b711fd1b046b21e826dd92195edf GIT binary patch literal 41387 zcmeFZcT`hZ|2K*w12TxHjG!Pej0Kb~2+~Cy5s+e|LqxiiC_O-k!XPsupj4#=M*|`y z^iUHN0V5>}5=wxm)DQwfNJ9EOfX*||^LW?2cdhsL{&Clv1q%-6oV~wof6HfoH-FmM zn2Y@+{gZ%zfSASkvzG(}ge?UGejx1-0{&veLF55HwuN3YKO<1te~<)R2>O~@n+gb2 zr-^bregv*XZk~4v6%dd-vhlsm;?m(S0s>D?Tbwn$66L>F z!JQ9|L=n_ldKkRyiH1Ob5klrbwra^*`;+!dxiz0IytEcY2r6wmb1LiZ?j4Ewe&=tW z-kZMub9cn`?pv`9Qu3BFk`l(n z>;)(TKDUnyido7>&ns9DYXAFJ_VaBjwdkU?X>5kSLHI&uv48Hre~FH)s+_j_Nu>pN zXmPX_%cRbo{P!WWtzAA!LT(rR>=*$xB_)bVrCa5w9m!D#;s0YU?VxQg(*h5LcUXE{ zlF1NraD5mUb*e>A%RIwnGVHey8Y)2_}rOHve{zI@xsIHXXSoTO0=j*Dyq{EPZ36RdcSLG(?{N7FHC2fjrN6G z+HGi6K)~v(-A{wgNv0(Is+HzKa7r;zCum)!Qet=J4;#1J_wLDjRfpeoZ1CMpMDe1c zFMCNjj$Ff-3nbPljM_88CYkflK4?$Ch_>Qkr454tl=7=mqJPRR%fWV} z%zXHbmX{!f(~F`~wbyEp4ZEc)kN28sDp7>0AkTA?o*tnNO61dVwm&+x=izKY>$weu zH+94lV$6oJ2u#CpxxAqq73AuS<^j`V8)FNo>@hudA>fLg{(5K=_oY8l<#q4&4o+V7 z1o+V|n}8u{Q-9kLz2o;~cgPJw=0#3+4?NDfGp8c-ib?rQR+(H91J~{jcT_S`{ms2So>l5Tb3HhtDr>7GE4v>Wd#+;!IcQmb>*_JMa8LcW1@k%)2ToeJ%x0 zN{fEpq$&H4qMwUT9#@j(Ncw^8^iRB25xRS1(D|yl-B=ScmNhls+u@jI3v!W+vlp|B zS>vjJ#RL(XZ&sZZL}*l8H1&sRIPa7gd{?th894LDsC<6Ej*{x#^eN$oi4e-=ol!mR z!%?RG{2JFi4BBL2`akC#5;+zF+ocm^w9Q@GW|rILaa}IehbEJ3dW~48dsyaN?35NP zb&Y3{FtD%SrZ%kqCAcnI=v5o?DJmRfC7HTw4f%X`=3TV+s4i?UKtstWe>|=)3!4Qe zq)!y*_OG_gvXg2FmrsSng!G+6AurrIlNWrxvUT;Y7qk*<(NH@Yl;>g`=^fHhyeNeI zv<%YfYPjqw?~*Q4SzbeNsE`kCWmYURqdOZyXWlSKfrXK=)rFN7!6xYw`JdQ+c60Th z$Mr)DM;&n^#t=%@YjBaE76an{d>w8W=uCndAxp!Uk|4%|Af1UwB%Y?8*ROq?g1jE> znRuSy(ygYAy{7>gj-+(O?Y zE9XTJPh4Pkfl5|*`vY2V+sA4skn-Ujz_cH%?S6WvpcIsFPZ2qM!df81IKmBSW=e`a zsw4|93d>CQHuj3^a!Hy}BB&WaC6H{x(-&NBP1epS2X9Xt9)&z$8)oRYD434w3?my|huz&Xeivu1?&roG7j9>7E)F-1Dd%7Io-IC>%8J*}?3`pkeHd57=|BAt{I z8EP#H1bcMad5{@tRh}-*W(%V-QqjDn=JU(WSMhLqO10`)H!PLDpn~hKdpj4}x}`r` zKz|oK?Z)(~M`M1i;|tP0TFkzbC~>kzspt7D|2e++I+!6CdSiX+?(qFmcC3+NYOTaL zl$vGYix|!s`Z?z!(SlJU(FJi^c^zpHq{DB6CQ!5bV!$A}bW90R?0OABHX)4yTAoQG zw=NB#ZLCMbgmPBqhjUc(u0PVew4ym0a2&F<#K}?_zEgFmjO1eO4ej)vm_SEoJK=S! z(bL*miaw?rGIarDsz8j{YZ7MD7-3T^X@lNnU^*6g2DRwruT$_! zWSBQ6ACewEZ!x;Z()MtUEoS-b7e}PJ*Pt_GJo}EF=WnF2E+?U~dy3DKQ3h!;hJQ{* z{$+$Q!iUl;8U19LQ%~HZ+-9in0K<0&c^Jr8t!U1WQOnGvAH~}&@ou9h4o309#?0^# zcP%fr<(y_-+%0an*WiIiNf+Pdm^1ZNj6rOrYwxlC-;{Q-B5D zuCl{&oBj<^%VFbc{e{=Q5up5b8<#jhOoxN*4OXAgt1{-j6|uv1(Z8Z%^*eH2Rt@K1 zZDvaPkTq4%AWo(9<8AIG!c zuy7e^y7b#Q6m|S*_Gr<{EXs?Ak2a}j;;oX#AB^wr`Kzvh4>4EezdxCwn)h`r(InknDhmZje9 zLBPI<8h_3f&Qg0?z3KThMB^{rPR)%USe5AG3faaME80w*jTn1tGj9`psfF6;Y=gFm zs=>qK3T6nuAz#%<`&~Vg z+Fu8-ol?7NFt>(+SU#CnZ9Kr@)So3oCs&4v&Vb!a&XGa3&}ccW{??s=F53qeVFgnv z{C;gKJd~t^x_hVlXFTC|c7H$l52gyZVAutn+Oo+wMDaX( zQz$Vb7HcA#Qh~J&55Je%`E!nk1-HGXcdcOm@P&GzO~+YSac;2Qf~c>bO_OtC_3`?{9C=3WV${gniJ*$+h5HcgBci9MGxY(X{&Y zRgg~a8M)mq@@}Ic?ghaR?pROYyC^DwAKS@ralm)cQ?@EBBC_yJmf!AK<49OlCagDn3pscw|d%;bs(mSTYR$_&cz=I-gz$uYpwz zqkhec)2~?MLy@R8W{=HCj(W~gD}7DM)=}IgvXl3D>Qz;DL_qDkzc^<(H(<=L=p1Sz z5*avJj#z)xubr$&GN7Jnv~k!sd+XXn7FZWbIISdRjn9oJo~!mIEAJ%B2d~AZxh5qJ z>jWO4;|8P1PaXJSif*uk4W~0F!+aFWyimns$-$4uOkP}mvd?TXt-#;GMJIva@6ncp z?s~=XV!4y6ya$It=v3;kLq6|CP4~V@m&Y$>oW|8cJ6Ov59kV^&go)Na#j_X4Dj@fn zG}ZF7ZJHG?yUP)=x&M%#y&{lV{$-8{r!988sosuzWvFu?vltiN`K3r31A7=hz&RU8 z{PW%7v+%js5ubkdivSV-B$R_`f~04P^|cU^air2?%&iO2HL&vGoVM)3jQ)b*_c8HN zuTLn&@LpkOY>1X*$V7r-w^g<^y*UOR`9XwzByfci%J<@}UZJMT@ zWEp*Mvu_I@Z51<)5F6#}bOUc^Bz@+D{R|?1tU2H*n-jFW-BM0hgOUnJH45tHJy`yH z%?NY-TFWt)I45HJDj5XdPN1~2PwFm)`+UBY=zTKv4n%D;!oOy6jw~2ja%--+ebyMyMuB5sW_Pq?~8kOIgtZ5_5=bA!DuhPJ+=Yvsv-a;t2E zd*sSfpeNL3VRmwwYW|mH2-+r@_5rhV!}~Yp563|}a{6v|c25&*mnE}tp11(zN@99< zc|Vvp_BJQ9M6JxC#KrM0bIKCM9`fG@$F%HhsW>%pYr4ntKI5{-oNk=o~ zR-Kb7(>@0{`$K!Yduh6Nfrzlkyhm>0K+G#JVR`#&&<5dLTplif?~Kj9>fuJiq}j`5 zRH8n64i8YJ2bn76D1Ul^^1Ys7zGgyCEB~ZeLx1djM+XFs*mB_>!MnI|F`B;OsSL&W z-ecoFQ%mj<2oJ)YqF~wmo~+moqGGk{@wSEwqxwtI?JAF&BnK>+-YxZ^ikP&lZKXDH$A?ypFij5qsZov@hPdiXpLmz^>}* zG8qz>;ce<)0={OfGx2VWVbz=_bp;@({gnhj(A$Ioo5oRzbCR#HlQ*kOZY~PuO^|3t zYNAHeu}QXN2hPw-rPmV_#D_wu1;WpnRBu!=GF)}W)ZY+12Vo@)%rA?Wd%9a;Qk+dp z{jJL3zdWrC95(Z_x-5tgJ;XleL1ag!gHQKQ=!;k;6OZqo>& z^D1el5F&AS{J{}1%Vd+wr^>H#kG*x0UhP9b_V3+QZjcCnd=Q^~Qoxm5VrigyCgVp@ z;4!LRx#~P>K`3q@0ugT3hF8h!*LjS?m78JF-gqcQQ{4N>XG@qeCY*S{(^YUgKwuA$ zAPyeCo0JHK7XM^YK?o{S@KRJ!rANvQOI6_yX70NC2&j%VCkhGMJ#NwsCuZK;`85}P zE!XYNZM-ZhZUAlDHrK9eX?{MTL*@Lz9eVTfjzC;trSsga?Z}V5gCd0*_SCvYvT>f zEw&jM!~>Wa@NwLWgmK9~A~fnfK6&s$TOhwYPI~ZOszR5e%+p?8@H|7Dir(IRW<6IZ z)oEgg*o;uW*1K$l8FG$tm8*%6BWQsF1QDcIS{xz#4@*=3^K;nRMmJu%5Sjr7P8yhW-l`gGg%>+;~ac3fMAo{lTdx zepNC8aHW_ZPMi9d?yxx2D5txH83_n<>GF#*|LWI`L)z%LqUa%C*%D|Md>cQY{iYXo z5A~%F*J>heW$?s(GCKafhp9i$8tX@@i$Pz^HJNoOJNceyV8SIDQUp;<=+hs@(M*-Q zX(Z|_LkiS!*UOAP@+j^Wgl3Hb>(SMblk9mD9nW0|P zL79F!y6Xw@P%tg=Ndf!Zk4@fRaUKDUL!CuoVK?;`N+Ly{EW9@LUy&&=LJ6PgjYaFT zw!f2e`sl1rOx#_s{y_yIR@PH;n6voUUh#wM-tkn6uLdEYH6VYV{(E*402fIgt~4~> zfU0y4WcqxXJ53gY#Ozz34!;x8j*)}c4=|mon_F2hdSrdYT@-h)QZDzWEjP(lbs;U3-)TIK5OIzEGUv4gnn)hIi^;Irk@=e)0n4ujEN~lf=$k2Jr+%B! zQGcRn&BY`}vx!HSsuOZn3D~0KS{yJ@d<&n4K7_@@kkdA9ku6>gB*{geAFMm#zxh;q zAi~r?wny`aRz4$ucP^l)vnJKs{p# z^~D{X!@-BOgG^moyZ`g9sFd2_95pvfH4wVoe=NQ}3fvvz`c=iXib@4$c}JDnqE@a; zKNm<=oFuHsNqt@4iz1eD?*tL2zagR8=63o?N0okj`&9x(dcgeJrpGpXk9D>>0^ldqPlOL zYU1W9XDFMXjJt6K<*pmHWX75HpoN;12aOg3*8=vnvys}Qt8%|Hx%qiW$Ld=ZYM9=IX^DC z@{RS~ndc-jD<$FwXcz4CW4;a3?;1aVq@?*;rx!42Yb72t_t5aUiS)(AHJ`Vyde=wC z7niPpts_7yim=+bdvh>i1C_*Yi4LGb-WE*cwIU<+B^qe^GJQKfD+ngoUX2TOa925n~dGPrlsjUrc(0H^ZGQxQMr8wOX1WRV0`&+ZKYbgVU$2!rQGQZs*W39_W1XcLZ4xz`CpM{BrZ;h&0FET{0{y zG3f}jOu<>t5>(Mkt0|lFR#s*-k4-{ytR4cMx zE6}jsXzB6gOM^Rr72GVButdDPYT@|k`0w{-wz zC{j2%(oqz%*$PM9-3RZ!GTh}0TPNI60HxyuUZ1V57PUp(J%3IWx)AIkIBE^ zEUPd9DzrZnA+B1_RmDZkX4-F#kd*|VKgD|(%bUStuIzV?swnFT8VT56&pW4DYdX}h z9GwL%3#$NU2z`^CZzZ5#z|qGkYcIsb=`PalV|e)VO5$YrSk)vrE0r3wk#20r==N;yNhJ!W zHMm$kc2N-VwB#hYsE%K9n;cjnVp)4S;3MIW#4iE)4=N-qcL~K(cUs1TFD*Z|(G*aP z-Ci4T*VMm$M&s-)UP$8b-}r+l)xFs;+KXDPKY_)A9`l0AAZ^6svUC5`3$UY;nB@=E zY;^VSh_gqbFdT3u9iJ(TVA}F_iCHEI)lUgF2B2trGIxoiS8w|h9^8B(5nV|~%GQX9 zde`QL@6O!S>*+6`zyDN3v5Tsbth77bFv9VTgC0j&@GrMSrixn<a?qER}S8O4$q>~b|c*G%j{V1uCcofLoHjTxvKRI=%N;3qlojiuN&|gArq?Mc0 z=U$Gn(|5)ZC1uzB2`X!gqQ6qnuA{h5vx7Zi_^==P$KS3A$G5Y^LnVY`FK0 z&#gdMS+TCwHWL&N|I2N+r#ie_N<~R_ojIjYF{cP80GqCxGWj2fvOE6A1AT@Ng2)<8 z#=>C4huZCfKE&%z-6xR1hH^5HA8fG&Y#8o-=@#2_F%Oz|_^{ZXLciRGgJPTa9#sT? zeM+E8(;| z34D^x?DUWJ2ya~gWpUVrZ1JrNm0hN!M=J7L7mf$x2Q;0Gw=Swq2_igeVz(}?-P&Q< z?Zw=>X#c!BGZnkKSu6oFDJaQi{J)qe6@VeEbq_nZL%g3%JP5=t1z*sY-$-?^!NWzo zqA=gp_mPv?99T5ru6eq&FXU+IICSo9EAJFUyJbt(0zp?U+ch6)QV{!1xC5&SCkqx2 z$MSFD2XBYVWk$T}BBbk%L$%2w<{5dqXRk&3hJ+?0zc~I)>d8gfMRgK)ckyTEof*Ty zM#2aeSpMB-@)NleOBsm>@_HWPt78(7e7R;ydPv9!z_YsiKEy83r*CHSWO=-k*sS64 z!tp{Vp-%ThFXoQ^VoA&(+d;17Nb&oMW553ev1=~X0#7vKtmTIAK+YxmyWD8AmR`uZ zACb${wQHgD7}Um`(do-V^O|3Ri;Y<`9CUeBZ}B1Lu-YD#_BtdKL7aOlzC^E4=QFAG5h8U9ta}jb!Go zpl@o$mzE|Btnlb_`%V2xxS2{NQA+|VWndqPxJ{PEFo@aSUyYt|Qc$8~BsMVDx}z&= zfD})2i<>I6wR~pfyy!8j3fatEY@ZvxV~Is!ADe!H!NO#P5l^@Gs;u=Uyzwv@BIu@U zG#Ip<{G0x}w5?^>&a$(z8xvEoLhl(9InD0qzX&8tz)*PI{h5_@g>a(k-BPl?lG}R` zpk?8@Tsl1|kECnIM%O=vO4P9{KHQ2QC@=nEjqk6cz6;HtJ1&GMLwesVbof@twUr)I z>hm(`Ac*Kv?RTmq0ntR}zK`mezE`&F;wST#dOJ^QvgGPj9kC$# zhNJW=EDAd}7FKg;miDdVt3f~&1P?MG>0mk0NdH`&vW7tRC3Z>Yjln=n5Mfs8j`uF$ zMXG{Q!9$&b#XlfMG4#L?9tRS1$GLp4p&%^JE3R!=+d9wNr5IYu9sFExfg&~pQIp$G z05Rn@b9ZL{`8=D5*RAf2Hx-(~NS8vGtKLD|oXdM0Oa4F1x>4#!UrOoRO+JD5C)hMw zpTJB8w3n+8&>wjI)fZ~H4MBMz!E8N%)LtNlZSdHEecxFdsK_9^iV++09;GAbw#nYP z6jTs_^J`Tv;5Dup3nH`ziZf(wQq>B*4Vs9OlL7D4dK%R0Va_Vw<^EGu)**3$3E;Jf(~Yf3ef#0@JQU0bia<|m6Y)`*>}`t36;o@uIs6xSW)1TJ=z2? zp@I3^wa37cZ;#z!>HExD(IEezn_*8x2u8JaxX3L#2y}yA=U6-%M`-9%|Dop~Fyt%{ zpAQA;kA9k-|LoL|!4CS=CPb;(($R@NI-wybZja+E@Sui#{J;VKL2N3^HZ|BHxN-IE zG~loMdVy}E3|GjEm@?x;z~)TFFr=)_5^tW6fr70qW%09|$UKkFx8$vtmW-A1`g3%A zQM!t{Xf6e7zWhSxs$YvYA;+d3HWuQ!O3&iHerFHCXp0s=2_x*9C~NX{w4&B`$tbl= zrR#ldlZqc`at3Ixy}9r*1zWkuck@LZiJX9Gw_Kc9TdqIwBf`J^y-cR0h^3QF{i0Qx z@q_VtT5N>pP-T+*d7HLJ6VFGVu?I@&woZ1@o}<6O)|aNA?#Yf&M?XsIh=kUy@@`Rc zW=)70xjyWI8+o{cZG0`P3v4PUUAeOHFAVoH%*LI&q<&QVf^ojuGyP2~u>-=+hLw0O zWISy;gi;)K9Tl#&VcqXLAvPbNKOl=5LT z1xv)NN~M=!jwmI1U+h7zy;H>wVWPZO8RCp2#0EYLVTRX=SSe0yNKSfRX61Raw-4Em z4_I}Ki4?*Hx+}H=26j1!ie-8c=0F9UGvHIs__!6IWf3}RHmfk1=TP3;@nn(yb7~xU zVfvo#L`}#u?IzMD=^DqjjI8^EqPdw|dR(v-gKxHxK!tUD@EeW=tzSYp+boGYrWUQu zrx`zLHH4-Znfgnncaeh~>~be~1G6g69GS2&ds`NR5qTp!*Z~y8f-E+ zid>`M?hPtE_EEe^8HJ@FyyUYJpFyG9*{^!5c;Ughl*rtmID!dpVgJ-jmzP;H*H-e% zCMtZp`RkG`Hr8|2KB3fhw26Bj|8OjQ{G$t^zHDZice`_!CEIMkPbOXXCk6jHdm%(8 z>$3&_&TgQA_DMF>A7~7%kjOTECy{xsOzW#)OAmioe7i*E+Rs+OKT`x>6#NoaV^?=p z@*cg#W8gEpeD_~;_ebyekA{ls zE)P-z_Cg0$gkH3DpLI|KT3a^&%+oF+fTzjMkQE2Nv1z{_JSeE^0oeR+0o=9Vh?b`k znZ+ifRzW_YA+Qu3imrkEg%g@pSsILC|v-i#iG9_UrF4_I$`FH|d z7t3F@g7MVre@A32S(S{L%y`T{z+?FPtJTR9@-&Y2(iJv*z)qWeE}-tSamZ-{#vQ;k z1_P-;Dq8SMO$>-->@23q#$0r^Z;=k9!w9vP^ZH$E-S}%~e0j}-7t*i}a11PWG+?t- zI<5Sbf%r>DwOcaShnxcv2B_Fdq-z1^ReJynF`W~K7(tx}HVfude?-`6w?O@wycsFp zbO5%pc66#(gM@3UbZgHy34NCAr+1*PO*Tc&I-vN0;|n5|W|G-H;ccc~no51}+RFe? zdOIS1;FYdf%*=8hnw5%%GbsKA!&gr9-MLoZTuRph0zv!huW{`%{Gcj8q?@WDLwF5d zbujLFXh=pcxByvgqS6<49l1NZ-#|&0*7y7+=BXmEE&riLog!*!px{3*7O}9?qGQYF zb^vIN_qOROjER0Z?CMJV1I_zf-E*~2(8~49YIzJA2);J8ePDJsNY=BpH(`KxJEYMD zYTSn|(q{YkMe0LQ!@#D{2eH^gVP0QIG8?-9Rp-A$p-P;cv_0P)z~g?kN}bG!)eyCs zlg*5X8L)0MletSEElN)NhDCKGMQ{Fr9XkcZeg4-=12PHw7s@QQd0&wrxXl~ zH+SE*3>1jv8;;?yQbEgC@2eg5YM`)4J3sE1(JwM&pmx~5-V*Jt$mj;To6p?0@l#Mr zwCh1@i#fU%21b3kf`7lqqfh0`z?QiV=5F(myq^t?F`_KjeV%F|o|CGu^966e%Y`A{6BV9Tx>HtZTm*)BjsGQ&%;$&$f*FbbY;0C8~GG9tHg`i}pl&L6LIZzPHWFC?~RRpDUYAGrbqVZBEiiREo4 zAk8BEwG6k!Ty4JF5SOR@-CWP}0FcAw{WFJi<8Uv}mBe-P4y>&EPu_dE5dHmRY37t( zvuieGI?g2uY1;CC^2z_sCwD2DOW|q?#=)Y?C;f7OTyV@pHQT@8yGj0yWWtzNIx}c6 zV9`xIx46En9($PT65BR*z5c~f$j-s)f92gVAum)@IxjaA4#$+e2$Uu_ZKGVKySp!o z6<=(y_OAs4HgTHzza9+x-!-Ch#&YbO0agnAB@s<8wS^vMmcUqw)`NAWgd`Krr-8VQ zLo_bPz7Yg);-TU(zL^XFPrZxSx(E*m-@kG5hoYeSZUS{F)yc&>EK4tZg(OcCL^dxzV23<5ZvOkE zC%yq^L5fNU;KEMPA!(B$fw8WUA2;UqySGWpCS3w`1^l?wi9T|BJN6Qo-7I* z;r_;^Q^)}z_Si76c!FGIy9`~MZyM{-sPMzWm=#FSO9B=?k>`pIJLWzvePB z)e;)&%yfy54)XekGoD6{=NiV~820Ws%u!qrGz?*s4)n}i3t4^1PMhn9~~kY%#WCEau>8+fmM@NYTlit71>h53ZGLwd}2X@fI* z$Qf-6J#qTOArqH-3?71tD_PE2ohj-=Rs<#hh4NBf&S>Y9dB!0W_cC_%{p|a$11+g> z%Xy?|7a^QUo3dUMRmv(Sq2ZJ&-BilfFt38v8L=|*Y5E-s?b>`WH~5xHOfaLry6dHt zl=@*@5ag6zSdTSgaKMMK3re-Zjw+&j13bC@3;Caz3GBcGt4W}_?i${dv^3F@5kzPR zC#2|3K8ht-fh-GQJmz%!o$da~{B9_OyE4_CA)#pVj4ytRSWcdDX%H5d%WvkDmJ}}F z=ZIsn_Aut^jPrs#jI$EV<#E{=)H$54Gk>ubcNAzG39RF7oHl%7mLv`IOp-3u_~HtK zp=j|(4ZK&0qKC*RFmbXXEm^F%A`PP&yYQj(92u9dmvPWIw(&hn3KXjeVUbE&Xtstc zXAjW~A{OWICe&UnY-h5ROod7GnqLTOjdYF-k6CJ(e)I6q<>Bte^x@_4rqw5YXQB6h z3;)oKfk4OQ!=EmoIA4UZbX#utYKJo4DufN=cTPVc@@A&9`Eyd~a3qJ#%_8twGJInu z7RI*AY?#P-_~%Fax$I z>~j^ydH2$b?O>cUPFvt-8;;k;E;%|Gn<%*$^F6(ptsJ5v(Z$Ia!c7SHz^|HRfv=Xr zeuHt-&JFQd7~8m9JpYUZ7o%Frk5s1dt6g9&&U|_M1-_X*U)hNZa|Ql>gvO8X;!Xs7 zFsV7mjdWn?KtIZ22N{!Di%xozf8Nq=NIGTn=TzGMZcjsE6i|h{;UwttO;_8=?hnV= z_dqs0ga+53XLb%#(T~HFbNCOl(9dDlQdltol^E}PEH?f*Y+gK0mH8=&cS5fvASOD0 ze|Pt-{Da+Tw+@cB7*3tiN%fsNrT*cnJbe3S>S=d*=pcUCoyjMqh#!u`u=}uPd@AN> zWJN39&m?)Kwr1ybW-O{I_AhleW95?pL*0DEinQE|G{)5mkiX|J@hLIBcZ{A}q4F{%DlN@Lo}H^t_5_`JVN6F%u(F_wBQ$09 z=aIQcvO`zAKiRYQQJrzui;FZ({BK-*B=6$tQ`?%4C9s! zc&4Ai|KS;bN@rdL4sv%g#x&N%@}SZ8_jd0EW_I+w0zK%G@*>T%)`E<|oP!>e$iXAW zG4{}d$|W>qE?~UX%;zkQR`+4r%H!)-pyMXxyg4Mt7Fz)0HBYZ6m{`({+e%h&Md8Ed zuyOb>n3DrmbeZI*PJ=)^EAo=od;yZ9j+KySCQmEEnNh3bO9cfuZDH}lN%B0W)ePI% z!;sHox{5?6y}LLvyBJeb=*gWKOrtsH^BQT@MgVD)TLY{p`43L=%PkjaE5@MOIB(b< z_MPic)&alsTrLGqtR((F-FR+x>f;)QmfzeX&8|;2&`(-}JuH($c<$p6Ksc}u zPVhJ(H?~?{PI49jf1F*yuU{Nq%|Pp zXk}Pk^h!#YzNUsb;KbVHK6hM+RZ5lwTo|0?voO`+Uu_)0yC|{U>_d0amGU(4<~Ew< zFOuXbvzs5fg{M7ew{Gf|8yg#zIe^OfFU8}kt{`>eSDvRy{MgAl<(SMmRU~6CFATjX z&p!uUlOj(=taWoc7M=z>Ex=8n$04f(kZlB-okDu)c<^1P-cPXHv{v@hD=lK+>4FnT~7uRTDfO>WJ+G zZDK9YX9azrf%}@R%9`TlfWZ)GtL;|*pnDPac|{dliQ@)IqxXTpb?dOT z@EqLRngb27vsdMV>}i_nRIyQ65KfFezrcov`@}qrhLqdl8r`z9Z;|$Syc|5ZM60#SERiIKYEOP>RkPt5_ zu=WnQR6y6VupY zX#Szw_(@%yxBR8@H0Wgz_bu8ln^vtJ^p|nhUH=8oKvpl31Ik*Ul1$cSiB=J?75Vf- z3}Q=-(Rg04xGjG@3A+Ly0$Y9LHSuc-!Fs26=$FXGE-R?m)b*inOqaL?6glh{+t_gQ?%M_QMr=`y)5$M)d% zELC_7t6wuPgT{Yy855#n*uy%7Un&FXHKckj0(X4>$YN!&eI4L1q1;xUFA@r=VfVl- z`VSgAlC->sa7DG1u(+8h4dW?e9)38pbutUu`<5Iu<@@dq?xq>pW$i#1!g$hlhQ@^} z94$>_gd3%TsAmFYJPOSQcL^JxQKfXol$JNh{@Ss?# zGW(-bY?o>oZ2n17`XS+6ho$UOA%zp{Duca`@NLz8wL_Jtc5 z(bsH^n~Wf**(qCornc#vc< z=e<|)R%-0>tXFr7yF+uUB%(8#NL^tZQIu~~=Z@;*(}468Kwu7Z%S@ruq$)b;O$-*s z#;nGhAY6}syG|YVOgqXp=C62>$vksXv|A>9L>_Zo?+Xowm^v&P=uMO$tWDa&v`5!+ zL9!#eK2xucR!2OgjOilp?^`|}0d71xO8ry3yqDaa4h5~B4f&`Hx1hsF@&okVxk{@- zUXr+?ts4WDJ|PcrsF|t96bAA-K;#(Jyf_BmrjR$Cl&(cUd8x;t)eerqdD#0<5QptG z2SS&wg+f&LiY8y?urHCs2l>(KynapPl$M***Y?7Aht*WnE1x#hTVojfXK>f$_N!m4$p@0&A{C{Svctj6q~fKay?btVQ5vNWm51|{fW}}O z-Z|3sd*X)P!bQ};Tdxdl@@+Yhe82kjs<qp=DBgS#d|F0)j>ct*wCW=XD+RXI4EQ+P`?gsDX+ zZliG=)2AEMssP~xVc>!645CE?nx~u52m3iUI0rqVULexG7YkcJ)i|MC@|Au744tn?LFJG5zwSxmVg z1d`uu0X5$$=Sz=^e!hJo2h{k;T_g{dq0O_|{6`|~(MSG9cvMrlx^6@+c4Ga{Gq->9 z{%_>y^!v~>qcN^0AIF&rcT(Q~f7)69dcU5SJlB8N1;o$uKi^F0L(9UO93;B}oE-S& zAb({7XddYd^{sK=VCr_n?#vB&JWvtHSl!H7E-tY@KDxzefdYlEKz)>JV0De$9LXWBr+Pbl_t3FF}=~7 zQMe)I)p|*r``PRSkuCjg){*bsV&%b6Is>XV)gb6ecdHa zUCa{B91s60MfN|WC;`jUjvqKgbQ@3Dar@$Tj3|4!(=%xhP%}0uE}yn}GOaG{Z&($t z2B1#QWHOC0k&%E6xPEs4puQvvA#&8*-$ZX(;h6cqw2;FE@xlNdLpzjfd`5W~apy&_8Hr=l* zsQ-h71`d*VK4D-xi0=x>Apd(Zz*?!98D8OJKA(QrlisJX|DD-m) zBkbF9-LPfKKRj&56EIXy%tF_VHc&2n4RYYW_W)GjZE&8Sbfz({fr$Gm1M7duAW5+j z8VbmOH{Y@$gYP)`s@Mkf3JP#%{2bmlcc%Nd3<9}-uhni~t+ub8_-}vesD@i60RNE8 z796YbRRsSWzyJVV0R}~r=)QnI>iKVfa;@ngfS3+E2%9Q%Pv`C2iZj3d%cJ2jzQOA) zfWx6HVlsf`sQW!J)&MaVP}N3i_-#4t_2Xky4W(E@M&o#cTZ_Yse_ecN(rlifDor%N0cJGb1%n5>)mOKsthhFfyc&(~Hc zz``9YTKhAAnEzFwlBT41X&P|M?dKGYdIwEI*{?e@;0N@$maq|&`Jq#)qL%RJg;F#<3mN;H-A`5RYM|!Kz}O+HfuglHky4D)TT37~ z9w7FK?=tZqg?rb32r$2;32Z|Up_$Sp1HJ%khpHb2uzc&nI2Ng{lP?dMepnX=Y`|dO zKQ$kuuTQ0wKyQIQybi@*l}CCzLR*(C7Sus#!#ZB?W3BojdA*QlNZxyMc?y1cwAR-T z3LGX{h+G`etm8fQEiA3z0S&&g&U_9PXL|)k8bCau;fxsL?^qp4;$)?= zIdpZ~Tw+gM+&->gfT@!w9v|Z5Hz(jo*eudeg^F7Sj)085LiPnK8Cyv5DTFLwcVk(| z+Ias5wlp7ZOCGT?*?3_yk4+=Fh^{SM`$vmo(YezPkXA;E1qr+n_xa%MP_?1CF|v1X zo!CGJzOncF87CQVG@*_>CWZHxjTGb&t0Oh{aYuUTMKPu{Ytq{%m+$kz^`UA*8tSaL z*2iH3p;(%Ye85;@1j-rYk{`%GAJzlI*qxh=SQCLFEjB%l;a;>RxMSc)X-qPw>Rz?*OTCKA(RGbMbWNa4 zbU%1d(YafQu96!3qQKL<_~=QF89=!PH`9AAmtm0rF2{SnJ-<95`a1vgEhKfRB_`je z*$A$eC@1zg#XuDt{%EGVWoKX`Jw_`^(lW3GmrwI^vY^Z!7qXC6Upi1j&usWZ@yjeR zyb7pgJT#5)`k_82_6aLO{~UM{708?ZL`FK9Ab`588b+4PPfjD}Xi&%@tU#{Qcknc) zGhPU%iEgcziLD$h>EpDXwkD`1ewRUZiv5AU32Ji384lPsTVUwt;8!ai>eZqO^~!@^ z)pAGlAD7#USH46tWP`x&S5W zfoD_PtO*;*CD3z5gv>(t8wgclzJm@S!WjZ+XMaf-^j>r_mc@VuA#KjYj;#uBq7yT+ zkyaesSo9H2CgH~4UPtE-3)Gj@?)%`se7X^Y4(=L*+j!EZq$vFxKdHU8l! z5q?0mbB@_}#MhmY6@#|Q{Gq4Yj~>V9Jp*rt!FCIbo&ulKOb_#JKw2kSPGS_d^f4R> zql}XnA8@n`oNU63zchHciZXe-%b7Axe(4>U5F?zdNyPT8$s#j-TIeG>7MSWoB7go6 zF)j}~wh#^xkBX2CvKOrm+3ZSaO2<&J$litgW8fT^BsCzZ$8ZjWjm%g33GQ>?WbIw9 zgtDk6vK2=7A-T_KV_Gh@au=Tq*KjkZ+;0WQP+wwp8d7BMOwgA+Dy#wR>=Oxv9Fz-mw#76bmou9DNOJV@~`qgkc z?0)jE-^ZQ=Oy(I-OF1jWq#=i9%t%{D;N%pc3P2|&@DFgl;sf)RR1kfE7bbvC)S(S+l&O`eGOT7f3y#l%Ikx?z~?(q&=<(QCEIH^%{HOP zA7yc%frG~IV7*kygCohIRwBlB4TIqhybRZe%)${{%qG>H5q~^<4cN6YQ{~cDIrdjb z4}e~%<-|TPF;P3?QpGG7$WlfH2it${7*CJ-IZDgED&PwZd7#RF8rs$qWthg={gZ}x z#9hTZe5Ju26JXRb=gDTdr9g-F_lE@pF-K;`{ql(!8$8Z@xFAA(B8mhy>Jphg=u_J1 zRF0i)fCdWvfS#3gWzK9@jtJT#gTWLV5ysaAd?Eh^s-?mL!1ogkNph|)lvFcPR`fdI z1I|DVH00IbecJb=+w+o4sRGSuLv*$V-FP9>F?L>3AWoE%sVEnnFRqqBTfFEunJGFx zYfMfAyz3f5=VPZct_G@5AqP%)-F-tXULFz89N90kMnAg&u#$dP8bG7*Yq`RWU*wqb zx=KXRAJEyuMq0Az6gdk8v>gM*gXI7eMq+T#3DzNUZ0XL{Ar(9zq7>Z3)66M&e`@C} zK{1j)p-j^J{;)D@vI5#5U*2&3L#00M(3C}e!Ae#K)lV8>r?6`b`e^JL3MSlS61Goy zHtX-CfPwICG)>lxpO(P;L#*TBeGRpgizlHQb>&Uz=Z%t&?*RA>(Hx^>Q*xGRb0^A} zHgi&-$N#)T3qx{tS%ms{8&0;YRqvZw)?&%f0S`gh?=0a5LM1qx{|#gJ+gCZ45Y6&^1S&xSc!ql&YMeIN&$VbcBTmWol(B`=;617}PK7{{w7xbZy}) zY~CbiSsDu*Cg6>)G<0A@)v|sp{-hs=TAL|HuQZ4pkznXYFF7k~9`pT?D3nlqt%t9b zRUu@i3N2+LLV@p*wH%ZWBz?VSTs+_q5EEWGR6IVfP$izi#N&Ia2A+bk96Y0 zVQc9ss4)0i;m&G#O;~cS%M1m+pum!?tM6CM5UxA8qumKG8G8hSupXs8`}btNS02_e zF#s*l+j&*i0u!@cP&*Cu_$bgzt&(@WFR5P(g>iyOSJ`p`E~sg2dKS{H;-+RQ|0eWd zx}2HN=GJJT1%m+Hr2l<`ZaWx%GgO;Gx*U$iC1o2KKR7SRIiWpXW*>Y)we74Ie-xtj zL1PNo7xJ&a;z(NoOcbsAjBG_6{&5D{$K>|#Sg@k%vG^rWgF|vwlf1?-n*ToFsrZGA z>6tjYIl9$#<0kWA>-?Bc0#5nPf8`7#VVVEnQRv0Mv3MRZ+vunIh*`-K!0EAefdly+ z1)vcGu_8-Ip^1^M)*Pod28l8Z{XUY#3kFYZc?_3UGX z2|2#%VFyUr57~Zp~Na{<5Yc%Z51gt=O+aK##l4tX^elG0r zwbePO1|}kEu+-{~)@miK=tcJtnAz+8@yJWp+3K2A`(AgN4rTw8-3=tezzFIRoR@^U z@4&!-&!zY9jNJ<&SV1GT9SM!K5+zslyGp__m#sPMI7MCA@|={>HS#5cY`rxlSE5rPzXA!*uC0HiIgQN5hrl%WLYlHW^7)Hq&YFY+_M;l8s{zb2)F% z#2~2~po6`*9PwkMh}4dJ(8?h9g2rdW3IMBfJ7R5ehM!}Xk_G}V0IN?y;zhU(J*?|V zDATF}hG7eQVZ#^EPWZyB22{1hn9!e6)#pUh_9XOc!4b(W_)>`e3+F+&p>u}61siIM z;W(Tm+z5v@F0!@6a7K&xTf|X-PoEX>y;P3_VAbTb(1*-U5>#<;GX2aMIILq#P&JAK z*xCap>BD7oT4M{{%O6e(1_@q*B9vp~c@cl8_#r@Y`VDhTRbQgk4Rg?cWfWp1HQP-H zd5;knxTY+knO+99tcDXKUqYokp}-x$f-n!9B)A`eM{A?ig;EVUtjiz^THuR1w2WXJ z&ZWQ+Lai=*fu54hmCJd%o$u8TPp4lGFs)Yc;p~n-Jq+rL);fv#PI1&Y4NNVtfiT(>yh^-_) zMwZ|!v&deVK%|s|MyXvBFuPwnvIJFh`JZk>bUmS&IFT^2#?wH2mjEYoiXH>`usj7D zwhB%v%C**lB`c?svpgy1R^WSF)^feS*W=2UWBv-Tb>Gewj}Ph0e+BbX$N3SZ^dZX+CR z&ah%TvY9$$oG^@h$kQxoTfx&@bZA-PDYW>8c4Z5qho;GT_?e{)A$cs8nuC3 z$f&#ry!!op8*-<1K84>ud3Y&qI2%l4u!6zIcPCPgWe}(jDm3t9vg$p-0z*Gv&(`1F z1N_{sW&z`RsE<(Y^?G*tzCWY(Q_x4M)V{kk>-MOv^R~qd0PqL#J=pj*@@)Kb{fcZj zW!4q@$@BYF$E%#dxdjrWn_wU-er^b$Zu3jN{(U8=C$>&7c}QyA?KYVODRBe5Nk`x% zQ1jq_-JliKgYD_PZ|ey@cl7i3EZ)u?dX~1(_29AruwnklF#Ve3 z!DSJ1A_@<*1nMG^HNmVK)m^&)G*w_fzr}U@1x+B&AI~3XsDKhI8yGM515Ay6MrRGw zKEJ`VwNO7Gk-)(1lLxq|apS$S92t6jz-$}x^*~+(od{NVE!gb^!nX5XGXClP3nim@^yGG|K|;)9H1g~I9dwyF0Gv5pE4bQ@d32#LLYognRjQd zoKeK*PF^lYop+kb;AllZi$1BqF8<&3&;VCgfv7^EPac6sR1>juW{@0uJ*3o}mUUz> zs9HfQK^9Uz(F{wBkDO_2P4gv;lTu)wPkG;-oSPXz*5&G?>I^`D>>eEGDa_fYik@@LBrWc!?UJob45Zwhq>7>w3k zp5qofAQ?_+{A)BP!!cSqemCEk?Yx8(?MVxOE)%{4|D@E)Q8b5D>wks1YhzdT{W zTH5ED7h;C1|!d2a?tVHRX-?0=}SIo{%QKtpi<}3$$xAs=o)HG z3S$R?TB7{I&|c~K{ps@!l*EnSqrONV$tvy_r!3{{AuS20+ZDXCW!+@iA`EFYXPPi) zqyrB$1CUAg0pOjK$M%p4%ElPzB&~In$%2;5%7S89pcO-@7BqSamQlGyi1IG=gHFsf zb+4B46rbJTnvmB!FG=ezxVFFCyg|+~%&ms?Z-!Aq2k;Y!5}QWZy#wW93QA#1i1x6L zZj{56WNMjCCg5PNO%ac^$)+H{ziAG-k%?5YVU3#OD02T_cfi0Na1f>`s#b)-hJIQg zs#6wIY1}j69N#z1(r-Q@uM%_+1hu1369$a=3y_*MsISn4uh zA09aE2pGh}M2Y!_4nxK6tNTM94Y7D?v6cpS^8*_rt7$&VF887Vi`?f@TG@zNT66`? zZKUud^XtPZ*8ffLV_kGU|MBfpY644b%)G4P;{}k}qG7nk-JQ8cs7@>`LGm+q?!9k5 zA2Sy&R}}7BHNJ6P;&ZCOGC|bbq*6bgi8>mDCv1qKMiBS$Q5t}hu46MPK~qGDyz333 z>>ziO$B^U)fkhh5r?08K4`S`iZAByXI9wqTqM}uoC2mMM658ZQ4Z7$F?#mb}5xgg` z$C5PG|E9P(0ZRy20q*QweWtiEm-CVh#B~?n7K)wDBWY_ZV~Fk9TX8-uMv}5Vj<^J( zVGLU6+*I%Z^U6@4wiTgF0)ZaFn`bh~UiY$*CH3%&GY%(@pK;*dzlNknTM?S-GO%rM zNOC_6`7nPZm3%SIim-9yVl%x!n}Yb0!Ivw%&bP)0tKiU*V^)M4J|pSmEkp;tSLWjz z{2{zO-z(68{~AXD0PMg&#@J7&VZh@FHQ@N6m-@!v)PpQuL+DmeJ17V&Q!Yrz-5?@aT|sFbRC5(dU11=(OCyl=ZkBeqDW2K1~tA7 z-|OApaK56F>Ots|#w_yXq%dU3p8;H_b5&fwfwtQaIO*Rt%FSk%_;6$RTmG&TZ>Esv z6rW_!DRcDsP39fQD!-1%AOTIp?1G?JUO2~yoOu!>6t8cD2qFm-S8-7}KUhgECS5A7 zz$&>s*hC>SbF;}}7BcRa1uYwc_*3&Caw(9@@u@ArwwO9#xSB11=W^6g+s&XL6k+5ezzU!gzh#2T%EQRcKYB7I{=X?H^_==*I6F@Sl za>i_cL7;o>{u9x~-ti%*_PiJ}alp{`Kp8m2`2aT`6=K7dJKFw6Z!x_{(D$nu0r`Q=UUPm<_AhEZ z{G3hB$1u#_95BpZ-K&jT=z%_2h`%xf1yX<}fRa1@BrF1r#Th67VH~Q7=Vg%zqz=BL zsLx8+=k#4jj@FCT#ZK+1y^LcRr$9X6h6pOTtZ+}7;36;xoRPl-K!RU z1#Radx^>k+0%(QJ;ijuZY)buF-|2mD>$}LgNfQHh`pFPk5t>ve8o^0&AqB~uTy(=k za!YO5SAe$j{tPPtgL>pJZiFsSV1cWTT@0u~^@ zP6HB9sIL}%zRQ$+RYl#i4A;2u^ly{PSNLNoRO;`VO!>j4m1VnsnzikG zoD+h|mPb@dzHpI)O`il+6doM;L~E{)KC6gJ*Sjzh3cLA)vN#Z&(&2KL-@pwG;XyrMNRNzLNT0<;>BV3#&T{J8U;f*9af(`uv0&Nay{yV1!ySlApx>` zwf*xxo2d=YDa|_4Dl!&7Yw#;st(W z)LlDTXDc&B*36nBp}Yx#DY$NXyU6j}VWOE#i22MWRp#MmP1)o%)bsP;B0dF*_ zHV4yMU0xq@P{$QuC(#=VSs#Yac0pyKRJkg1$m`3ohiZ(1!a)}~epdP}*v47AywV%0 zHj4VcmXR_RU7C7W$eL|@L!A_7`l#p<4Mii=_qGQkm%2>AG0#G~hp1z$2aWLQ^ z9A7H6XOm}LXQ9sQP*k)*XN^_?ERffFF04T^RSzCeG*&CCeBr1K|MiqBK1BeqzebKHe0OeqVE=3I-T?(5Gs%-@ zyFUw(#)+b)eS7k&&~lkV)c*bu8rUU+M#-PZG6vGPEU=dKB*VsN734tcSD8TJB*G{k z5nyBl)t$&0cv(<@y1-$EPFAYG4DFUP2k-6Fy1VNpXh%R2?<|9Nd9%>wbzOf@=F}2& z2PbC&b<6mhXT9OT-=Lqy+2MCUZ#=XV^~- z_7q@ckjPANb^sSG#FeL@E_h!sk594W%)c0~@fM0KAmq>{M{x3@ZIJqjoT43K8DSHU zksI90|Atk&x5WqXY@rz#!4f}NGkg+r@=m=Jxea;@-UtTN8rI5kN^zDqoOA>QL>~my zXz4sOQ< zDP&^gEP}a(P&UvAph#`Fn4C0pga>n5saKoRPa>JvcS!f@iZ!()M!n`=v_>!nk>}2a!T7|vP%(_C`?Hh(Xj)s+%37};Q#JwR{_)W$nyp}mda(9 zqAA3^Vd@$*oX=9N?jzKgg3NQpVwWaOAyifz;7AKT_i&}VGy5GA`g(8? zuKwYR59sjD9{*u^WgfgHOK*qvgVt9MDkKoJa1)JTSWDpSN2Lh(6NLT`M$L33Vgr(r zKn(a*^z@42+HA586H?YRHlIY52ch;DJPM{??eY}W#p$_ak=5R$BCVHAp}1agD7yd9 zLVx-+6Ai(n=Pt8ZbblenKv+dp2TLdp`=9U|S$)m$g{4A51FLHe5Hqc$i@aTC1E zkkg2lihQ8tV*7@r@@kR;X2imou(9wMKc^jr4&NXhTM%`K>!hbZ19Ipt2rDEY z_$U1Zus*2{Y2`t}7*RJgup|o}5nhI^jE2XUG@rz9J^=7mCj;XGP@MDoE-t6&4CHqa z9JPT)2I=>ZJ;3{GiG3X1+&XaXLi^L835WdQC?Eda1I0ZJdU3d|7}H5jh1z?RH_^-B zh73TfTR|z_@&=xpbt9eZ0m2f0EzsVp!&$r>$l6p6a`FDa0}xZNfkGzO#PM_LQGGfe zik_JY1$_(!ErNm`FtNcndB=greselD%mASH8n|&-G|1bX@{hs5vvMY=hII42ZtQ~p zdqJZ(aiJ!yq!R>j^qM}-wu5l(KbjI+Em_BvP-6v8ZHFS9uje|6ph1`nC~jOBB-kl~ z;$l#aL>;)TD?lp ztfu+auV_>tD4vOL2hgoQT8;?|UO28BZ0^yJd`HIXXU=j7haQ7d0q%d3M?`%@268-n zsr8VVZYpM3kp%IqQ=Q=^>kON*jOTUk%qb{Yih6hqyw7Z#$wxUIHI1NI^`I4C*vX*S z+38@_YNAi;Ws1mYI%rv;MbAom4w@G8Q%!Md{b!^QT7*|$$*gEiVe@HJfe1iLm_|m& zgI~7$ynhBsnkC5K78nMyc7%6GP5F3SqbW*(L&fH(mHt8`0kH9iRs4AAXZbn7le62P z4m;tmqhSC^&SZ=Tyv=FKgvzV23Re3xyMX*k(C^3M0XAz=MESBJ$Z~ZyO%(y9u23D%crH!B>(A3%-kW< zD%e0Wnhl`%tgstG733D7H!kQc73_6zTgr@a$k#Y!%r`!EQ1b=31xQAus^@!}c5l9$ z$zUow?NmS@u5<-}E}AG`HZP53XaK#zSeK9$IWMVH#}0xSUoDnf1$s7hD{u#-31qq> zWy9HZDMe{IZrL|Vv~D-E=-=J=$m5DB5!{=n(7(U&NH>`r=0=k^1lsx;OdLHkp z&i*SQMdK%l24sN9b$T{bW;~w`!RuMFIk0*HK;4TBMx_$K8{=`MGv?)f=e3~?CpyYW{C-HZCDdS~)Y zxojlP-l1eye@$2x^kcia(+|6C<;>!pd+Aw2;dxd2LVw*+v2YJeN&`Ot*o|X`FJVcvn9pDna!R zLzoV5eZM*&XD*m^KwuQCt8dm8Om9!q+(6{}zDElMY>3Zy)CG!k?!8bs5rhp!JV_t1YM*4zEZnpJ&k_N6w*{dg|?`vYI| zSJ}J%e!%$BrBesL{_DW*-ETE<+`Ale;)ypEKJ0o$f}zzLYyp|TOWQ?D_k_2?a2Fkp zHom{e`j%<%7ms$pc2WQ_OHW#sl9w1z_KGAEUu z2WA0=1c;cnumKId+YsPai@A+kzLjO9c8}ZJ`%}|mLy1^G)2VN2=yc^qITEi8_ogJQ zkN7fVNZ#${sv%=8Cv|IoIt+K@cD@W>Ymc4i3h#szu@yS(pj9hB`8|?EL4-xJA$uSz^ca^B=zFB#^|#0s8yz zOW*T&Da%}={8?4!2T-j$z6V5R|N6p>z3U$RoS-xZy@i51%BW;*@%7sDZBk9LEVcHN zmUR(ZWB-3R?Jh%5k;qi32q$SP+e(6t+qF~e+da<@y*I7>-3v#!c&wf3e|*2~Bi!fs$pF8mUo4%a)3a@rCS}pXNSe`GVR4Ugi ztXV2N;A&NN^xGuRl$nxCQb zVPJemfvUn9>kIvDOWpA)?NkZ&dxf(uw6DAZby^-8*4DIcxBH-Ki_0yG@!L)w-*`;p zjP}*zrw#>U)y70oa~3{bT5cM6CPM4KRVvy;^B*(}&aJEPJjq{~VKVvU*9v1wq4v0e z%ItujV}?UxZ~ENQ8M%G#sH&}!!sXl>_Il3;Zim;F3hY&c0(&RkJB6YWC3v%%fZB6t z8}(?k^4-TK8V-_I{$~e2o_t_d<^FBblat2LqjB4}*ij#)t&Ir0+VGq3qoJv2<=^ww z6~eY{ziCHHQ;$=vBkJY3>u5wMK_{MEyae7%Ki1bX#>_~a#z7aHJz4T-XHNNV2Eeb;qfcmP zc8e{F-yy|A?w)~mzS7CEvG)T$!gApYbC$o3R*Wxq%-nRd^Zb-I_&$XvH`!Iuwyiaq z^XbVjmVVH!Zv82`(P3Csp zeBTp76Hf>#M6gpojo?OblbIoZ{^`Er(>4~2ms#79vNqjfmGOT&JCUERfWWJw)5d9% z3Ch$p-EBWCSWw(AO#}1$-rnoy!%K~NDsYQo+fMNx48<*CP@E%q$BDMvt(Md#+37GU z^M$6|E0Uo-Lk^x3rOMYaei8$Jom_X%D#BdhMczAdus~18t-v5Au%a=UTvy6|zqO|* z!-rN1&jZz6&hviQ}PmE;(muQK@joc zo%=p24wi8v4jmHy<3oahOzd(O=i_~W58J4S!DL=hls)gpH}y0>EeW14Y1ldVe*0%t zPqicE7YzlBSPfGB$0;=i`Pj?sB5J?m7 zqt<_$bg0jpt#;yU$JMAEh7AN9gPTu|qTx+FoZYTAoV`09N@PrpblJG>NF1&rSsFI< zI0SB^mA&k}+Az6aQcmd`StkM~oYJ-VKUzG!AFgGY_uwVp#Quk`Lw5Nr;Or5ECLOnV z@;y1DVR8L|w(LTD$cFsXfxbHli5_-DxL;mhKxd<&I;TU_?)vceeg#a5ch0P@;PQMAZ_t>$Q7^vzX~bv%cx zjM-1eT_C5>e`K4AJQNo$x>-f&ug7_=#j<~ulzyk?SmwSKUIl^MVMs}Fe2+Xb4vGsV zb+>W;EZlh^Y@nc+y~1r{NS#4Yc5i|xL2;1z$jsUxs@-jmqhC*%UZe?y6nwnUo`15K zeTq3QJ{Y#67$;zyiCRaiK2V4siz<&|4hRMPZJ&*>TyIf+XXWzug@@RiJSPVOh;eJ& z79GymyI&`VwfLK{h5Yhg_HTEMFNo4tTEl8AXnVF>e<_WP53(B=cJ(Nv=V*Ponw-)3 z#Kh{WYf*H=5VOFbGq@duM|xo{rK~NAbN~40b^P8^J$4?g#L2yWg)7BAKHfWz(BNp# z))wq3^zaLNdQtFW!7<52HqqNLpT4U}tMmlD#Xj~|rtifzzjANWw)u#5z+LqtP2^gt zciTWUKh~*KU{Pw{p74ef?H?3A81m7&>Z-kCg@E76xz9;ubO^jQ3Dp0fFQAlGKDJ92 ze><4_TB!Y-PC282?nAoD*i?e8&41}ivGOBF2)_^8=Dsb!PP9{>*S)08BZH%?p2F~N z7!x_Qv8ziT(*2UB2J-X>dtQ9ZE>K+CS-F8ySx+0K_c0&pI1&tq*zouBlX^M!51#EQ z*1!8J#eA`1-?j0YvC~!GjLo?km8Y?-&9WrOl39zCE>3?b{-Da;KQ}UQYm3!;PQl0H zq;OwJ(59h@vAQ%rB_F0DypBts?m$ZFamdG|=>DjVm)IPjZ#(Y)Qr+}US9>5M+fSKJ ze{9~TPVi+M+N5$N?gr$+gUQXP#3xh@^rPxJrY#5+D%NFq6h?+mku%8!0*5_2xL zov<`Ny4aH5zwIz^^^t;w9lU6ffT(`!h6lU)?|QDyXCp(=o8uUZ52Kp5mo+awg5M$0 z?qybl_5{<6ZMwVmy6$=x7x%OBw=0{6%Y1*Ktm<#^KX!8 z*cQtoheFS8n-}zA8nN6>#erRu&q~48WttCO{Wz)IPt))2qb|{>64J5X>}M1YFpk=u zW{)CiNAryXRqk8+C_(I#Qbkh9QVpw8zZK1?Tdlf z5pEu1OQTjW#rxDLA<@Ku&iQQHR2-SKt*9-qMuv}+(j}JdnPQIyonh|UooQw6l|y!uI>^o>uahMZUwjO?$)Q@D$p! zk+k!_+}}PxdGiffr`WP|G>mpApI1fYH8}BFTnkN+62HlJ!%w*lxH?_0Jvz5`MiGAZ zovQ4*s}1Q|>lY4&%+@nVtFlLJQ1H>~z|vhQ&5=7q(VLbZdF3fq|Gr+2q4<>lHBm8H ziS0DR?@!uUc)Zl)g4#AwgQG)i2?0CAi}L;o9n`13`8H`omGNL$_f=d|#cLf&V$p<+ z{r2*qXSI_hhZai?xO(2g#^_?AU)2A+UR%{(qt>`;Eq!aoz**5)JN^26qcevcn|t`2 z=Yc*e(wXdWbolNH{%BL_(S`g+pZ;1L6Zg?yNZ3+FE^zGIYnAWG&^cp!XvNxM=u@2u z^)NxH-5Yrn= z#g_Z%`sTIcL6lQ`)_)kmvkC-CnA0kQp<6`t?6C@a6VD9n2^bTY4FkI^am6 z5nc#Q^xpK%GnYH(J4tk*?Ld0(3cw` z>e{JMqvLf`Q2NqpjfiXwonpOp3`Y+br+(LCCFeKLOxECOlR*{=nl#N_5z=aVeUW0k zBZuXWe0=gE@IP(TnElH84X-3WdX=YQ(w4Gtyxhs{`hT)g1j>svw|da4-HyF;dsuYt zu+o@Cqv>gOvtdZG!j|*Y@{x=dpM0VR93Nm(XUDe!gq`4dWaNhyvOcKHW+4~FdPQR`sL z!Krw>qlELG2gHhZ`o2`9FRSjv&i{^8`qE3#W{lip_?7x#<{9WB-di<$4T^XS@lX@@ z51P(aQG=Rbav)G(1PZ_~xSXE-ETr}Fp@ze%Z% O@Q|U&{_MS{e*ZtreH1(Z literal 0 HcmV?d00001 diff --git a/images/sink_blocking.PNG b/images/sink_blocking.PNG new file mode 100644 index 0000000000000000000000000000000000000000..482cf4e9434af88969e7d134efd9337eb81ed626 GIT binary patch literal 39320 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

      n@@6j*ds!*`67m|-(_2`?xft<=Uu=5Nup;x zZ=|FYT%%p-b}ml6OaGyxaxha^Bp4ufC%MwBSt0-8c+@htVCs#lUv*GkMsJ-g4S7NE z_ik+_l)NXH&6F$}nk!_!WUg7*!(5ox8E|c1aY!_=rp2hseRT+p!2NASfrN0vEXmx_tvYP?YME{#=MC~tIM=$)0z>=?4$A8Y}|Or8TkjDaR+9`v}u=o|6d+*mM|~p bhypeDPOalC=~x@mYO~GujTsx<|NegfB2)=^ literal 0 HcmV?d00001 diff --git a/prometheus/README.md b/prometheus/README.md new file mode 100644 index 00000000..1a76709b --- /dev/null +++ b/prometheus/README.md @@ -0,0 +1,89 @@ +###Canal Prometheus 简介 + +Canal server 性能指标监控基于prometheus的实现。 + +关于prometheus,参见[官网](https://prometheus.io/) + +#### 当前监控的canal相关原始指标列表: + +| 指标 | 说明 | 单位 | 精度 | +| :---- | :---- | ----: |----: | +|canal_instance_traffic_delay |instance延迟 | 毫秒 | 毫秒 | +|canal_instance_transactions |instance接收transactions计数|-|-| +|canal_instance_row_events |instance接收rowData类型events计数|-|-| +|canal_instance_rows_counter |instance接收events包含变更行数计数|-|-| +|canal_instance |instance基本信息|-|-| +|canal_instance_subscriptions |instance订阅数量|-|-| +|canal_instance_publish_blocking_time |instance dump线程publish阻塞时间(仅parallel解析模式)|毫秒|纳秒| +|canal_instance_received_binlog_bytes |instance接收binlog字节数|byte|-| +|canal_instance_parser_mode |instance解析模式(是否开启parallel解析)|-|-| +|canal_instance_client_packets |instance client请求packets计数|-|-| +|canal_instance_client_bytes|向instance client发送数据包字节计数|byte|-| +|canal_instance_client_empty_batches|向instance client发送的空batch计数|-|-| +|canal_instance_client_request_error|instance client请求失败计数|-|-| +|canal_instance_client_request_latency |instance client请求延迟概况|-|-| +|canal_instance_sink_blocking_time |instance sink线程put数据至store的阻塞时间|毫秒|纳秒| +|canal_instance_store_produce_seq |instance store接收到的events sequence number|-|-| +|canal_instance_store_consume_seq |instance store成功消费的events sequence number|-|-| +|canal_instance_store |instance store基本信息|-|-| +|canal_instance_store_produce_mem |instance store接收到的所有events占用内存总量|byte|-| +|canal_instance_store_consume_mem |instance store成功消费的所有events占用内存总量|byte|-| + +#### JVM 相关信息 +> The Java client includes collectors for garbage collection, memory pools, JMX, classloading, and thread counts. These can be added individually or just use the DefaultExports to conveniently register them. +> >DefaultExports.initialize(); + +详见:[prometheus/client_java](https://github.com/prometheus/client_java) + + +#### Quick start + +1. 安装并部署对应平台的prometheus,参见[官方guide](https://prometheus.io/docs/introduction/first_steps/) + +2. 配置prometheus.yml,添加canal的job,示例: +``` + - job_name: 'canal' + static_configs: + - targets: ['localhost:11112'] //端口配置即为canal.properties中的canal.metrics.pull.port +``` + +3. 启动prometheus与canal server + +#### Prometheus expression 与相应 use case +* Sink线程空闲比 +``` +clamp_max(rate(canal_instance_sink_blocking_time{destination="example"}[2m]), 1000) / 10 +``` +**sink线程idle时间片比例(向store中put events时)。若idle占比很高,则store总体上处于满的状态,client的consume速度低于server的produce速度** + +_简单说明一下range-vector,通俗来说表达式会用时间点前range-vector period内的所有samples参与运算。_ +_range-vector值如果太小,图会碎片化;反之,实时性会比较差。请结合scrape_interval设定合理的值。_ + +* Dump线程空闲比 +``` +clamp_max(rate(canal_instance_publish_blocking_time{destination="example"}[2m]), 1000) / 10 +``` +**dump线程idle时间片比例(仅parallel mode, dump线程向disruptor发布event时)。若idle占比较高:** + +**1. Sinking idle ratio也很高,则总体还是因为client的consume速度相对较慢。** + +**2. Sinking idle ratio较低,那么server端parser是性能瓶颈,可参考[Performance](https://github.com/alibaba/canal/wiki/Performance)进行tuning.** + +* Delay(seconds) +``` +canal_instance_traffic_delay / 1000 +``` +**Instance binlog消费延迟,有两个注意点:** + +**1. 如果Canal已经消费至最新position,且binlog长时间未更新,delay的resolution受到master_heartbeat_period的影响,目前频率为15秒。** + +**2. Limitations: 如果store满了(恰好store中的数据包含最新的position,且MySQL binlog停止更新),且client暂停消费,那么delay会不断增长。当然,满足这些条件的概率极低。** + +* Binlog接收速率(KB/s) +``` +rate(canal_instance_received_binlog_bytes{destination="example"}[2m]) / 1024 +``` +**'Sink线程空闲比'与'Dump线程空闲比'都很低,delay还是很高的情况,请查看binlog接收速率是否符合预期。** + + +--To be continued... From 47765526050851ff34f15eccf7e25d0e04b5c6c3 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 15 Aug 2018 14:26:55 +0800 Subject: [PATCH 076/360] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=20if=20(logger.isDeb?= =?UTF-8?q?ugEnabled())=20=E9=98=B2=E6=AD=A2message.toString=E6=AF=8F?= =?UTF-8?q?=E6=AC=A1=E9=83=BD=E4=BC=9A=E8=A2=AB=E8=BF=90=E8=A1=8C=EF=BC=8C?= =?UTF-8?q?message=E8=BF=87=E5=A4=A7=E4=BC=9A=E5=8D=A1cpu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/kafka/producer/CanalKafkaProducer.java | 6 ++++-- kafka/src/main/resources/kafka.yml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java index 4543aac1..0bb15308 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java @@ -21,7 +21,7 @@ import com.alibaba.otter.canal.protocol.Message; */ public class CanalKafkaProducer { - private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); + private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); private Producer producer; @@ -83,6 +83,8 @@ public class CanalKafkaProducer { record = new ProducerRecord(topic.getTopic(), message); } producer.send(record); - logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); + if (logger.isDebugEnabled()) { + logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); + } } } diff --git a/kafka/src/main/resources/kafka.yml b/kafka/src/main/resources/kafka.yml index d6ccc51a..2729fc27 100644 --- a/kafka/src/main/resources/kafka.yml +++ b/kafka/src/main/resources/kafka.yml @@ -3,7 +3,7 @@ retries: 0 batchSize: 16384 lingerMs: 1 bufferMemory: 33554432 -# canal的批次大小,单位 k +# canal的批次大小,单位 k,量大建议改为1M canalBatchSize: 50 filterTransactionEntry: true @@ -13,7 +13,7 @@ canalDestinations: partition: # 一个destination可以对应多个topic # topics: -# - topics: example +# - topic: example # partition: From a1588e5f983dd06bfff24ab7c9e99d412f502935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 15 Aug 2018 19:45:11 +0800 Subject: [PATCH 077/360] fixed merge code --- .../otter/canal/deployer/CanalController.java | 8 ++++++++ deployer/src/main/resources/canal.properties | 1 + .../canal/prometheus/PrometheusService.java | 12 ++++++++--- .../prometheus/impl/ParserCollector.java | 20 +++++++++---------- .../canal/prometheus/impl/StoreCollector.java | 6 ++++-- .../embedded/CanalServerWithEmbedded.java | 6 ++++++ .../otter/canal/spi/CanalMetricsService.java | 5 +++++ .../canal/spi/NopCanalMetricsService.java | 5 +++++ 8 files changed, 48 insertions(+), 15 deletions(-) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 6b0fc934..aacace84 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -98,6 +98,14 @@ public class CanalController { port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT)); embededCanalServer = CanalServerWithEmbedded.instance(); embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator + try { + int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT)); + embededCanalServer.setMetricsPort(metricsPort); + } catch (NumberFormatException e) { + logger.info("No valid metrics server port found, use default 11112."); + embededCanalServer.setMetricsPort(11112); + } + String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { canalServer = CanalServerWithNetty.instance(); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 9083477d..1cefb7d9 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -4,6 +4,7 @@ canal.id= 1 canal.ip= canal.port=11111 +canal.metrics.pull.port=11112 canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 485f6728..18bf63f7 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -22,6 +22,7 @@ public class PrometheusService implements CanalMetricsService { private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); private final CanalInstanceExports instanceExports; private volatile boolean running = false; + private int port; private HTTPServer server; private final ClientInstanceProfiler clientProfiler; @@ -41,9 +42,9 @@ public class PrometheusService implements CanalMetricsService { @Override public void initialize() { try { - //TODO 1.Configurable port - //TODO 2.Https - server = new HTTPServer(11112); + logger.info("Start prometheus HTTPServer on port {}.", port); + //TODO 2.Https? + server = new HTTPServer(port); } catch (IOException e) { logger.warn("Unable to start prometheus HTTPServer.", e); return; @@ -112,4 +113,9 @@ public class PrometheusService implements CanalMetricsService { logger.info("Unregister metrics for destination {}.", instance.getDestination()); } + @Override + public void setServerPort(int port) { + this.port = port; + } + } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java index 73102795..867e1e89 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java @@ -1,15 +1,10 @@ package com.alibaba.otter.canal.prometheus.impl; -import com.alibaba.otter.canal.instance.core.CanalInstance; -import com.alibaba.otter.canal.parse.CanalEventParser; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import com.alibaba.otter.canal.prometheus.InstanceRegistry; -import com.google.common.base.Preconditions; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; import io.prometheus.client.Collector; import io.prometheus.client.CounterMetricFamily; import io.prometheus.client.GaugeMetricFamily; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; @@ -19,9 +14,14 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; -import static java.util.concurrent.TimeUnit.NANOSECONDS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.parse.CanalEventParser; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; +import com.alibaba.otter.canal.prometheus.InstanceRegistry; +import com.google.common.base.Preconditions; /** * @author Chuanyi Li diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index 3be87c3e..d345602c 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -40,7 +40,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance"; private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); - private final List storeLabelsList = Arrays.asList(DEST, "batchMode"); + private final List storeLabelsList = Arrays.asList(DEST, "batchMode", "size"); private StoreCollector() {} @@ -100,7 +100,8 @@ public class StoreCollector extends Collector implements InstanceRegistry { holder.putSeq = memStore.getPutSequence(); holder.ackSeq = memStore.getAckSequence(); holder.destLabelValues = Collections.singletonList(destination); - holder.storeLabelValues = Arrays.asList(destination, memStore.getBatchMode().name()); + holder.size = memStore.getBufferSize(); + holder.storeLabelValues = Arrays.asList(destination, holder.batchMode.name(), Integer.toString(holder.size)); Preconditions.checkNotNull(holder.batchMode); Preconditions.checkNotNull(holder.putSeq); Preconditions.checkNotNull(holder.ackSeq); @@ -128,6 +129,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private BatchMode batchMode; private AtomicLong putMemSize; private AtomicLong ackMemSize; + private int size; private List destLabelValues; private List storeLabelValues; } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index ccf21020..9ac5a834 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -44,6 +44,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C private Map canalInstances; // private Map lastRollbackPostions; private CanalInstanceGenerator canalInstanceGenerator; + private int metricsPort; private CanalMetricsService metrics = NopCanalMetricsService.NOP; private static class SingletonHolder { @@ -64,6 +65,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C super.start(); // 如果存在provider,则启动metrics service loadCanalMetrics(); + metrics.setServerPort(metricsPort); metrics.initialize(); canalInstances = MigrateMap.makeComputingMap(new Function() { @@ -529,4 +531,8 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C this.canalInstanceGenerator = canalInstanceGenerator; } + public void setMetricsPort(int metricsPort) { + this.metricsPort = metricsPort; + } + } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java index 6d823be8..a4528800 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java @@ -39,4 +39,9 @@ public interface CanalMetricsService { */ void unregister(CanalInstance instance); + /** + * @param port server port for pull + */ + void setServerPort(int port); + } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java index 0d6ad088..45926348 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java @@ -35,4 +35,9 @@ public class NopCanalMetricsService implements CanalMetricsService { public void unregister(CanalInstance instance) { } + + @Override + public void setServerPort(int port) { + + } } From 3053b9635ac124a58d50a34623bd9452a1efecdf Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Thu, 16 Aug 2018 07:59:45 +0800 Subject: [PATCH 078/360] Add header size for each packet. --- images/DML_model_indicator.PNG | Bin 0 -> 35317 bytes images/QPS_empty_batches.PNG | Bin 0 -> 44056 bytes images/RPS_client.PNG | Bin 0 -> 36310 bytes images/TPS_event.PNG | Bin 0 -> 37363 bytes images/TPS_rows.PNG | Bin 0 -> 42873 bytes images/TPS_transaction.PNG | Bin 0 -> 43954 bytes images/client_band.PNG | Bin 0 -> 45392 bytes images/store_remain_events.PNG | Bin 0 -> 56792 bytes images/store_remain_memory.PNG | Bin 0 -> 61006 bytes prometheus/README.md | 89 ------------------ .../listener/ChannelFutureAggregator.java | 3 +- 11 files changed, 2 insertions(+), 90 deletions(-) create mode 100644 images/DML_model_indicator.PNG create mode 100644 images/QPS_empty_batches.PNG create mode 100644 images/RPS_client.PNG create mode 100644 images/TPS_event.PNG create mode 100644 images/TPS_rows.PNG create mode 100644 images/TPS_transaction.PNG create mode 100644 images/client_band.PNG create mode 100644 images/store_remain_events.PNG create mode 100644 images/store_remain_memory.PNG delete mode 100644 prometheus/README.md diff --git a/images/DML_model_indicator.PNG b/images/DML_model_indicator.PNG new file mode 100644 index 0000000000000000000000000000000000000000..bb999511eddcfec80c7fccf4421cffd8c8f9ce14 GIT binary patch literal 35317 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 YsENHN=)t04(`#$L^;uou5p?7~0Y{knLI3~& literal 0 HcmV?d00001 diff --git a/images/QPS_empty_batches.PNG b/images/QPS_empty_batches.PNG new file mode 100644 index 0000000000000000000000000000000000000000..fd29572086c50b950457ba0cae74898877a52b2c GIT binary patch literal 44056 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 OMMn=CAHp1PyzpNcYVHaE literal 0 HcmV?d00001 diff --git a/images/TPS_rows.PNG b/images/TPS_rows.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d84b94cdaad94921bcde9ff9a38f9fa32ad076f0 GIT binary patch literal 42873 zcmeEu2UJs8_b-kpiUN)erNtRXu~01Z78OTykf72#4ueQbKzb-q8DT7d6_6^U5=ns2 z1A!zULR6$^2mwL}y(EOtl0ZU|cd^W;^L?}a@2ywXTW`IrbxCe=&OUqZv(GNSb1pZ} zK`i8Cm1L!)q~y+=K6yb(N(Le&^&NZNTHqT@f(0LVTNQG_;)GOjo600`BkgnC>bR6t zY08G>i)(=U^+BhdLZqa&?^(H4ow@MSjFc2o{>;hawvp~rA{Ozdh$xWXJl2H6+kk&V ziuM6#P93}6(|i-Og?3!pYuH>Wek#eU-gC>&-(&*LZf&|*>Mis8s#PzKZ1>!`X~*jC zHl9||IcfyD6q@t&RL1+uZRPnnVGa&_L0A~q8IKZD6PYYQ&{HQ0A2nS(O{mQ$&~>$O zu3MX$$0TmOg|{;i2E?Vraz$-Lq;|ko?JUN=95D8LR$t*3AMI`JT-KcnzwCeh@UK7i zWhgIEa345XVj;gN>e$G4_Y_V-HbosDS$hw3GFhrb-aWKtdqz%=${nPh%*f4h<)-q? z)eGH|mQC*!wYDc(yJ^UqV*W)A_*9@}tHMtimfTx?Qf*Vsv60pHwmcj39a;C%##6O! zpPAZ6hHrFi{dR(ZtXb}!PJeR`q}xWxUaYa$Q6Y)&58}CK_tm; znLkW+UAgvJ=129@jpWzgOC#%zBG1P-kGvd{G*LOj4}LnlHKi`7G?Mw7wnpsYlety zJEl#Q4zA~2g^f&7K=L+h-O{ZgRJ$5FuD@9Uqgh-%)Z6M(wGK}+tTTnT6nFPQ;&IT! zSg5h~EkyUy*(FY<`vLh}W_G^5`1r(*yNX(_`x5s8D@jVK!|E$D`7Hju-RmG1jaxQl zbwL|nUMCk_4ULR`#jZtFj|Opv?;nUKFUsWP(p0Vn_s^{+wJSz|wr72}LaW~BbUb;* zIzG>#G_vHuo{bR2!5PgayG-ohEmpBAUUSs@wqWhl$f@gOQReDkMqc@Ko0ZN|j;nox zlvb3;9SEIAT({ZbKi+YrJ#5(eym6?L$mOx<&=F=UbvspWEwT~Wpb4{>OCnzpQR(*= zUd{Cf_6wp~O55G-G#Q2ZjItUYp`UvPpPQ&pb?)aSu)oLpTz0!TX9n%(v^NJ%yTSTM z3*pcBQJ15WTzo8D8YXu(?ElC_VP@&ao3odM0~H&kWL9}#A~s2Peb?mvx+mqZKjV;Q zV@LjgPAAmd)Ie3bOmw#rl&aU4^V4Zng6RV#x|;dL0H-sK#*6uWmCwS#(L@)negI~S z{XThM>qq)s=>uEyE8T*O;BfljE?v7D86v4tiPgIX{c{%7O7Fx0o%LYs1|N&-ukDfD zs&uu{u&&iZ;&!gkNn$7$f<9LnaYEqNU?w~lPQ1H0k= zXf4v$({bl|J()}2XW2Kf2Y3qyr}!fs>3(N=U5PG z#W|c?1$2Fvu_9IMIfEF#es`FXZE}pTV!NNbOhHsU8GgD)E4>hw0w17-68CO^Bw|PN zt2IPXzg)KgM)te0B8z92H1bf9`?>TSv#P?MIu16z70>@KtSNult6LYI}RD_t_8`)ySg$$A3GCo zjgK8Ik4T^%G;L%+z+*Kld;l(u<5!D`;WC>S&RHW zJfQd~`?z>+VE#zrwOX_8T+G{ZmO-eusFzQi)*>I6QID>P4vCG^e$T=cGI{GDWFjCuWzJkD*q@ngyZvh62Dsr)ix-Ki|~DUJKy=wt3L;!s{D8j zz8vLHmuv0aZ?{erg#~4_z26VMQ&vIwH51u>^WM0H=97Sp5R&Wot9Gs`dNQorrHiNv z(32|PYp`bGjbfI)%oD`JEO<~*2SLKldNoiV8g>ruxo=^IS>UfPKy_-$4?C&Go8^;= zYvTrR2q}DB^0kE3qcl44#bKF#EM{=^Vto8UB})mH^Fko%@2u@-4#fN@XP3b7+9AJ1 zAj6BsdbLc%D&cd+>VJ5@x0*J+WFJ`DIiP1t^VxTf2_q*A{7DS>O;{m6&cp^TELDhj zI|**_Cq&Pe_oUx&x9TokZujl=#?VNri z*8HXA@E#P~eM@V~c{c8CZ5BXoWxtjB?VKQHEZ8{%jW`~dWwlbBw*fn#F`C=@- zPec$LUn&aIU3L$Pb!kB{dUVQi7R(@5B&X!ttS>ZTOA)yrSZX!7T$buWfa(iQqSB<7 z4zAGXF4(bZ+uCO|BvwOi)hVpC=rBuW9hVgJlTLR*cqBpB6b7exN7m-2DHI)ya*f7D zEyn4sRW-J23`Aq1r+5Y6?Oc}He3mkfPAR)b3cBp}G-YsN9>wgjML^PMWxVt|eMoV5 zw~phuUJ7l1e+p_r^r;<>V9(}UuGPUH`|Rat&&7_2OF=Kw<)0f6ldcI;z}_y|^P8(=VC=pK`$>Mhx-3 zG-0QQ-h$DTCWo>8FT?�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 CRkgPO literal 0 HcmV?d00001 diff --git a/images/TPS_transaction.PNG b/images/TPS_transaction.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9d0f2344b08db7907b0ffc2ed3085e74f0c10efd GIT binary patch literal 43954 zcmeFZcT|(x*Di_`6$BfIARt@pG^HqnBA^rz>sCPeMhFN25t7i7sI;xpHo8H&D2jz5 zy%Plm6$w=Wgb)F#AqfzYKXLj z8S{-BWH*S2h-|d9IB`KlMATA5WaZ4dwcvl|wph1-zg7fXFgFn?!0ns_Z&pK%t&K%Q zisHq%u4};i_17)z0z^cl_Xz*2u)Lr=BO>z3-13C+??Fz}yvZ76_Y8F|RWQO`Grq0dWfep41s8@xPDDJ1f#NUWxKqsFoyx2UymRJu%J79l}pxUaKj79F}*Zv8; z?4ImUe2t%^8r?*3uvLuBQcrO<&lq7(^La&AO-fBV>EmtV8@DXk_3(_6=(^yW3ES3c zuUGQI!)(hF0+lv7`k?ZvVD)3=qj#=v-~HVOH+Lv0N4i?OSXA$1lCm6)Fmh^DurI?E z?sjcA9McoFeRoqt!nUZgz~d=dx;s|B(?2mCBMMO?^EdQ+%*ZssUlm`9RPq|f_l;FN zQT@{=y(}1GZ~f43U@S6g`%d;C!yk=yKnl2>X|nh(7Mhynd<{+5Zm+n;*bl8ZPGhke zV7?B1m9;!XDIS?u7)Lm&uXJE(*w5>LN4#efv?MuKT%DejOR*z0N>nGvT-pADhI#4 zT_SRp?ya#qB1a?dy-DYw=01pN7vk_6pQy-IO%;1+l5~1xU-1cxe(69p>37VCxJB5+ z2JPf-3~F0c0g+Y08E3j^uYLZW91`>+wjORaxggW?hmse~U0%tHuJS3&AZl!ZLQA1~%-eW(UPk4>^y=>-SO01V?p&!ILE#iL{ZhY_3nJzj+x z)hx#5B*%H}Mk$j{99*}MbVOA7-)paJ@|Ib6v(?-_C|UG=>tgwvVyT!Wwl-18rMcL~ z{gt$CS4pt^;%InO4qf)GCtZ)w#`WoDiiL0A7M)|T0(vnrL2JS`S-!$AXzS+00<7Cf z#CZRLsk>-#wC|O_i1YVX&5o}xVd8w=-%TrymT1bdvdbb?J&xi8&SXzNOH)X7cAJ!= z>S_)(1dPQoW}_FO3uk>(F%m>wL7wg2pd8V zTV%8JGzqbY<9PL+H~JRfQLJquh&y-P?r*FCBO++T zQSm-gc5|Zinx6D{KLJ=WI z^_m(FS5YKdKEL|5{Ng<#*5#4QlK|1sLnkJx3*yM@t7%Ab$OukQ-V(Ahyu3w^Hg0mg zUBBz);k(p}EXI6Kpqd-{_l?SIeQ4_252pE?;_JkFBL#Xk(g1_)`2DSB*c9?{~g}&KjmgVvzaR%yE0d2%$F5%|Hu(zWIi< zy*hoEHc(amO0976iXWxLah@eDKF5_Hmjz=Rump}>+uW6k-TVQYTJ=`4>^yp0I&&3e zZrl{l69rzBIjVHvc;hlWBhrVgUz{i zZWV>8^0`b@u8y3`IowkC>g(aWowD2VSUq}ES9&^Gg%qUNSSr4wl0sn}Rm&aM?cc$i z=Q_O&s44N797T}IduXN3XX)B@8u*Z%R>1A?=R;eE@f9-*%(@I1Ie3!MUt=hX{}O6& z&j8)ZDOw;K4rVgPt?+Ou12nJT)%Ak1>B!XKj`}lqwR?!eg}J!Y+s&$c++*_7;4DJn zD@M@vf#xwr1={bg)L>#;)seQe_krq#!4T;r{8=o)c@{msp~t(O7q${O%XY2Fg_}`- z0jz*zhpvovu$egjdR5KQW9zFC54KYmoP3%C-GU7u?N4xoE2UjEETk0?dkb+FIT+u> zK&U@?{)~k#BUjb&8p7iU1N$CLR6D=NlNN|vohI@@w9f|L#y>XrB8T)2!Ub%uZ!?<` za$1ZLOobDphR08jAmy~{@L#HYw_`JV!iGX94v3WT)8nvvY<-Uma?xz40Xc63kz`l6 zM*D9?ET2`znVkp@t1j^Fz=wI)FVrX7llH)k2CWU^t!q{36)+TJMadNNET)?F&U4MY_%bS8K>DI-yKn~*KV#3yWfls%ZBMZM}v4fLCq zQIohPjX2Sq(T*~AgNC?Tv?E1BYYIl22_04UYl6%XZSbTj46mp4v0fo|_X*ECtS4?z zR4p}=(A{3M+Xz=SMF_t;2BU606PQP~?JAo_v~GG#nsCD3JGf5$F8+K92Twza_4wgO zh`dU~U~`aNGfXw)b2X!Yw&V<_v0$6|WACeaC{95tb8wsM_HyJ>q~lSlcP%am!ino7 zmmyK&%!T(^X}fVq?A~e&wYGpC?>r9k?1mfV%4rkhKf35;dDlImSEt4McfgZv)z=hO zpvjL}EVeV0t|M)L<;2~Flg^^;O;AG==XQr=0|U=?PR1d$#C+Bgk|+WsF^yI7I=^dc zV5EWp+PhQEy>WN2D`y+qn z?MYoFyRjtpRAs9pEBD0+l9PXL&3l&X@ijvyhvq;k8RPwGYpc?O(n2yT*z>Q^gn5+s2CZu&H)<3(7syX%bZxi}PHff~yza6dwdGA)8p4r`rFlOK7Kk}j zyaicc!`(SyBnj4E!%)a#QhmPcNxquAEe24~#A^MLYy6PQt)$y`*YfI&Bgr*X{`5Jn z|Aj9yiP8X1sX@N2+o%7e5{CxPA(~Nb5)+qU*o6IqJw0KwH#V5YJ*AS4LwJxv?6FOg z0X%spyWZh zf7VpIeXK??Jchbw@RzacB zpDOYQ00RC|Ig4<|BV;A7ffc09`?bdVkw>;qpN@khLJ%j$@g4O6RYCg#-J(N79if}r zdk+r=+owTVwj*w+mrsw;l4>!LWRC+Gcx>RhZWQ5h0lcRqJt(-D7ZYH>Dmk=mf)y0F zdX1xY9l4%fbfAzmd%(f`Db+cP1RKoaqPLT9O}sl;oB4DYt~I_V>FdpE28}b07F0lXO%Ux!1G6Ml=#?wkCkZIBlZ`8?soKoCHr|o^dNDL(g$av2L4R1L zmZeB+QGLPDy;vJJrW!0@^L$xs3MwxYoeQX>!yxBaSrYL){uxClnqeQrsixTsjda!V zM^K)jttyM@cPh#_Wt#%)@(@HG- ztASDZZe+VU%?WCN*IsE5@BEghijGwqh}(m=a@cpW?H+a9u|99iZGc;s6fqF3otp`pWfeYA zv1+(ze|zY3AB)v*+DUyADB$%mk{z;$SZqMwBIQ6HtS5rxl@+f9rO%ZUl1K#wgnr*v z&DfT?>VkyQ&5xQ6E3KIFH0d0@P`Oo3Cd2G`k392DV$usERzyL|4af$QPA(!wM|lYq zh27w#or%maqOXlcN_50wmuYc1xoi*bV0rBJRc#)y7YSP2cDNkm8bqC3No<`d4Mxq5 zQ#sWSG842MFoZT&M+ZUOXuM^Ygyr}s4_Um zJ69xKtoUZy8Hfu!#JV()wrJ?n+q4Go5us2H}(<(a>nX4aC%HWYX z#^R^a+_tRjI`BhWxOqg_RzCjv`Y>L>_wwSYKJIjf%)v9GDLIa^EBrYyTZMo3jF+>% zYDg*)RyGxA6dTA)OPJ7GQjL(@!QU|VwIsE+`7^!*LQ4=lCruX^*Ox%K@|$w2pG`A}P8^YI0l_7$*Dk{rl7v zUb~dnc!DlP=&Cmlu1Q*~0wps1hfeV99P;>-U*bxYe=az%Vfh6X*qd6y9_NR?l?wRNY^#DcDoGJTC7(kG;K^6<7}_~* zaPcVF_jj`AfwsI)YE@MJqIS3QA!Y4lq7Kk-+z@Lu(ywKczL&XInRkrTJpM`$!nmK9 zAR;Fc7;Hj5@?$b0=LYAZa{|57Cqq%?Q{_+b_kY+aa`4~=OZujgMMjMyG)!ka&Dd*q zyLVjqxl(6~etpv;F=2f$Xrc~gJXB2EwVuZe%=GVH2TMyp&1B43w%HPkNL4O$16a-JcxPH`wV zZda*)-ycf8n|~VE{RpD$sITQXO-9tL{BjGVk)|#IOdM=;It$ivZFM!ugpl|nC`8VM1LZ;Wwa*n5FvoX`q_$>8A7ez^(ruDZT7&I6Ur4f}frixgP!c)5vAS(1g&$m$5ZFP=hbXtI z+pkl8HBBByney5u&( zHL;aV&;E+Ypv@4iMjPhiLZ6&bDK>X<)vu#)tNy5Fvi;qs%7r( zNyf|;zcdw*H*k83?SD9mu{AL+SbS?@rXflXf}{cg@}gdY zx5F==)^q1t`rhB$UQ$)1+o%g8fMRk*RLM*4Cx9BdNm^oyD*WHuEBK|gg?|c`cm7yoNa%bRd-wL|nM+l( z!w#X=wRs$G^awnXEcn_Ku!yzq_fD&LF*#o_9ZqT4asBjV3)%3GQmy_MneA_b&If+o zceKoL(7vmZi}OK=OQ*r>oP+xp4tB@6!&g6kvQSNoEVOT3RoVk@CDq&hMXt_wEnV4B zhD3ZuMqFQm#rqr4NgK(zh_o;`)~k;e0Sn>Y_iO^}l4{sJtVWDXZp<6S#o z&QsMlc$rx!s{Ih2g6Qd?G%E71H&d=D4i&%uiRfWTL#I1EUI#04%=iJ~rUcUuW29p+ zF;%%+9--^{IiVTaR?#_PM##0VTW125KE$@j8lThCRq_(A?l*a3cD-VgXW7d3ruZte zg-Q+l22My)7kaOUagda0aby+sO-T%uWVeo1U04I>gcKm14sq`pHK#_pe?F9Y&(+1d zKuV%KzvLJmjf#c$hNALQ7Xmod6KJ1LQ@VuWU^&A0?ejz|m6U>~>mLkoB*qhJsqpsD zy%n@c+f;e6j&I6T@xC@v&*`168g zDo*s;ZW?B+yv!%_7$(LOsVICnWE$2HAnE5YSYcE z>A_!W(r>iYR7+)8S9jax-1>niP0&96UN4Rf5y5xLO1Ob86@3z%P(6{7u?k2!$rWZ< zqnCEx-;yXTdWkp6fMuW6+oF?)5MvG{uXmTSbQg5w1bsH6k+gE)drc((@I(I#tG z$)2dl{0@gRt|A1uoS1jNlxW@CUD?iUBemZcZE|D$GK2cacua*M{?gcD)$bNp7`lcB zPeSjj!KH=lKnPGbXYIg_!;oJbB5PxRI&EJ!GV@Y@)xemHnli=vph>5UkLu=K+9p*V z?OPj*3Pk(?>MKoni|r+hK}_?iO*?Vd)v?suga?N8^2sDqj-~0#&w_?49{6~k5t87iwS^07Bx}UCTCnC0mkkkcNw8wXq)K?c^B9y$^ zLKM$cB?Jbt3N1!voe$RfU<;lm?@&=lly)>Uq(JW{>E09a8R2+u7F5oH)cjOi4=GJo zYQ~6;@et4Pqebo zwky?7^5dr|e#Tpq&P+n@*2HpBmQJhYZ)tRQlXsc!<}fTleLzlmp1hdRKseUGv3ZVK z94+wvI1a^Q<@6K zMD5t)rK%z@HKdcbu780+4rTp?aq~9V&g-X7S@dss6|%Ba<||zEs&PPpou=O;h;utf z*Gq1`=+XkrX(cZX%I7Z2dx%w?ytOcQ1g$d;kmBl}y)}kBE$G_b|*$a&2-q4?Q>i6#p&ezwXts}sLm{ux0<){9r7nQ9`1-_q3)6M0vr))Zi!$M;k$dy zwkFCxZN3_r^Cst?I9O-dtRInc{3pu-!dl>GQ;RDmZ}CIYv4e%rGd2>P9Ub)7azmVw zVt?kbxrN@ClJc6n|14ZYjvHte9{u_M=?nN>LJCvI4i}}*;GSc?^AM8@4}Jg1LBTfu zR+F0Rh$rU-GRnI)nq2=wrpf0giwXY8Z~j3EMKn+T{P^>>pNv)G;{W^$ z=Mv@@$Fq(m$q1JeByylRab`Lsi0%J;R0ZE|u%?zq?xGfUL1awPB5>)Jv#f6+W3at=$4#|&`9k(LmF^w+92Q_ukI@bwl(a%Dze)>H+PQT7J z=`;pduVEXjG?w8#(q`@GoM9h^I(!-yRd86D;&9o<`sD8QmLn;dL}(CGiX2*>T0uS( zDrfA@A{~K6yoe=FZ9EZLhOd~cZp~2g%4)Be;;mk~`OU+x_9(+LX8Uwfq5e81o$qgd zk?|}?R~%BN93zxwk07@3tvd%xF1)f$0@j+=G`2m`Y@ar)Dan4mH(lyVX>BlXWnaO~ zvXN>wANA!(w#xS@t7IJxBU0JZxGhRv(AQUX290U2frcYg0xp<#qM=UA^I=h*=M0r8 zl?5^pIt`r~{Yj2TW>hvFxX9Q^66A(PHv`4#TjPlaRU=p+%P5}6J*c#>Sk*Mw5Rjx=L& zB+FrIKIJj&NeZ8T>P}8%wY*;?fzjOF4GfhNg47b!t-FeKtJ~Tg^prPBoAgGW-*mO9#LE5&`EzFj z8C3Q6^=E!S%-%N>pr7)L$_Zbc(P-iR16@R{wk9V1)00jrDXSX)1i-G{FA@T)Y>mF# zuVE08v*&-y8vuvG?iA^AI^&f_1JIlVNp#>FB-0xc3 zqL)hsK7U_AF0TmoF!0+?3#14Ll&VLg-wtQmcI6^%Q%7)DSwuzK7!B`M3@jN(N3gW7qD!BGQOZcNK%^+8Auj zHTO&)*f)Dq7B0RjKBR8m~E z{26f6&0y9h$utP&bOa;EY|e_lJ`swCCJRrOC3Sv{vKOY^n@8W%uDKmzHZH&L(Yo($ zn!**Whjy6eyR{yu=P;B!&AQK2bX@(jY0p@?=7nhOM0t*Z$8KeV`4&m2%^0glRXKKr zS0%|SGG}%?wCDZJKlr3Y+uk9PF6lW;PzR*y5-;UCEy+Jjc6IM~5S5@c|KN}jI|$+V z&Uo@sUEp*6?*~EuuFp*M5FB9hDhDDD+q$G=ZSE&R(x8>)v5yH^Q++ugTcSg7e(W23 zL$+vz;hP}5&BQIwa&_z(<6-J=jwRCR`8iqC6x|MxMtmu0IOm>ft!I3B#f78C1t#eKj{O za8Y`?L=0u5{$qm=3IRR4zhC_L_WIiun?Xmu$Io@w6fI2xrVL?*o(A-4M?zH5K@Q9Y zOg>S`3yES$2r@;ETWol15#j};HnZLPc`$nm*{1#B-y`V7u6Cb0$z@rweKays&h^^v z)$%HvR5z{ah$CFyv!KG}tp-pcKc-947kT7hQT>5=0`Hw`mn@pty6ADPpQlIOaE>5( zYJ!^)nNWJZi>bOrzGYhCBBA0=LEB~GUZJ9K^Y`_Z-WrMW@@;u{AnE>^m7|Ok8mIz~K4X9wzrIrB9JDkb9`)P|eCtjYJ>= zVfS9J*zmL)Z*`B;qF6^a-@X2aqdhkDLj#%dCqwK)jjnO@2C~^_U3ba0UETPk#++$U zA}XZF8SonaXf!E7D|5$}{*UTl zU$DE&s_?Tfe=;+(kiZK+N;1lDuCSMRz?`(+qh;E9iuXLXS;{-&#mNeA^Y>tLbJyCZ zMFj>C(-M|x_`c^ghLT2d;B@ScY)WcjYaBZ&u5cbE7t8tm_2o zDEtDO+yIHp8POExm>%*nlk`Hj(%6@_pXLTVCyTvYcxb;PH6gG_^Kpp4Hxuk$9Zq!W z2j@3ec0Mxb-r;^+R=nRTdg?3>u;7zJZdmNmem}G^>+P;R5BO=1i&${QKQFLZ`-HXG zHIptU0J|7LSf8kzjG?QTSoYWvh)HLVnWwj|ypcL;uh(<3?$*GL_O2A63a?@PFW4Jh z;c4ExqwP1C*vX%h1%R+onr}GSKvHOtFvvt5Tl=&H>>MG6SrO|FAKM>Lei`|&=xXIb zWp|y}oaAzo&O;@Vf*aWwEz{2PJo!#UI8#{Oep8w>Azj1PK7zh?>Ymn)6pd5~G2m^R zfBkqg&=F(hR5PFQ{6464v~z8X*3P(sbd0ayZGJqonPX7FdWV1i$?s}x9Tq44;M>-WD`Y@m5sCAlB%`YH(rhD}docWVr{-3Y~L_#K{*|U3{zNxLOIX9KK zS7~w~=4vxKzHgmnxY@aW>BP1B&}sQ>y5%}cxe$JwQ%4-HSjo%&X2&{}h@4F^Imu%~ zpZ@N9nH$%9Cu&o7_vVDP-wjF}au+{&4|k<)T&y&R~)mD;m5bUD|1>$Qg5Bx z7tK27XaYL;(C6uOpfPIk;Z%{?H6#I~fUe_QAv|&RV#{~l11Ny|@OM+zTZS7Lp_B>I z0^*I~iauWa88eFw-G0h-fr8zAJjWGYuPGho*VbEvgR=QME7ur%7*|$J z0SXt`&$&3%2hxzC(&_TwvXv4*=6e23=FVU+*TU-jCJQAboty0s|DK`z-*Ui*vGPw! z-2eZ<79}8Nxovrr-m{T-*&O$tkQaO{N8tYd%HCa@C+1Vi9u7sEV&!F6(jcA&W zdmw%z^kQ{9CH|hPj90AiT@9`9u;Y`L#^~sw|Fj*Rq{DxIa$#>Xz}+`|dGJb;vg9s< zlZB&}iW)JG#~+**fB+O}I{UM^{9miC{S=Y@X9|0}XTlY^yEvb0>N^kDFT7XxAhCNY z#NCFQ&5hU{4Z*^V{)OyjSxk{I`<>#R`*k2`MU z7psn%wCW3&G?Y<~{3xA?73nsOU-LX(LF}BOf%GH85S1636E9dEE71~G1Msr=JK@2p zh@6~ve_4qzI^0%FJ!xLqZNBsst7{QCzyI9y-32$faBy>C;udqY6+b>-cFQ|I6|?U3 zmM>0NoI16$YW2?{kE7;oZ?Y$fa?C7F?fKbKU)ZPobc@n~1ODH({QUgq1QKPH4*dPI zkG9D8KND2>>7oDQ!T$#_m^6xDM#B2)In*ODFLoO7dRrsuK;-BZ)D^l%qq)TC4{hiN zb@T7qibvY?|H>G6UEO(%F%Z?8?ONOyC(NCH62WDQJ}dL+)mMV4v)~D;nDw%SJ0Y?) zS|TE+4VNO;^3LvL5s*Q}=@Le4mmyqmS?I%`nA3H321BTuR41d*U2cf=RX9 z=;QfSl;Ldks)ktp($DUJX)*`tk+tfDp~NirfbHr{b`MX7EQQX6-AlO!)P&K`xbc!N zhty!{`STysMpQ&vDDSFs?L}Xw_gCuRR*Jm2x;62_orDJuj@?)>RM+%XZXeRTOYrJ-yZFy*{u}Oh`FbpKV^-U&@{SZ>O>W{lwKp@jkZ-)$ZoYR#+eV?|>xDpx;5 z1L)PO;-_4WH!8x>AAZbP!r6ZfRmTcbA_Xhp#Mih~e|D^Wm~}E$aa{mw94wN^>AlP1 z>&Wv~S_rD19@f0iFHZCLQG=uhFhMfr6Nme(B0ok`!|oSPt%VPD-wA6DmWf8Qj{{ew z?nEze@|`gJ581~6d%JJGgv%F(KkO00wD|B-61TQ&ggbDrW>}>iILTc}2F7HYN)i>J zt~Vx%gg9*(y9CQ3BI4IrSYi@Q%NaxY+kfp%uhL0MVJnAM9$yQ=0r}z^C^!F#r|{mO zM$$J(tMdXkdPMx(iTFM6P}-u&APvuDjP?y20@j{h`%5et*WQ@+tZ&qh*&T1N4Z7l? z*}fhF;?$zh&r2))+p?8@x#6v6Ky>iB)3^BZw$I*zRr+RtY0eesLaJQhlm4>8Om+p+m?O577i8etpl|!m{Nq2x{>5lH*1pDkT$7_n`+v z3fGC^k30SoL#fX<`51C%8vE)|9KCwq3KE{C6ApIHXo$IQALC}hNiG}U!{d7(hQR^# zU7_?ECq<~pqprw{`-Di-N>Z0F2*e0iioMQqs}zo{vcZ6pAtkHkS4cw!S5OEQP`RVN zKCv~ldI+X7-3Vg{L34u)8?Y=IDGMK6V&8~oMs;xYNZ0$GPsQ)lExwAXjHfVw2%Y(f zy>;K2!0xuzR#iL9bZ^m%Jfl#i&ny0$TdVqnVl&=|+3SWPOUmnYU&Qv#U3`JPK4kKW zKhg1|@5RO3?lydMxV}@!I^Ge691#`p`7gl_^ul5<~9Ju`jud9sUp5GkUf!4Qn$S$S>^_o&10k*ls0; z>AjbJS7iHyq9z*esTzG@&(!xhDGu5szm%l4flJDBQyH$1Lq2}0mlVgE9MzSr&jrnx zwC~_6Dtz;g&KaU5M{PeIWD>cV&l?D68)c~1&Y19X#CCHb>ypdx(MRKtFoxEY+Va37 z$n>hGQ>GXF3Q5K8Ef!OaVz74hjB(&L#?a|uCVQqb?y;eM#oBzy)*IUx5XAJvb4j^f z?o$=M#A;7e(EWoK6$d9o^Chj3+ZgKg3xAmNsKgrc3yP_NM~tD?tJ@ga6{TRfo+FHG zr21vW!PPC@(Hm3!zeLBrJHiNMPTQ5DX@hlOd722|3kH8|V-U(=V7=uO+@~4^+h0l2 z;oZ>!$t#L#!0~9OJVTYj)2K>RcuzFrf;lQ^C%B?59M}SMB*jeR6A&oj#^gq_9~f8> z42&1-pE5O)Ej65P1v8KYGhi=VU=T2z6U-7LfqI`Ge>lG63rTPW`1Yu@JvvCy%NdPj z&lu_e;{-cTqJoV43MFYQaASfZ7?hHE1i1ZL5&N~gjs>chYM*E5BbxL=l8`O@kaWH~ znlj5|zpgX9%br*!%WfxwwV)ja)1?K2yBhd|Wi$wBXTOG>duHg92BuJc=~H(! z>?xQeOzSTDbv`h(*9;ii>j5j2B$Nsb?FF{LIOn^M8a)A1g#HDxgXE;hHv%>ZfOAK| zHOUqDXp-VdRHcz{C60klD!~kz>rSJB6jU!OX4(KF=wRHL5LsXs3S9l4m3Rw`uODzy zWFu05qhH&if)s(JgUL;o6bFq!c(7>iry4oIDeTtLGO8lj5V-#BQ^s4-=$+X1G{_ZN$O0$#yv}1SHdm)eArzhF_PVQ(;D1prQ ztbNkz0)Nr6ee22#!u@P_80SOHTn5qJmmPgCA(u8$(;7tfVmh*7WLh4pffmgbTN)vhM{Ac_lUSp6n!oZ2t}4jyw+uYYslJg z`~3F~^&jf&fxlCR^#*(UM>4UDrpnbKw*65@>-J&No>lN*^(7(WP@`?2HXH6cnujAH z8;&h8XOWv7%ShU^Pw;4OM^D_;ZX=F=`kJZ7%_d2$j+F`dRqQ!#9h8JZzPFRfT+X00 zLgNhiU6(D`bJ;w9X_2Wi5Huf2p$(*c8=*-_^5ft%jukM4zpRCU*gGu@H_v5s%aOQv z|FzHWD~N3r#c(rvjP))xQ6Wb1$s%>XdZ~ope~6xetEuPS0rAa%0B@ZYx#HmyM*akr zUu5F!GAdg#GQh9P&%mi0%$6Jz>$Z%KTyqkf9`u{8p&iO+<v=X+=K$Tvhq+_#J1oQCzIa6gNV zhzEB5vJ@@CR)Xd;NdM9X0_mYcD3yJhUoXYa)B$_t-vyGNM7(4ObZd0^R@a>v_8l;N z>-fmG@)$NgpH)zz?sKo?_3?jzVM(WXC@+c$a&|MP-?b{aH;w~+*F%RP-nG_C$)hx9 zQOvPA2nZ*c%w>8Cw6Q$CZXfAm?P#6fUClSgCW?PIv5mT_3NLEpm*!E!nF@anD&H1P2A!Va|VWI&t`|w6vp|||o z-CU35>J-50q9ZY;kYAjyGcBMREaFDBZJaY(GmI3WD_rIxOU%Yv7cTPaz`C?nN-4pOf!}iUB4(Dp$fCIpcNyN8t zBVT#t`AK5~>XjK%0;1P(QGcU98T?v!aHbN0Tt;m@ue{RY?kY%qQ zY$a#<)i$ub9x z1PpzC(Ie46Q-)cRfqDfQH6vSYb_0Ob)u|oAjMCl;vm;#z{);I=k(xHCN|Sw#1aMak z35XHWo3Bs{M6snf`el%ix2(D}y8*`WfcAhmUk=2fhigJqPA9oxQMK^)&OQS`M_>F> z9h>&07i~d&pw`p70Kmvph(XVknRE(ss+_Uv=mXg{%!IuEdZ35>woDJ}p-mT1^5u)0 zGuiIxYdmfNKa5vlLfpuxs!zvw+_B2ldn12YN`<9TDN6pQRJ!Qy44L3Jl$_+m0!aLN z3llE{pw4AkRgNYY&QZh0r;=Fp2XG=KCh?K;u)5LwMa}|UasKa``)12A^f_rDQmjuX zK|yR-C^`;^Z8?8{N`NCZ;~h}J{CbIB1u~zyGcboVn(fAsZ%x|%(hsFVRB~wDV+=9c zsNCgdyS>0PVcYOixH^YLk*d4;_K76CX8J`2&88vW*@=kk1%lhZ_H@W)qtD3njF({U z1;-eMsA%#=*K*M=?B)cpm;LLBvxI`#oy+!xP$370%WYtj=rT{B~tiP3z=<%9%b zL`7VRmwUvpIg1akH`R{OguqG~R65;U_0s#Q${3mGC75cl{>3Bs%o@Y{w9}3N|3IE5 zyoBRx^)SvPq0R|l^8WJhW-KY-kc&t(J8MK-!yDj$0>rmQ7knXXgghetU+&&^s5x#7 za-s3qT#4d)JCR+Nq7~4O7=vgae*)jXQQq`#V&W4l)P8$#9S29aL*f4G-{f_;xrU?@ zDsmZv{7yf#0@Uuz^4vTgJnpR5xj8IJUe0eJ4e-rfaJj*m?TZ`^C}mdx?7M7P?w#}} z*svU_Fa-{=u=u{*82q#U=O`U>TjZW$?qG6Jtn$u=;s?0n%xYUeMIQpd%RR=BV<&CEDISpIhC@ z1;Oz8-GM+#kGU^03P2InP;n~H$=|CUc6}n7_TDZje0iw9F4ZJtd1t8J2xIsOfw3i% z(~NDa*r-!Gt6rS1SeZS43=lB~@a)o)<)giq5Bf@8JkzdiQ$5`;LceDdh082h~xs!$f~Nh!4Mfe^Yq2viO78Q2w@pl6=gB4GnUxifCG5GQOn z%$Ao$-p@uUEO)Hl52KA+`5?D>^Tz0TJoj^G$4j>t9r+xN%_KifP==|P0%`DgP?r!tr|@V8wjP<^#R>^ zc+W;kG6~J0^bIvekL=mF)Z72u74j(p-G#?18;4ZdXZ?jn&aldH?GTmrK+qw5syNgT z1iHs#EgJtoOX{Od*ULrI$UXTjLIHhRsH9^g=t{?6P72_c>U2LEnZnqF=gXKp^^}UOIlJ9p!{lj-U545BAdTGvx2T)SqPFzS< z7e1TQ?z|A{x_IXESwR2@Vi}wes{EXVIR>iboBcq&md0_X!O3M@N9y~=9N`s@JR>1!mK;$z4r+Tlb)oBQY58Gc#{k{&;Gdw*J-%T7c!-QJ zozb022;?v+eNC})_9N698je~uo;f|;S0WVL=Qi`}ytbvS|x;VfbEORlxXM6?plK<1+d>o|u=oHOG}r9rYW`Lz_&os6@g`Y|!i< zPMB{W{+eEx9qB8F#bURzfd1+(Jj^Yz^H>5N3!azGXK!~3GUi2e9}Pu3H-b<7?u3Q5 zWlnP!>A}26vF&s9(h@Sd{=+9;p<9wvgL!YmMZc&A2zAxaDIl^x$j}cyLZ~h)da|!O zI;$}ZNPTwMhFl}=fTJS65Y1%_`A-!{#zTV~Oi@AU;Lkwg2q{mp?cNM`UE6&sdnPP3 zifs_D{}3f;kH6F zfP{Xg1jwC{?AMOl%Htal7dDlrH83ys#%*A@)&T9gyrTQ8849TElI#>S-nTE&&6ReF z3Y>FWGsT8Dx2}?9$(Ef(r5l!eW3K?`n7Sl8UZ7Kh_>#S_p>R9<87d5-`!x z%5JZD)sq!A>B%e+ESmQ<3Py~eH8d>pTZ;|$oMQRk4LTQlrLO^@GAGR551n?v2Lk4$ zDpmfXn%r2P%$~&`HR_6B&+ORK9Y3d;z#e@e8Shr9|39jG@3^M!FK#^QS5Z-EYZU<@ zS}iVC%M>J`S_jHdQ4x`$C=ppIdxW4?u_~Y-fKKIiUBt7VUQ22|?q<9B)e02t*j`>-$^5)6lV1Q`y$ zEky#a2`jOgeZ4>XkN$?2Rskef1S?nszf@!q+>K!oB;aBApYuZY<6E3oQ;jd^>~=6# zA1Zf00|@1bkTc5Lqkr1^#X;f^4nBJHWSEDz@w2hNoP*NwBlfNMnT{~!9cl$C?@JG? zU-Ld>x9=AbERW!(Q2=o|N4PZw*p_ad>bmV_G4=Aq!)%ON%@&8*H2{(xuWrWjXS+iO z%Vpl(XbiBmFvWW^&jqnFPMm_@T;u?3-IkJ!aX12Ba=5mlOVFJn;Rd&>K70bLl%9;K zQ9g`+8x0>!ndkAb{BG>-CgnWt7&kE)voBpN6^rk;o@VY59Fz0Lxc3U4^QM&rTAW%d z?)B+v0gGT;N~k5;VmVB~6J2Hm_PKx+oV9~bL5s34SR#=+`>eFis`9dvv1hbc$J9$k z<7qUz7a({+S~8}Xp#X?IF%^b~hTmHJ-oS5_YfaF7w=yjD*0EotvBwzJE$kgc->j4P zSr3b;3lCpm9u^LkJDR}^m4@C(ke~M^N|0Ibk-b|u`;+;tPxcjNXm1->ri8FmD^BN7 zwpFdO%f1=1GuoTgrJDk`?9>66d*ryOFfns<>Vk|NVDgcvX@Gt2HNd;b6kNs63RI?Q z9ecC~%X_Q8tyAzT%z0-R0bUWt><80`p}V6p;s zk^^JnG{Tn5w8C%G?{>mphVcC&hbYJZtfaoI1;qN|``d-5{%w`@)FABG-1Q@pC*&HQ zL|LCHo*WWf!RK0pA_??+;1X{N>xo79pOmvuJwyR%uf;A{M& zt$T6Pyvt>HN-Ja%pzf-}K6-6G-y&a7VjKo7U2}h}v$r8>+YL%sl+*&nE;dk2Pzv-{iaI=_+t70Z~J(SWGO4FR63_=0LYWpwRKzgr;WdJqA<} z+SdvZhm)c6`d*l5HYk}cWw|j%05-E`ITm+0PdGk}CKjnfrd(y`qL4@9$rmV6**9g527`CGU1`bpI6=5Ohf%6YW4-NrQ=L`7 zgoHX&rRV@UWiIA&S_+9@5Vs{_5i~?rn`ueSFIJ~L-^7>5*z8cbly`ogPyCp{XTMn1 zRIz6Y!XxFy1yUzKrwt4*u$>SCgU zRI`tL@4Lr0rBMaH(!6Ge-3Z{2qvFd)bugjM^lvVN|KfpH$Q5-NrdN5$jk3R1eqB>G zks&Mouq~6u+fA!m)m>B9+PNeCKO^7iTjJzWd11EA0_AUa1DFgB2((Mp7V{em1W{C> zF5c9fsg?s+{EH1wEVbNzuUo1h0Y_6dubtcHYffiLlx(Z3`44C3$h$kL3Bo8vj!YPn zWf_$tBolC`%UoLp9X{v8?kqu;2O@6~P9tAZDThdgQQ2-Aat9H@nzNWZo15BEetstQ zk@SBe621-(f1j`M|9SZB$THb3T^?@QPS;p-FEsvCo)c_#L;D_=F@t6L@Ru1ffMLcQn6vGV{p(nVLJ`P$guC+~JbRGUQ4WQQLM;?Im8g)76L3VRYv%Q>l zg9!dI!s(kMW~o#erTA#m1|%y6Qe{A5QHCSbW9FTlcp=)XKHceqL^frvi{sajHk%p0 zkz=fNr4S5+SE+9EAFzf+GH*F^b;aw;m2DLM%q-RJ`%I9({&YK*k0-5n=IrQ{gcI8D zKKofL8w~&Ht4LZY^~EHpW&~k@?!XJYosbqAXFOSRwsmu@X==U>YPF z2>F1A9|4;VceYPOsC7cH9d#sZu{7t%$<$q0sWYNc%8bxj_DG4ES+-ppLG%Bx6QcPI z|6!JeTtQ5hN6QFnPzZd)s(FlG6y{YlC_q%a56>J}oJ9#~v>^|-v}q@I%`ynL+cN!8P z%F_GJ5Nw}np*xrSMl;Lr?bEE3^3MVOqkI!6NSUnzTrGC;;cf@OVi!|+UpN86vPEQ# z`hp61v;rNaB`E(whvKe5U+T~uGH6w(hFKgWH@4d3x4Yu^%%=PfRzpK+$reWtKm9y< z8o7sRIV2b*cN_foDMpZ&ak>)|*~xEvuX^}F1tN_|sPCrD=g@NUD0L3BpKwc1^^0-j z!NE)f542C93Zc|82jsva7BL&iEjg!;lnOevBG-L?W)mAA?-`jo4HtlsXORst;qlI z*%GUTAQkmJgtmav0I1V9JSv+yk|#+T`|JSO)llhOe!9vi6<~7ByD`SHx*GqBt&Miv zs&TrE3@vf10`So;QKw!T3z=sxA@>ZF zwv6KM@9oaZHSN=>n{J-fnk^p08#P6ubGvaQ zdqHb9pUYkVoV1u!_^_~24iao|jOOrG|5niGA}XKu&e6taxbETE@7(~fR`^LYQ`XkJ z(*psOtRrfou|y;Z?HlP#^B_sXhCd5lB!p6zgQJVCZ56cT`UbS-k3P&sWmkiorvfz~ znx#Q&j)XH(Vf?+tJ~Yx8@5ZXjg0#eW8MtA%24YO%0U$N3^{{kVi}hgaa3)~+duV1G z>*UwsLYLNPgPiARXS>)BeR&Jy;U968|M3V8IaV7A9x--OryqMD#Mk&Hp}pgbus-6z zmqF@<7u0O*U}l$n^Lt9_9ofqWJRP+>91#t>Jf z|0t>09KeXGJsyof5)v>lR0CwT&r~>;A#h>Y>|#rOVzQ+d^8jUi5)?*!hY&Yerc{~# z_kV2kMMOQG88f>ah4tQv=m|Ry?toQ(-ZC;NLlKnr!^Jvv*I?!d?(P|>$j<3}ut@(y zR>EW6>A59_-pr`e|NhkX)BC%zj?0N%!~ToVR)}I8AfeSm4WweYfPkY^EEh|0VkMH# zWCf)KYH|&umRSD)4>HSOkkWgoK}fPJ63$dz^MB3_+YA&0e5b?1cQ+_Fy-brx3Y{8s zA*y|hgScXt#ZM@zyqjL*INGB<-KY%4{Wf@dHTQGVc?{NccN&1bbmQL@G~UzHWEapO zVYKk`-q~_zXw17IY+Y`{YLtI#Ca;@eFMGxGa`9x34PvElFFu_ey6)7&ouQ13wi^vo zK*jw7HpBMA%YaeFD!uB59Rhdr^ZNmiTI_dxLb7-cLSH3iy2Ha1VQGG_&~nQf8*=3Y z_B~HPyj|ER>uZbWQ&M?X_DpRo+gO;&+w^qgtpCNs1lAGzB4+OaPPfBT-2CcvPg15v9rB3Y?A z{|^c77a351P+5*ttcbW%4a1o-os$v1Vok8Q3IZwrF4%~(?av}t9grUIe@%z3% zw^y|DosCH@yC24udLtgIUdc3`lg!fSk4RP~1BL4zxC2d#G7H2{HWIwao8YYip~!O; zsys_pJmW1K!Th`f#!hEtan=9vyW>ZQo>nBAtIQ&S5^`L5?^|Q-O)VcgKOoIc5fInl zyW1xaW{R4Fl1i+KbnBL?tcH4pXL&p;{w~@^{3x+=6Al1$q)=#%hUNbS^fxp5vaf>v@Bqb^<|mo1M`@fhGY(SYRw|xyJ*>WC3_JZI!e}k_R zq=t_LF-p4La0Q16;|&lpANt^~pto1qmtW4B9;%Sx%hxF^J2r`5<~l{NF0rf zaM?P%M#un+O&aWd0EzW7*EUEaNaODMjmd%+fs!}ZvQGDUK@&j={x|-LGuaZgM7r*O zYq#P~+>=&A*wt3|zg1yGAz(X_#-|mye&JXF%~IskFMYaR#NI#xmT#ZIjtu;~@+NL2 z<^ZaVk@D$ygWQwpqGIaw2sPW0nwMKaAx-BF-xQPkx38}+OI%W^XB(H(8)j8EL?&X1 zNN)~w7wwO?4xiZ9e44`2C9nyW=sx5ZfpH0jo8p%T%-1I4GraKxLNsqwS+JeMv*)Yn z6z~cjE0-o>j$_S%r<_CyDA2xE@0R+x0RU&p2AgZtX-+FOI^7HwmE1qSC zWuG{;zs*!11!s9Ut!VV45fKwT(q5nn(m;I}Hf33K3@$s{Su`loH zry?~Rcc&n@_5d3f;}xHQDLw+6Q&o=#gGBsvYyDK$`0h8fOq@eQg?A%#g;EKPZa&W3tz+(iwL3rjDcz4&zcn_AFkq=HC4%)m6knEYPsUi=CQVOk< zKp0@tV|>yw`2YO9{`HV)x~0Wgv^3#4+l5}Yp*h{El;p|-oL+pW{N{%8W(GAsJ;RS2 zqx*U&7>%_&TY9H;YdqmbYL3}yH?|7ohk zP*RZ}@Q|uTi@pjXb;sLGL*Gb{ShddX3fO&VaE;dwe^ojrLF296fNoZ1!3UP7(3X;L z$D!0jOfjaqIe>ycf$v{m4wy8;2tU`#{-pe1@Bl&492m6lf0*UX0W*=W*kk-td4REQ zXbPCA0zx!CAHHrJ25ex#!Kn*AFCFkL>vp}7pen+xr%g@({JPs5omYJy0PS`apwwn1 zVjMi4rAkn<;c|{g3ox8vBzfg90P->zD6{& zUJDrX85o6qvIE{TP_ik2;t4afzdv{&9azEM7BIs=n4z2nY>jIxd(860DRk$ak1*#H zxKE5ae2bv~r1)H59Q$~{Gt!k|UR&FSG->?t;RHdqL^hg7##Y7PY1kfu4uQ#`J<*-$ zqu7>u(Q{iH7=AX>u|s@$fe#xCzCO;Xv`;zPio~009dz#O$mV8@G?Z|!`Ml3N*%EID z9As;lj!X(H=0`^`m*2#QLy#Xuy-7jJWNUgd|(MMbFe+k=R90NH-%p?usdWXOuBFr ztQqFPVEItC1DuS5kf?bInDm=O%w%rX)JrQr-cX=(0x+g|xmNg@i33DsSRZlNcg|tB zP3bOkbY~@ui_@v{?U*p&&Z1*-0rtJ)B<#C6tndgj4j!B;he{aP2nnIS3y+CAMI5qp z(wBYor2p!Z-2dsCSBd=O`z4^_KtXdQHeiRtrrLrr z!qi~VPEp;Q^!r5XBm|&X94`| z*~qxK?VA;smVK>5V=~4(qM)R(PDx~l#E*A=$i_`%|5+X6Y&C^r_$OuLh|1Z!i-6vv8XT#I1;5c& zbVAx1q7Ib@3jxg8_5r$PuTH>wq^3^GJV@vW0_OA*qRTFP;K-K^VYUd=5r$Gxpw5>Y zhs$wFeZalC0)8$IC~!!W6z&8}3};yd&tZ>J09(qSbIe>EW_=y9cE?CI{VWO6wmKwz zFRXVq&aO;DJPLD9L}?OMSbOHp6FpUA-&5|V0)^kV^3zQQ0h}gOnFEq??xnsLhA%5n z;i8&CVg2+Z67|q^?!4E6$T3P$6k3QMC)x(z`cToRXAYyIa5cVzc>*;fE7 zsToQgrJl%n4I|}yHBd!8=XOWK0j(#_?{m=>*4T}6Q#Jc^ZXvNAl4=Oog)ONSa^W>Fv$4*w{MKg{oY|WDKr=rv zv~qU04Uj?Z9ZMAPP+>Vn<%)om{4qZ;A3v-sif87^CChqRuTyJvmt1*%{o)7%$apAg zjgr|~oe9Togc%`nnaiMW|J)Ozpoy_x~$>B=0E;Q&`1Yd zbItpO(Hb<&7$14DBgry zxc`aZj_EFsUTULxr$7RkMoZBAG%k6Kq6HY*%WMej5DZgq1HccpzLDMwpk3LbY)ZI$ z`ewo|+Vk}lT>+(NT;*J8Kk=2Ek1d^Ey=ActNxih8k&9X&Hh48{h-3ZSZ;jIvSRqDf*Y({FD-zxHf;)?oKxsE6b>xk&i z!c4bG_TTEpA6mynvBc6w+$mjmq)wBzkv17b0iS)*;re};Gi}O@{#_W4Lv=I=7^?f) zEY`0@`^tEz&0&bDA1#Q1zg=E0p3TJT{*MLyS!WXOLni2?EPf4XpL4U&-pkkr^_a>-krx}dN*O#}#0 zkzgu>rNLs`r$YfscJ0CvJ+sm}sXP3zbu7LVDNQMRTLMwMxR~;2#|9w8o?3kKDZ&h$ z6%;N+7akH9-5hEX&d;iDLMm~W*oG~LvqHM6PJUj{87zMi=vvTUZXHC?45lL!$lg{E z0-xMEpV(3foF*{rSw`XN0ci=_J=@Z2EE{@n6- zBA42|9ff-IN;bbEYIw;bluGh~Rg-T`ss(=L&I?iJ1qM-uBB&Ltn%h*%H1_p%2=TSV zRm>M{f*JzVkd|E*+A~Qdgz~xNpfcJsbHkHS?r4^f8sHyy6){ z*1D&TXfKZ_eJEWJ_2+P#Zo#EneO`SuSp+4c;uf9RyOJb7d!bbPB1V`TL8Kziff9!K zyx#wrQ~;Xq2kwQ6(V~SK6xNO(=oyZY{5qE`fb@TyToKD6os3;SJlj)yeFXyf&sC7C z6kAxaJxQvU$|L!|12}=owgp4*FIF<8sISRPLla?(UtciK;itB4UaNCp|JjJ$A`nFn zFP~H?C*Xy!=(kivi2%rs%;!PMS6QV~+kgCt zjkayKxNhf;EPJHf{3B8;oeEgBEc$IEK~jf`17vybxZcek*~X~zxaHu*h(usgm1G3r z;4w8TRZpUg2x+zfM2#{V|D1JwqO}R~l7@>`=`9GF z-3V&0sALOeds^$!4FJ@n#ZLMOiNz0Z&!cE%ajl$ODl-~=VaE|<{Tf8ct>uYi$g(vP zd)^MPAx9;@;2~{l?~}12GPo_7gFFPE2zL`B%%*Z(-XkEFQj88D2a z`2KZ$_VFa`*px6M1e^Si$O@5HR}%exfh=3)!WwOKfX)y3?zX3-=n`mzMuPe`3!(lB zQZnetL@<4N9SZ_6=*sW!iW$qUX#sEM`l@d^Ot26qeTIX8oX7XN(4YfV1gLtOg?(NN z-8B+{Y76yItyOE2j|C!lI7k3WO~~J%KNw5Uh7$CJSv1<=m`4}@LPR}RoYPDOSLZE+ zX>f&^f6gLlF8;l7B{)1}U4vwK%up}jWl3{9 zeYaNNfJ}f5Cyb5Er^A;FLV|l5IJ>~687rp+L!NfQ6R>Wcr*{WEKxnUsH?XO*&!Yq* zNXhI9Bs@k(nYl-G@OZ;NmM>>ojw%i@q1Ks^blCacMKe*LLe*!l z4qBs>(}ozmjAEqPlJ0|<3&K?QDlji(i>(guDI zW})i7rQP-L@mZC1B4#z;>oSAl6dZ!;#x2?qZvbz@8LlnUnKS=L!6H><@YXY`vFXs% zrZiX;Rd#aGJbHm8(>k~ztWQUF9j~Y8y8Abh4ooCfcN&Uh=tR@^^k>2{L;vvwqiVeo z>7*mAv$?ObKoSZ?k;hzfC)YLOp4k}d%7G$*8e=%_!gAJ0=EuKnQe{`3uE?ecPM=>o zFNQ9`7D#@D{tTf!)j?ip!P9AY!K?YS+lEq_hE$ZI!WDF|KKA^A%MDb%23QY(TjzZK zejFtX`au-Eyd@%8%@HyhzO-sug)+1} zzmW@2cH%-}J$tqsT!a3F+~$jas{^K$hqY#Vw_ShsMwB=JRX8nIG_FX9CN#J9`X9HJ zS~#kY3PoWRe)$Hl6owImb3Ls?d?beiFIJvgm!Dh52MBc@(hlC05CYFfa=131;bX?! z6u0C31w0>cU_BS#7g49Q3mNo1M1-M&YUmPb0X5$T>rkJF^R_`RUsZ?bJffl{tXEvP z^+xaEJ{f@r|2eKQ!Rc>RgeEsX1|j~6iiJ{)u8EMkKYllvN!GU)OqYK66(V$?l<)lO z`PRlYIDe$S3B2H18DM)~wL8`O4ObqxE###OIqz3It}+Xof2@Z?6qLONIQP(h2{GDv zlq#HQK4#AyvLWiWuVIhbs`o_bARG`N z4g#?FQK%Q=sXARQCufU)#%L}nOM!uHsXRajLOXzbfIvu~`hZEJ1Uf4!aT90M1 ze*3KY_&g4iz3p&N&sL{R&?e;OGO@AdANlC|hvi}rClgO;b z`&jz1dWU@cH0f_)H?6p|LNtyA!W%D(Xf>i_GZ z@)d`D@XTQ@bjY}S5iT6x`;ndT89`4`2ypeTj$7&}pRrp-aF>rrIpg%|3vCd#*D<#~1^c%giak z>P%*zXa`dY(H_}U-g zFDn?EDU2=8qz$O+#xQ1Gbpw1$49qugJq*{q2T2U%0oaX^y9@~f0M;>*a6Kk0haz@) zD3WLgZDDdR4;?V{!H0{(@3Ao0X#kZ;&|!vS2|#uOJkSQ9@qMQ71Q$CP%z2o&<+fO$ zQg;DVtqFJYHh~^D7H}8i&GDGY9{7|3?Lua5!B>4wz*yxpVXSiJVGj8cFjhslQ{G`1 zt06Q}X_;P4Jp~;|91Gy;yi4$D;JJ8y*-U8+?+X++3~Q#d8iv1c3x`e7c`vJ#unv)M zYe(LNK^XhT#(ctxH|J2{)uNj z-@GKL6==T`5*(y7>{)Fs;ya*jFo_}W>95BMcKP}(^*00Ka{j~-X`^uotG{LV=)cBP zxtoo4ta~Qxk%eGOFYKWG6p_$ELn4{G-%6p7T=jH*cZepP82O&Q!~YLqE6Rws@S=~E z@91+~yh=9Q(@+$m*{G8)P%y~7?Y9Xbw;@>5r%`aGo$>CgiLp>=y%0nE4{0pXEXD(d zIT!{J_eW?LN?kb9IT)QgBsRL>hIg#4aI;8N;gl;fw_l+E!xvqWWz3I1JWt54h8u195{2$IZCJFo_We1yrKv9lTouIpcRrtFRf)f)*bU=~oB_TK_!f!hE)o zL?}c^96{i!KpopczW=Pz2)ZSB-}G@Ley~0z;reOc-fR{w4RKgt0*mh}^yMz#5t^XJm=@RTU)X;`GUu*TNX5R`()YyV&Q!x>KIQ(KqH5 zGYa9-ykNdf)RM)O=mud2uzV*W7~pqFwR+L!MoE@>9p4ck*+AZCTVT()xZ)ZSP@~0ErHyg-iGLYEhF1;SnnC z!)3fF*A$cuFLabp2xu^mam0VYWY(?Fg!9vZq;2vTq@tsNx>hWQAyw))4J!o%zmNs` zE2KZ5AV82&(l=P0Ei4FF{1gl#ZTtd91bbISDq!be8HE6(CP0wEp$P<#mS7W9O8w$KB%LXo?+zE5nV z!CVumszAwC6~L>mi5-^SUvaS2@(WBb71?Qo<{U6Qhe%f6_(p($yi6)3o5DNIXE_2U z^&`&T?A5$9Y|0n5y9gNmhJZqC?g%jLC&P>am%N~ zi2{*Shd7W;F_DBIb10$)D&C^zkk%8!2$wCO_YL#`kofSc%B}@*9)hgXj&sD`y5Ss& zjkbT!cQv)%1U1a1+V7$LDfe?Z!YV@`JwCI zu516hKI^|ZXeVG<*4}r>{s5^Ik4ueFt$^yJVz!_KtA6CCU19G08X`ma$Lyr2-U4y% zbueU_w+&zF8j?*>Ez4IwxA0Z zOD|z6U4Tw~k@L~B_n&b{t|czgh*CfV9cmWq5Vi)2o6Dl1b=`~I+udCb&ziXxLu3w* zc)B2kZ`xcaxCx1ZIgCa{+!FIPeQb{#CvdGZyPC`F9wihh0va^9z^pd7i{ys52S)42 zzAAMpYZp$~4k{ecBYf8N?zN_sQd@pq*!A5=DTcsbrs5krAElQbTRLkuqb z0@M8&?Qx~t#1fCAbFfKlrBPncGDkE=U)W`2&ekPfq~n}xvF%QfpGPbyig$+`+~v|! zNA7@3&5MP07VjnyDmN>DDnt@g6c>$_Hmc)PjXivq9D;NhnTz}{M+5#Pu$HOFOz@LJ zaF`dRNMD;Nvk6f#`=JFY+gVFw_bSAw-_p+WJzEeFsDfr+e{)@6f?mRU_AQHqAWO(> zmdHgaJcOe^M&*v$Gsz)HFV8wuS^t8pRpStbCc>PQ$VDV2>T<+_@`%3xYP$w&oCZia z6Rkr3s-QA3Ka;XyZOA6$M9oL=m5wa2HX+LytOE*lOb3!2spO}S@c?|?I%h#7KA)Ru z!`kDIDC)d#jWhVx8w;6`nuvZqn8kJJy@~eSR<|_7D0nWDSrZTJ!`xeBI)5OrwOOM5 z_|;*gvH=pas29bG3_$=_wLedybPr7K%EIJg*Ms=YUMmF4#4OcZy&&ELD+wPz(2pNM zni>m-wmjUhYxpyFWoNL~Ex_^p+0pH9;cDJr*$Y z!OT|F#zm;iwSoB9rBFX+<|<6#mTg~<>})zTKW*0bo&gU5ED#DcIK@q6)LA$Y2`R>GbiKB%8&GAxz zi0Q<`h%=>?vETNpTPpn=@2fYPQgJ>HZE&^#!h?*M|&e-Qy>RX?j_bvTf zZ;S=dt_qvE0`nbU4>>4_Yh<54cNyU%gRa=QkP2GkObwug65h7Msq0>&w&+070oj{D zGH(xPv-xlTXMKLraSDgTt2yAWff8ZRc?yA6P~GcEP5r`R&Svw@2trT?!rAx<^&)&> zJE8;xpBcW3z6EUwx76C>N9SLUZ@rqc;U}A49ET-R<-_?~QZ?c?_qVzw;FRc51-|6V zb_e3C9cUTdG0xXg zhEn`ey%70bsaKxV?}#~nt9O`F{vbQ@V`X|larjc3s|tGigg?VqTU^EuXK`9_PVD51 ztG|0f`QNdJ4;gpHm%dpEHwT}@ilkjc?3gJq-e7`yp)VZAza1ePVsuLeh&*87#kOVlyCPc=Fum?aR-uHMX2dXszwpM&w`l4cv1S zyDOKzE+`^Sz$q*Fa0-=Jzy0+F!;|cPLXKs;FiCi#D@o8QdhR=zo~|grKU7-$hhX7P zkv4y*KKSxXt}%gk?8DDnG}NDwJ`2C;AINCw<{UC^muq!1{tZA(6uUbqmGHQOGRi9n zmV_A`U6?`3ORCf6p1TDTv$;gHy1sGo{00k{#-@h(X*|+#}W!8u^Q9t{xiy^RpmAkR{ z5lQLztwmDpKCL_M@A|ZM;r3I*0xm9#^K}Qw_26&I?;Q+$?X0G6cQEAV7gllwJ@1EY z$DYfOPC^SOlz-FpF>@+h3R}8e-za)_{8E4UU5a+XgL<(^*2lrheOq|nhX6T+`2CuV zXZ<~eKP^qn`f`b&(ZFC`QaX(P#lrZ}0Zyg6idOl~cB79PMSGSAg(11SaVULyrMu9Lqk>7>?TJ`Ihj+@Z(*=Z@dv~S|#YFClOA(CfdD8TnJ;48sA79I9t z&7!@yHqo-D>VTxhqmKWPu2E!#YjsvPz^UlBBCM0lOan?La$e#thRU|Ok^AFE;Z$!l zd8xBV;=C`@VvaIhI)_;!R>zErU31Qgt2~fCMuXAam*1s_-8Yr1d`Z*QN15}dP%_R? z;BxdH|8X%#WfK9u=o0tLV$nTDS^2tvuM<@NK_N%NFwDhvi4Rf(bkKx_zCxxZCyEy!^io+*ULyrTdrf zY>du>q_XY8wJf|cWK@4Hy-}K=|R6p1SV=DV) z9k&XX?K6xB|6bO|YFKfNrH1tzTm|rEJ1K2w@Nt74?O|J6Z>FX%T#5Teaz4L$^}iC& zl6fmRS{NI?y2E5%i0y`&a4|>a_<>d*N3#hNnglb0<@<#i)%C*~nOD$Cc~^YTU|@qj zsMqHFiS(u3a{^&~g4{ma#I@wS5`WTdoHiQfjM9*Xai|WM$J#ZhHE!4Sp1ksFKF!bP zMrEz0yt1Hw>ti<}y3^hMTH(Lmb;l$G_BST2Mva5o1-EdHU>s z)*Ej}0My>TNM|FG_dvpvQbE(q-}nC2uH#Y8KOUy$8~-W#qy6t~8u!o`-~7)%*)qtt zrzs{wS1 z(j0-04b?c@K&cEylP!%yG>)k24~PruR(`WXbM^>Pe1)&-EBfb7TkPEd1<-@SKa&}V7+<)ivw(!DF9t*1No1UE0mvd%B^klZtyscRBPi)1~ zML%;|RCu8t4a5S37*R3b^wymInz!7-3;T71cJQ_FzJmXKGi7roZB7x0o{Ucr8iL8c zcZ8w78Kb$wF=tDK7rtN!)$LQvIJ~DuWZM_7snD1RFZ{oG5kKB#4lk=}PLZzC!H9)l6w~wIhcm zh&|Q&#vI0f@~^3p=1g>6s?2#8YsOr1rsb9T^d~25{?eMPvw^9>8+z0!lBQ?Hg@@+j zp(h<*hSTuXyCzc;TqBmq`=7a`u2Ce__IMay;HmosZlCJU(zyM#Zws?-}dY|yXPc3%vpOUw$kdo-)Sj56HWDI5TpI^O)ZnShROUW z1Gbb~v#OB+!by~@6%%z@uO zph9G}`fsc!8Zrx|oo>}3%Vcu(%J|UMN}FY@vo{azDl3+!wB+bVG&iJ}MFg$iw8whw zw-fyPK-KF><*%OuYZ~S4{2IEIFZ;*$+B?*3!RR%_Nk{T_GcWpMPGNDiV%Fo*RFz6{ zAu8GSv9yjuP!RghU2-WJ^3~FxgwBt*GM=678>>1Vf9UqlFLDd)9rVA@b}iqsB{k_a z(NJdDY)i`_4g#2tBFQu;G_(X&B9830G>Z&IaypT^J$6Br4ms?Sf>)ZL0 zX4SR*9M_dy5^rkxWEccS^FU(7I%0;ie0nn6?2&m~#fr%I0dk()p(TlD-&Qo2Cce`D z`0XfYL{(V5Yal+J-Y)#hdWD(!jVV{{-o@fQ^;jU!Ic+%cm}kd=(miMU_XVw z`iH*Km#(r)h?f85mtSPXkk4L#@I3x&h5Nxj|GZQnyvX&B5Va?t%F^!LyQcsVGx&!} zSDqaDNxEgjk3T-{6^`Z8a>M!~`?o=Qyub)O55g zdWcotN3gi;Dr*@!Y#=d^mb{{&NM~dA$-TiA{8qcevIestBS}f&#&0i{8DB6M=^fW1zoIz&zzs!6=UV2 z|DD<4HmTRPI!R+&mTmcwwQ$bm3#Zn=Wk2PFN>vDSI5yaC%jw~ zte#_gJ?}J!YISL~r&+fRsHDN-o9qH9xrA3;xdOfZP3uf_$qj;V1AghP^Ccsqf$T}kpVu;^^$znL%8h65W z{S>Pop}zId?ZNUD?>rxoKHp1R6HcL+`dCQ2rl;Gt{G7P%pI~f$p+z~1ndMNIy@{t(%fpG z<%}gI|MiW{56J7JNLUL}OWugsT}HAT`O9=o0p7(zve!``7Zh}QsxW5CyUo3)Iw<)1 zPO3*}er%z`L$8;YDwAEKYm1%s45eDxPf=LoyNN{~S-a{C{~hxUOnW@(a*S$biR*Qn zruFia@-EDFI&hAq579l-W;FBdd=({5(n#tEGBkhkjOzM&a#ZUKHA;CYgOm9rP0q%F zXW5-6qUDav7}m3g{@87J+tts@U)Q*8)4yU}%NWW#WX}^nxh82|vV{h8rA>todd2Mj zUBTYN2~Vg$Z?-c#=rr5A#kH#R51kmfqizHlPGd`eZ^!yDbX;+8VE1X-j04Y6dRy;^ zV@&nO>6$Br|I1H^Y9ndZS#(RMIaO;l*=W?%kcrO+Dlg6!xbUQ|#ufVZzO8$kwx@cX ziMHfBR%{%RT1>jV%HeRMS9wjgfo|{2Uuo4JwU^PaJ|?Hc`6~X^r)g5qxr|7gWgf3i znbG{cn{1zg%o+8~w2c1az-N7xiKXu_uiLC~l&rv4tY^ZyUdsE^*a7o!Kwy_fB%X+7&;~Ku+P}cBSZhu$gxa@xY|Zr+aR0M zW@%ZBZi6;j#rST9@iV=UoIGr=n_7_R@yLoJEUr5FvKxo#D7#x(^PI*tr8m^Q7($?n zDW>$Mi>?tV`j6`K;=MfL7;8WSNBPh=BC|tB{?||@^jEy;78-2Wt@XO9Q*|G?xBCPw z-nDahq~>==;>8-7TIC?SEBv^p&E_SC0(LPx`ZqnTbL+)3ZR=(vIhw!k3%=R?J8NH< zffqrwdbInZ&=7w!g&j$IyDh4oU5K58in3xqkE~xg`A5jBl&z{y8rGjP`R&*>|Ki4# z;r6JKBhCJ`fzlg}sp(f5zP!2&r*<5jsjtl+F5FdlvC}40G3<@T7Tkz)w`VB4HTemg zoAImB6VuIgOldL8p@J*O>@ zx4Ys``uz&V28DfeH1ou2d459PG;g@SC2r`A`t_^y^y{O2q`S#Pszp5d z!63b^6SU;uL}<5mcj3C2+lLHyCXmp%OqE?XNA77TQ~COp&OuKpGTIwM$!nvM?Wp@q zISOfRms&6v1Xm)X_ri{71)wyZ+ghjSkCgT zO>&}KpS!m9X-C}dXsXc1SYyLgq}9lMrqW_l75ZNI+$DlDfi)9_gMqiZ+(R+;9}5G9 zq)SVuy${)3_-ueZlj~TMcY)7y+?+nlQ;mpwJwuuCVvn!(?W}n^lUqo$+dY)58M~jV zp)EP()|)Q2M@EsR#A9uoBBp?{zytWvDgTgg8?? z@l6zyV3Rxtw~qDeTY`2nv+TNUGLP>bQVe4Bdp21~QabxLHPMyBcM;?U>Dx=)j}oP{ zlmg}Ls%JF+f10c=)DX8puyKG@^^B;aT5Pa|UNZ79h4tL;nCrRv%k`HdhY~fM?^dm7 z66bgrZgN#lD#ps_DJQ9|xgWnjlQiLv$B&UR9S<(MbVZQ5UV7tAH&Z%&w_&arfY7-|)=RKwkATWlg1t-hd%;7>ziroSHM<8aNetD(Lb~7QIQviG^#d z>)kR%j`TJR%zNEZ`ceH^OujF#oa_`;q)+#V(^kex*o4|&ereDmcx+p4_jNkAKuLbY zqqi_-f_|}OQf3vpoU2=l-~4Na>~DRigUzZM?ep_G0;igT{^)L5`>f-%_AOOGR81IL zT?>;|J5m*=|sdOKgqrF#0jg?Epz3UAtk%D z3hLa>7b~oZS@II+|M#6YK7=W^vS6=R<6Yg>8+g^>-sG=MR_M)^wz;bO(a zrjjP>$Y5x!Gq%AD1~bnY>b`w{PyMdnv;CjzdY-xZT%9vMpL5Q8Iq&m&zs~v0-SbAU zO&g>(@bU3&I&E;mgpY5X0Uw{hxUdlT=1-B6a^OFHUlZ6dzJkv(BcLMabky)DA72q_ zG-`1VHi~qFApObuig#)Kg9K9H1H@Y-IQW%0mg2uT;$eQZj6q$*R zyJM+Oj=;qZ7pWH2_B^>uyi=>|-Cni6B~pG8Eo7jI4Xh=)tRs9;P_YYsuX zMXzV@iEohDz|7yb2;oV>`-B|R=ZOhEqnr5mu9OPZPw|gf;Mr!iheZDKAIq6<6kQZP z!)DU!f(9O0{FgtA)OOTdaO4*-xojV^HWO3D>g;8b?A8D4T@F#O{vS7a;TZM3V`UUM z-1KMd$Sacn`ERuMj*R>iAOGG1^shB*%DR+;dff)nzv6U)7Rx-h$#7d61}H~D$8OXp zmg!udd*6O#P6IVan4GZLJk&I&0S#KLd-Ydiy+E@%CA8Q%c3HYNp7*{t>9$3nx!%Fj zMn70Q@^dnT7&cir7(u`5MHV*T=iPc4C2Ww9yGg`=)K^DXM-YY7DnddpvmC7GP5EPj znaiye!UQwFv@KqLrEyjHZS4+y8I%yAz%4DHbc3wJV*8qib=GT|8KxauQRuD5Hlx}@ zapqeRMIqBo6qp^Sl`%C{%dkN~jH;Az={dydy4swrC`9|B^LqIGp`baO-XAD<5Z)WH zgo{@s%f@XzO4;*Xx;<6^FRbjgoidl>Bz@(oSg_jLx(D?R z&O({!-jWTbx#zJ1j8=gb2H~M={i5?Z{2{F=EDp=ii8|^6PkCK%7~eTWxcVOg<%RoA z-TA^~%TZgiAwHqJpnLv-Nw;{kNV7%iJe?5mp&WhU!_bAq`Hub8JP; z8YZy_J0;`q9u;yze}iq~27}!g>5T@t=-v0dL$BlH;@Tr>nr9StEeIPlga^z7y}Tgm zVtOtGQafUNsoFqa>SG*MYHS*DSOCmA@aa$xFd23=*Vlr#I_b(k*=DB}@ixIlc0@s5 z){X>SI(aS$vW1w9b=J2&62R`>{9mQe`7Pg-exv_gQ)bMhkItb+={U98_X7g&_f9Kh zbqBcitrciyz~RoFDiVLn^Cm7|xb6=5+Xay~E-zs*buZQip4?+)YqxPfEW_D~>wbh! zCWQrzdQGO!svG9L^ooBZ?3@Qr}QItCe8q)T=A5>2Cg z-AS7g?>a!>1&i7(iy~(mTjU_d2kg;9U(0-;pW%5*2v*w81cu<2>3~NVhx&yx7iMCn zI1OASih_E}^>TRsb))Q;353^%^W31uZzbBPdJh|VCsm{2>QHWM0^N0np(UDbtl3!9 zQ``}v02MWb7`GaQa3x4FX$}hxk{C@H-;fW<6bYabeI>!nH`V0maBSD>s?mDxRrJiR zsIaqZGvVUWwd!y?X4ba2!Qw8RvHgF&U(hjKlXr=KWM3jFCd5nFz~T7i~ z^>(!hvtQ%GaGxApNUSs&yM&oA6e)Pz$MSZ{fO9q1W(~8Rnp}FWk&WAzy`N|I@Uh+U zYgx8PewU)YnFlqdb*BdVgj$;-Nj}Hj{pUOC+d73vSF!G0(26i0^jPb8v}X5Rz8uZ5 zYmH5j%x}CS|@jgRqxLu8z*` zCY>iaUK2Z<@0wP}m2P?5rky)~nAK^9LM-|}hT0!vhNej)dj|Zn^$1_fa(JmzvQh%sqa=`p! zAC58WRi6&LpFT3pi%%VwzzRTN%EZZ~bUf}3jz8nO#@)uS=obdR$1RHUOJ*|CIN(%3Q| zo27f$IHko?NXntXbQ&IcYbc>G@76aCA**|;BC$m(#CA&${vpw0s{Gt%a-XlwFv8o} zN6U1O8NKk5BbU23ec?o7$C7c6=Ui7ditXTaWL{=>y76?8qPo3xnlZDpJxiA!(Bk%z z;D;6YQx~%>xg}SQ(B$ygb1wN}nJo(*HT9}!*3bZcz?&S5OwC@Z-eph9=vz$EU1BA( zmokZ_Kw+1C*BjhGkV5pWDRXk2>aA*Ow9;GARO#IP17j~oN^U=FQR~@?^3tgK5Hoz7 ziYn>S@9CKA!g(}L?;j6&5YuyI5svl3dgdmD+WBummHErbNrilhnYVM%!LSw}J*7d> z1Bep5nj-2-WcjR_?qCx=QCY8H{z$TCqT}T>mWwe?PI3C$A}5)6WURb|h(BHDK|^_e zbZf&A8S|DoO0b8h%Eo@2?IPkqX>@iEsb5DLS>V!ePQ5tsNz*h`CDc8N$e`EK>pdT; z*msWSa=y>3RflNg5 z!v>$6qEE(qhWvelJus%tbl&>wet>^(;Y9Fe11I;OO#`Qfn_j5BHFAHE{wibfOGjj9 z=|XC06EfK~;=1Z7=|^OB0b@@QOcHC+WU%oTW5EkgsdvcJO>~jQWP55T#Y>YLTx4_y z_e!&*iEb=x?$8%C$W}@(bfS6}#CHk$kv=FS?9%XNk>{QZ@hI<6wT-$Mm*iRPG4Ixk6Sucvmd<|! zI)O}d*@;cdkHF14C-!?-dF-=K8T3(ZTKD@P51AD*cyYJ^64!q7v1Dz`%b1u7Q?)m8 z9(i7|eL`J*kt6j8qwe>1q{m_N8ujY?%De>AUK8Vo)P2!2D%4rMaxrK;(ZQV>1gC64 zxxDbGg?oQ}Q7vbF5e5G!#4Uo`wtwoF(+l0qT#$k^!gaeB7m`CiG3PZkQS~q;t|j3{ zc?K-E4{Q4NX%Q*xs+DVdHni-Jm3DAJ-I7XTnX6S2E_4nmneN3LiA8meNl&CR2qZNx zg8jqR*}6r?&>qC2>6c^vaw(tZsJSYMNk$)MyIY%r(#`GromXW&Cae7ok*XZS!o4$>G!Hm% z5Qfk}_G0Q(=&sBIYZRwdPA_0>AU0!5hvD{(?A9T*obtSL)k?WTJyTM#ib^eMWSxO2 z9p9cd18g)AkIBLKzV-F8z4q;}(Y7t9k^;tSC#j83rOQqy^E~BJz0~y8#S-Uq z(YAp$w<7^5C(}q58yB9iG&VmX;+d)*Rkl25p+uM_W*qBsY6x-5VlB8L?a!5mVxzHL zvmH|{YFiOFpoTYDzr`K1Aa0g>J&;hNs7Sqmd9;;{2wpIqa@eZzhykhv@0em%Eex9i@w$82vaT2w(VA9%$Iylf!9wUeH20^+b_-@;dGpd zfvX*UGQ}<$va(ZS=+H4IA5R5Gd$@bGp8LGOAnoeLEx1Al2kBpZ7JQQ2+CSL|Sr5AW zJ;f0ve44k|R<-Nltm&tgBJwAs=iY`hk5dESR!iO&C)(IEgVji~{p=ui_t4m!TQhW` zVMvd~al^%;=QZj6GI&gr9~}k1+Sy1JDfv9HIkLqKp1*6*Hb1L8ba!K7=}-%&Glq!w zv6GFpY6$dA%htgp-6QDHRdrfR-Hj~4YIbcR2WvQOaL%dIzs0Z;Gc|P=ZeuUB+a9G- zoW86*S)e^L0E3sNn(q7__FqH=S+48&%G<0Tm^=XdoJgQq6?DAdFlwcgnJ z{+atR)gID8Fllx}Q%88HAF@_VI)tDu&x>GP?^7oivp%O-0rdspneZ(i=S@RAYb!nw zjwo*^b|vO(Vy=e8-rj4f_BOFhI)(FY@$po0Oo5)v7U@~j#tO`!gJ*~?*}hWYZi7Bq z=Sgld%e0nMC}lcS*YBol60#I4$Hfhq>!QA@*7(sGl;a8blM+1FJthUKcX} zrtb?+^^kc_#L_ig4E!FsJzIT#E~N^u898yoF+Fe(J$a(-F2P))%<3AzPxjC6&boN8 z+g=^=h}N5}U^&VFKkPUNGM|0A^0IPf)@e5b4;%`GPtJKgN+itn2K8$)1DN61Z3)$L zbG>tlgmoyq26XHj1KVz{>Yc(KaC~EBYailXM|IU{uW-Vv;R;^331-$#VrASm8!v1$ zAU*GTOc4pVoSo;QsdOyWc(ypQCWSdsUD>?a0~&TFu~&><5amQi^Npp6J}fUXk2Pg4DXM zU(G4(&A1|_)Bd*Z)NfXzYg4v6d)qQ+JI1ap3b}|~lhcjuo8d7N{tj0hv%<0SfsCMQ zf-lN{G+?|q=*7tj7uQPJDrKJCmVfj8zR>7~tE~})!M#B!&n@+yB$2IdB`3dFx65){ zsZl8UF!Uw;9@8}dG<5_1ABp$F&bLkSAeVabov8@;guSjJ z4A3n|uy_rG%PQ}e*~H5aeh36umVf`s%f3C+mUyt5ZB6%4qOla8(8;;` z0~FJ$)znW){^LMIBN|@KdefUg30}Ou*-&bu!KVM@tNZ3DLuoH|9mMFRF%%4=Zg}{2=k*4RdaE5L zd<)B^rx5#Y)T|lsa9}sitEzPu&1*pMG}0eLr&D{DMoE#R#tnb3UE!7Z3UA#F#|j^U zNXCnGAL^I3rD)v5GNh9nTFyXkVzY3u8|i_X-s9{6H)=z~ zqq5~bwx?5Dhs2OEx1ZM1udRLU@!dXrUy85kYiS>P;(xFE)Z+?2{Z5^m#jM#AK~}m2 z8I0cIED>uPNT7m_?8YNDX|+v@F6(vOTBIV&O$Ep&zPkk9@Yf)D>y7*O7Yj7+(AU8s z4#$edVwB|-j}z}+5DlbDG2474g_26hiD)K6Ml0E#nmwy41(R&B6NbK|+EZKd^-7g5d6pkkJ@CiMq~PO|`Ta*JfT_TIki zuxojj-{HQqujUwG1BJo`8}2BrSAxTki1{P>F{$BJru`DlVpDC$!(|$hum1vwrWYLq z1Gxn5IP$Rhp{sTT9s7&7c9AglO7;6OxT})&u`#qI#qTpLR4~&`(pI3EWEbKw$);nZ zBa6@qJ=E`Nd=JyFh-}&^kD$h=3why}SiMgIdk%{@5KiIBsJaLLOB+1mRM;fEd&|UF z5NhK}43mcd7A7)W+*#rK&5OJa1x%g-u4dV`Q(NK+u7Fvs_M=N<(B*ddwVmHhAg}j- zwE;XwpjmHjyf^sphi%o|OA8((X!W5vV@wrv&KUV6z?OSlU#szKME1C*ng;8^&_}%K z0pAoABBH;e_W>pmIa-(q4I%b+6v2dQH6MG0MLG_S%u(q((#X04T=j=P#GvcSS8?7- zbYdv|-n7m>1&O!<`=0Bb+m|^ycgULN=!DelD&1;~o{xs?k9erf?;^rK68T$PK}5Kp z?+=ObC4!mAxuw^nnqHf1*F=f!0~tjvlTh`!_Q_@wn>*XG35uA5t!^Lb$=NK$iT&f; z^s32V2KPa*2lMeh3?gunNNch3aW~huj{KYO(wo?ulRIGc{EEJ6)iv?UkN7Rk0*Oxc z`}Xf6Zj#HfwT~`35C8ZS}q&a5Tb67$3Yu>X+m#=P^jKUeC$aP!zzVk2sM_^Uiqt>^RNkT}=MO&c^Cf_TzVT<); zO_3vX{^9GE2 zoiUCDFFqgzM~aD18)(l5F;e4>9}XTYYLM+Fvt+eVKMEEfYp;jP+i*Vju$;-}st$98p(>{0>+S(-{WG}IXt>js4DVEir@6Pm|w}dKtSH%G0LIRYjYp=b%i#P zRcb3UiWZb2UL_rMIQv+gW4S+O!D|yuO4an{+i!;xTD&5^93Sx8C0BdE)UNiM_vUOp zPA68cokg}FUinCM{XAsEKhjTE}|2R?tVtUQ*O>sU1Tiw)E|B&QR${P%g+lrqFrL- zjw_^|ZN?ccEXC^8E1$c6c&vKxHr!EPL#oX$Ipb0WL(;~LX8pF>WC549eS3WBA8|$- zcN2!D<4O7RI@)V96^Z4Cm#%fco3d@k93x-IoQ{n7)ff zK<$!E_f)M|n`t_B0ywud*kHIaeDSu9qhy%3Wgtyen$?{UQrZe>Q1+WdKgLBr5jmSw%_VB0%Od3i-SV-j$GF3cfJ-@0<*AioRQ zr!$r0(Bfsa*<3O%l6@g_FAOF0eaiNiwTJr!Np&|{4r_=QI2rE`)Yl68NP!aC1v4Fc zTScT(j*^1jm~Rs`h`Q4MS7F)baIB}epm|G@@N`xqrhaKwGS2AJtmDk@ai2H-m4Z2% zLj*p0_}nLYC)GJb_G5$j^DuFjk4-$-w5)^qd?m10etcHF%~CGbC7YOU-@}p4WG1u1 zv05b)t>J#I`YFG6eTJln#TkjFoyr)9zwQxKiEbcgn~E9`*1T14Y!njA+=BAn@hMj? zJ^7WVAKd1UdSK`CP7gu^RiWizy62e=>kttt#@->!C?wAz(HP#H+(5*-G#(x+aZ8bC z^M(Ie;D*r2-Dq%JMl(5h#`(9E#Y4oO;o&p$-IuHUj2{-9p@F& zwLX$q6;{b09O1B4hn&a`9?6G&SYi53gWK#7TW{C>Aj3*lGHIj!I|!rkY*rQFC@_G`KV<07ua^r$iC z;0Ws<`wBFxitz9Ka_UE?>kUYU1T*!ee@unl^0u|$<{xk|EGKLh_GT2X2WJ9{u*>lmgdsm&_)yWXZ@P$eNXS z;~@S6nT=}wK=_5~KhW@-p6{9g8{t0%ZvQvk7;{0Nors)ItWRBmbbRh*Md9%ypCHSF z9l1<9FFH&ErzQr!+lW<-z=lEPP~R{p9zHD=qMLtPKC+tlag6wIyzs zGIFJId11Z5mL$EEO5u`V=H356H}-h`5fWsZjCMfYVYzKO7Isz2b=22RldWx26LK?< zxG=Zt-_k>aCy_5v>o`}xP>+B9MoSMJpDf~J*Cb7Ot#sD0OLuK1Kp=bHe)({IsvIQ} z(ahWGgn!uJ9;B}&vQ1BSZXI&U2YxyTKKWtYjy+3Vy$|=Nn%(~ao(mg;*BeA_AD&S- zIb=(zG~8^E&NV)%5&Ug_YT~e?K}8Mh?+q){oUh|r_nPKh<4xm9fw!DnL;G`T&N|X& zo1G@M$y5&I)R+!5J!c*&*81kxxi|PeUDRvg(m98VEyBlh!)NHV1htn7@2A8(01RE^ zo1slpq%I*w28!sp3Pvz0B`gjgNeR)`o8S zT0?5z)w@?~UFOM-^4_N8TJkxe1hYA~cu6&HmzU9Sir;nY)m_3NwqmRG>*U}2KYuaS zx8{sHEA*y1O1iciSK1hXLjkb@tqBb;70jYqa@M6dTH1shXe7%#Va9(en+HgPf z7uJzUXtJ6>a}oMnRJ|OG?iFVw7gKQuK-iPNb&5c1gC2j?dFlHie^;!KM@mnI%N2Kp zOR|-i9DH{2@9&ps0=_r2{9*A*;0vdP4G72EScSzeTr@ZMxhJPc#TjXT^SQ@TIDR}> zUn{`gQ5Yn#4y{>FBm+tT3W)j+zQZ@QXv!Ndco1)eW0~03QJn(K9=bPY&NFPD7Kf!5 z%Uxt$7aW<1dxfh`<}}0Nj5K%ru_BGy`PuN9FP0i#UBmRX#td5fu+sb^?+~hC_I?p- znnz%+k;>6|0`Fb2`#sJ%l!IG4{IXirTy(IYiabw=F&QDkG@;ahvyV8D#IG8KXr~E2w&lgKIKJ_`HFLmmMv*wOZ z?x}CfgpZ3VAzyAdbH8b8si&vD_2W6+FSUmUS}V<~b)Dwm8b3XjZ&Xu)~kBE}iNH?;KzA*F`W zn%|1-@L^Sw7~IsoqBZDX-7*gcvNDPk(s(HPk^?0;hl902H%wJ}G%OxGL9#L|Hqi33|+n z3dcH;u@4mF=+a)e!$AvUW$Pwz)3HTs6zm#;4F3=GO9`EByy_rTxqbC>o8CLFjJDzc zo?QBALSULlX15>L?=1G{e18Q!JpQ#ThE0AOvZ=J8nrX7+Qs!s%lhM_lYU(cbl4h3O zo|SS>M9`nJTRcZzEqaob@f$yZqjh|iqWmtzQWu4&m}iwv6g8UH#tGGqgA46vnc3aV zj`Q86j$smyhro@6asth19xbOfOP>rRR(rHkw)VIc+OqO_ntA?HjjG)nPeYJj;1zMC z%ax-xN^R)@XA=hk$qDnem(##n{3cj<#b)l}-MR~<7mzkVn3{mEWp*$1#u5%TOaT2? zzR>|IcsTR7IF@^gn$Jw}g_-gi1w!nmSi@-8X3Ui~mIsmyT60QQ-sAVUSLwkc&y|pllk-FNN?eVpF$}_S@}tcFMAxP?F@R?5 zsLw7ct$Jq1epqu?_%ux0Hji*NU9admQfDEAj_hiny;=;g9z3gr90^> zi$CW_?Z zC0%h3X+0$1n+K+L(SMt0wAl=)1=O=FZZU@%VG~AY)T%xo@K53S)|*$~C*c$Idvd`e z#j_%@-P5g~&JTh!U;ACt?sqor4VnN4yqJ?8cU;at==-OuMU{JNdLdxY8tfOxYAWv! zS#C6FxUP~m8?ZKWfC4NovU@YvJ{ldfL`@WRqnAm4x5G){Emyt-8A$QF%)|#Gds}-4 z(vCMfh5&Df7Y8Z5Qix$2veVPvXx90Fg!xw3O|=jw|G(Tku(})%-Fw(&3AyzXThq~c zvqWP<3V*N9eDz$6p3H%V54KLEi^h)CV>OmngX;z>Gqnkh9`1c!V>)|c4sPt7+5cJvb}*_d)Vm zZDzJ6zl)f1EHmkf{WiK&qYL(&uOzmeZSi-4oOvYLA<=N=Z8Nq({ZisUS%Wh7Etjpn z1*()Tt8@MUw_$Dk@_O)8X=T#$oPQmFAxXj~awn&Uv6TSeEUMu0ue_if8O!5&Sb(;? ztSHPu=g{041(}$lNBl0u)#XLVt5kfr_%Wp_zXS3yheb}~O*R@h%xwEUzJx=*nYe;5 z@j%u=0&AeM5lKpc3+xN7oeSdmX z6Q%lG5K*@nWSO;@waJ}d73#CTikQ&pCkX3AL(eZKW@JFk6x*r!fUSC;+J@1lorUXx zgBh6$J(&#+c+D)*s`EtHfW0@Ajx=*9x&Y=QV?D@VG3+WAAzVhU#O}0~%5@W-x6p@6 zmE}Fsq)hZToStGfvp7@txf8^h-@i+-PwS2ZzJ^LI ze4<+joW?O}Vz<1kP#l_mZ?@?|kY<>mi_O@Drj=dgsfot5M=G2OTSc z4;wq|mA{=8T7|8`FLvoI*4C8S@Ao3rysE;QB3)UJ&aX|~qMJK<*xQ!wJOV4BON^at zi8bF;t6=Vi<}vXHpAp0TtcNwWIk$_`iDvrN8&9MF{|o9e_O(77Q?yVJRrs*Q?;a0+ zb=U#tV7_{AT>sD`~Gr#F`^4faQT95}7vSe+fN*c zcdvis`GnkJi(a*k_r31)g(%d-;NAe6 z6aDKAGVa?2k63~<3wB)h%g7O#O)K%v8pJ!rpj6hvvzCK;&0S)D9-Z>KlTd{z-*RSZ zOdTw1tENzHxS!7BR8LELL*E1PiYP%@L#NS+d05=3?ssBOkE}6<@lxqG2gUvwBnYK? zgqxqIhMTPA_sQEnu~VZNcPxdg$SlbLk}qbH9aMF0O!-e+K&W+w>xs$7XB_=_!Mai2 z!Lx9myrUko`7Dfoq;eg8sSeG#ur?F1H16EJZ0}^TI5}oMo2dvae$ou%(!Kq!!KHI8 z&9$9bEw8x`o&|8kUZiCGh-#aA5U<_2U3z+|L5`iT5aprvi}jGrk(DAUs;Fj}f{2Sq zW1-9#y6X*>BRHAvfNOD`uetRI5!9jl_AMWoD_gkkJ6GTYY$)d0^D7l<+A{R0b zLN$u}0c?iqhk?OM=;;OF#9|}8xF3d!Z=tVzwX0}Za{1cQzqO29jfNcu-qNCMA3T2+ zuRTY=v+tvskxg^+T%SqlK&0Q~qk}HrHq#t<2#N3hcTXN&Jk_Mz3N)x{Imigc@RtKP z)y?Fz28)l#kOA>H{4+>LfC6Bpz8Y9nEG)SMouTx%+$QV|k`TG5e)^|)jOBnH4*UNh zyzJOh5wZ*@C+#A}UU^krM0P6LCQv+V1_N)vc?m6jyZqQ!HIM$n3x2bselrg|kgo^diq8{yAa5_rIQfTlHeg|0zB%U_j4J1h2V`8P+>L5o#mS!E?Rx*KM9Zj>qnMcx?E`v`|7Y>9-_N-_4Q&) z+}}Ug=r^Uyl}@FQ1N{AHg23v_U%=$=?}s<|Is%p}3y!Fr;h{5@tFNa<`e&4X5Dety ziVJ?UNN}~RyfRPT@1!>0W%6=mEKxUsZ>q_Gf7NvZ(6=9cn9p6Ce?(}0JP^d8nQTBS zekF#PFUoT3DYM;yly^acNly2Yud_zKHVr_ybA)y3UOfjeE)r$=#`L}@h}`02l->c% z6C?d!h}XPHtk3z@BXGdVu(fSXqcDt)B;eB*`*&wYW_rWB2)N%$er$DzdjQ6~Sa=`x z1yNX2Z?>G+C}NP?DSYy{|1B40;Lh0JJnYQx@~MtHnG0*j{L+eUTQi2r@JnOgxMHqg zFDr#)!S5ntW-S3uHi+Z=FD%?O4!zpfLjSR0#Rs%marD~S%(fee_ldP??ae&zMPx4qJoxq9cto{6QNUKD2-B?NN(&%OC&J^M~AavRkt2WV7pu=`K0 zdy#hhQg(F}lx6u*bLpvF6`MoPZ7{cjki3Esm#S&(xe)86k(O+Um&Q-R7Aw~?+Z=QA zFmew5-TP<`?!k!Gf(oc>Yp%W1QX+nFAg{#&1DYtrFpK0;eA%=zOm&6(Bc}J#OFDJR zj6|TM*g?31FE9tm{p>j;g~{VboZ1PNf3Xp*u(6kP^r@;~rq@OT^$&UBDmOm`7g|YN zarQsq+y%W^2F}@}xWH0R>9gN%REn-AKtWo8MnScL#!5&+l=;tr_r$muBg5ZRmGFx` zhre;R7u-)in|QFK-M%VVwH$h^vy^RNk5Db=xY;jQyCHB#O=u3BvbJ>Xz=P$`x5K6E z`w~h41;~--iPm(7`P2D$Z|W(xrTOWhhjer~wvAh7zj!j6oz9ub&Sw(!xS>8vC2k~e zE8IAr+cR5DvgUrfue6JVoKB&*=W{<8hy8sCIru55o$but%f#S0!EQ^wMBT(oTr_0q zMF})Edcqnx@bOVQyCG3&Hwi!WaeqFO!fr47n~vUH)~2^GTKt4Y38Kcdv*!*N^7BevGlDQT;t~Q3LWN&;(2=+fvSy?x5+Fi<&*@ioj7i zY1E}8YskbxDSLQ}Ee&m4(gw2!O@@6nhb*afVP4HxVgx#9TdE6X%4!t@}ce8pld`aC?G4_3Bf@& zh`gxmcEaq9QuZ7t&Kg2F3q})i?hL{?7u9g=3}Q*!ysDc0IoA56|ILG0^!WiO zKtW*~sF;2k6gDS-!c9;(*zp|NZcC;)bOVjGXMlpN7AOpWf-STJT5Sr37kCdSu{$fj znr)L!&~fnWkf$pm6if^P`xuFB=>;^#OlW0WB3(ft z8x&A(pimtT3htor`C3)DU&IM&%2sd+;4t}w?YaGCt)+(ATc_A}OsBPOV)5*thl8nM z=a=IGirFTXshI^qs?({I7k3-JvA^V@=yYbA3XR#g;GT;)n8K87%eBBjovIWJKmpP^ zloKeJ!+sjZr7b^^%wz;!l1ntq6=j0^Q4LowCMvQe12dqp@{O7ZCaulOf;XUZ8x0gj zJhiJlQ>;o3cRvn0qGPz};*!w!H(ZwkXx7?H@7G?p(imQ*(`sS?M%o)xPavk%4YHq9 zl!5#eqj6I_T<3qcrd1pT&rKiuBz!WLBJa^&(ycb0{SrCX7iAMc`f=ci@BcHaEN{tO zdPVqM9~yjM^>uzX50$KN@Vqtfe*#e!hGTLqgq%DV=o?XP#(GvpHw<_D>cQu^!dhg3 zFY(?>GdVu{`u(U~N&VaZ?Xm-wC|7|O)+Wm0J+2S*qfb*uLwd}*21m!rHhbFX?8!c{ zIT5;eqv(7v*x&`A=4T6iv)Qdwu{_(b(VD5h=XZ$bA8Ym{pf>dcGf%-cA*@woVpjL3 zF6L)m)A=mBAwNBNc5z{RkCLt3;#S%KD$M!%bxMeYl!OtEJ6VJ*O8q$gA`0ytKM^xT z{1X7rU+B;F65?C`o?AA9d%kFNkMDW6FuCsIS&1z*!^*HTYA~4(!2|scbu*>5h%lM1 z5GS~c)W6{8O?CP8Z{s^|X(LHfuyAwy6)A;SzAEqWbIo_qP^$13T4~#z5mn^0U;7=3 zk%y5&?7o;n75__1pLVMamR+RxIpYrA7b5)V{Tzt!yWYb+_*APMrd-)WL}dh?l z4^EECYmU8WJ_)-;XryQxZu4ld_B$XQ3((9jl+$oezGy=~`@D?e{9@AW`m8&Lrr0B< zbt4D`Y<|?ETsd3b5#xW{uVeSR*&ZuFABGZj2QJ2xQ;$0>+Xy6&tjR&so3OtWG{H=^ zNb-tY^`8bO?ae#` z-}~P^{S8|o7MrFWW&=7owMu6m1yFDBdid~;+6mP7!Cx75G^}#5BO;%F7}qxo3rt)k zH<{<*VnJzmdTh<3hp1hsGP|k^MmKDKsf2DXCog;nI5|6WYqcBf@)mdY|JX&(Hhz@& zYT}pK&;q@F+6nx=LF4^(1J8NK*ncDyZ`8_xQ7-6j_~Vx{a4LWd;6MVWK>Ooaef^ID z-}8|-YMQTSKktyQ0uyuU_ut}P@%_{SmH5v+VQyk^cAE3f@b$t5R^i_xXvbEss$hqi z1vJSxeV@`={=FN)zR^!IxrLEcW0#lb{nEm6l6cu>Ih@GfUdfSu60k?ZRuBEM!7BSH zZXT}!$2OHxUsvJ;BY&;85fEIr@{a%$B_zIyTV+6L9FrE)G=Z|yL%hU^|2HiRts4EI zKUd9QA&Ybk%?$<_r+?+UmB@p7tjoatfRjx9uCcsqtSmkqLd&y1Q^Kgr)=mKY8pAJ= z;Dw>p!xvj+_N)+kn>3mNsS0CXgzuFR`_+0eo+wRB-?lbWT}duJ@$G-KaKD9S&c7EN zlKRC`GoE->wA=m`dT62ojT5Y2mGG){*Hi@pt=`@en3(tm?7;jyB#wsV7-6KdJ8uJo z@lUulWYxFrM}r^Z7%?4qe(0A8cBP)D4)U6KTn{ejyxj)svW8X;X`~!5%C%XWc?fLE z{3Y-+N+1AvDR`K(Lq-=#k{#H2XxTg~2&(YIYOmS#%dCAC<{#Xx(RT3M@@!r)ndS2a zl&*%x>xrrlXm9`^9cpI8|LYdYEWo5bN#IRa@yma&a=S;LW%bMj(}rRx=C_{bthx3} z?R#Qqch2MYo=IONdhQf24)c$IRXRTL7jE`J-k%FgBkA3wnEU!UQ@6gz>XUxE_PY@VkGX7Zf+$9*fOM)S@Hq6JKR{+lPK zCo0jx2Gom{Uv{r@@0TYu_Q~&D_e;+Tcq!1l95eOxR!ez(`diH&`6|X@(v-|Fi=<90j8}SH)tUZu~FC6GS-eDN$Z zzV*yZU+T)kz?Trxeu*NUVbUyYAzYRj1UYL4NdyR#nkHGZUO;?jC3q7-d58|>(c)M04 z-G;JHfaFFh_=79z-TjQ&B)BynA38tviR~3Ool@u6LZcd%J1^DL)O=zGZQ7cTQg^H3 z3?$mi<)TQq2{vSD80NriI>9c`Lvj`^muB(Yr6RVo=lyoVLXi2=^RtOt3s9mlR!pMq zY}Y3?NM8?L;!+{p*QN-S88{OVaq9In9$(}45+xcBz5rxZJ&pQ)7S3d3v_9uNPqdTD zMRMkchS~{Hj)`o`3@a?>mY=xEC4m#8#74Chor8 zm+nxPc^0u)<&3xl&4tpgwGr@ORFqXI4;+EZ3cqayTjq3n8~pY)1W7&6rYf+)3vAYKDvVDqP36!Bp& zBD9BB$Ug{N$qIuawgCy|y8z1}3Y5l-vjSYtkz8nd1h1A4YSpFAh&%}(1M?%OrImvk z7xc^=fiu0Ws-W%DbS6Xhaua*5`fx6^8gsxJa;**4n8%g`gXdNPO7I!e8v>Xr&Cj_g zxb#c*<3vy?0Rw9|UjXR-1Tw*ZGn;H^%%-zI2Bj1Wh%VL+ zqU&pin4v-G)Z#1Vh|tn^P9}-$xpSAWP-ut~aAHOtdv4So3vE9P6sSdb#w_yWiSZoN zD%wD8GYN>e9VoxT4Gh|>vXxNbljlHuiSi#mNq6WD18pWgHiJ+Uf!q^^zz`?k%(lR% zFQEq~Odwn^c0h4>FMt7RRJ0O8Uyul)$rh05%CiVu4-nKxH5Z!D0Q7T72{3NyR!j!R z2D@w!wDG5xsOlnT#7uTDVy3d1j#eVk(W2nO=uLCk6YJ8skL)Ewrvo|2)4&ji-Pnxz zVrN9?=SDCSKY)fbD?u$S5NP}^(3PDD1{ewOVm&GtXgeObK^U-n`!pC-@HHUV@!Vkk zk*)V7{H(#zNo={tvjS@k+BZWR2$w_QhGch)J@D;WvwAB4ls(gn%P7{VSJlJri?esh zcVBH0Xzr-Rj?GkfY|V+e{v(3-Zv|&Va*b0DDBa+I7~FvkS24=ilHs&SZp9hO=xUAa zY2Ed6&}`fDI@l4wmmtnL_}&@t_F5f}xKSC#QXree?wR9{YULK3{vNLTxAKU;7a=RYg;fAh5P zDFykMr~jqT@wuWM@-e1k$2R{Od;H$_k1#8MO|uFh?F;(B)cGR=&%Y1mr;FX4^B48c zc&@^VsWh;oV7Ni^msxEYp>4NOtbob;lmDg$aV-GsZ|xiYI*ZH239^65@^s5BvoQvu zAd&cS-wM=wzxO6sbiupJzsRM_2=lYuyw@9F+Wtq!rnh)l=a*Yho~V8D%W!@DjPa`B zHUN+a@G!_SKAz1${#viU@e0_SvBEO-OH|%R3-P@FS3kX7b8)9OP@AK|^*FCUv zxg#oV%WfDMdimk1IAnDaRpm2PR8xbPFqCVX@Oq~Ka@;zX0Es{F-C|`Z7yi8a%pcp? z9c>z&0p5W3a^JP0pf5|a$EooZ-YO%?-|n=yYBd<~jyT9C(nc+SzQCsUKUutHu)E;j ziF)je3;Np>Vg+OV;IxWmCyjjLIo#}6pia1d(LX!?l~O!jtHaY9tVck7Nh{J7VhYls zssZ=99K-amO5T)eXV6vU!2pw(bhVtf39VwxhQ-YFUuGi3kh@LLHO z9cvlVPnk-ebEsVmvf4LnsdvA#PTJgC@SalP2hUXP;xBj7Rb7P8MGsv=MbF5#Sp{Cr zqkqyX#C+4Wtm0m7S*7iyb_aTh@~QJu$n02uIaO+OSd_U+$inAZzi~t(VYtqNV7t_ z9kQ0tuB*S`-bTGEdo87JGOurTOWE(4oStFyEG_F4Ktf=V{ItCvBGtfnuQ)N zU|o$>8X*^SDijlknoA!z^xWAb36A0o-tnK=_w>%h#vF9mna%<%fhfaXzGWqj=q&h@7Pkz-a(3DCw@Fx z&m*Hu`B-;XCP2l@>izF5FW!=79FaR5V@dMbXyO#3Ve66(3uo<>vmraAsl9|t;?WiP zdc;@sZ*b{bbq&M{ARBtrqv1#Z1B|ht9jI^)skj67`0UD~9GC+*iH1 zXzfnd)}xD>j{aCfHt;;qE~z|c`t;1yUmZ(FdtNc>_%B%r?$=JTieLI3hknCjD@dJV z$2f1GPGqLpTUHEHh;|;|%9sF)FCO(gn_ewS+|~x;^?7HOf?lQDL~_i`379sAQdQIx z6EzP@*xf1~_3qlQr#2vYU;>@{1Hx~Tcw3xXB9R!Rfo7LpS%P~grAc}wzu~m^6Y2{> zr7guInNC6$J+T}2473GLPsFmFBt}_}*yU4Sg!(>j$5)|QUutcxO*1C8Y3(-FJb2?x zF4In^nK06tDQ3vr$e&f=9M3RBB|Ir81Zwow+fOXEt zSGrC~p{**FGeyIHB|fk}kC)JD^^z;eWg1>S@ujf;bL%_dH)vtj?#incDFayauOPkf2tPfeJ?qtt631#E-hOcYfVRS z*L|E<)R^5pYhl#u<#X#^mB^*Tn%>H3pBrAk_cs^Qs%)?9zDMh+T3Ug-zh_NQ%-Dbz z`|Zx3aoKHd@dTd5*iXDYlg}$F1VgQT#EbN=YGHWRs-kVY<_Y8Ud-D+H0Dfq!U zTGEM;N|{g|-2s}amX(cew^X#pKX{J{i@!~5&=C&mcdo3bXLd^=5`a0(+2lEDK=f8J zLAqS9+G8>v24Q~Kirv_oCl6_d9bV$-(iSrvwBaJrV2;BTAIQ*pyqg^HKAEj_Zn`<` zsoH3V>xKz~pQI`3*Z$f8(Z~y~ge~n;on5((PhXm!P3)sTg;{Ume_Fb!7Q0-%S&C3~ zE?sRlO)O;4oi`=qHzHa6LJfZhH;_s9$o6MM_f)a(^k2~?lN=RhD!;=;! zOGgv>yVR@Ei__lP4bk?ZSeK9(XnJiy;4!`6Fvn|6qSR{%DyH9|}V@8AwWJfmR zRt#l@*|VfeCMTDGz9yNwZrrUy$K>tKm%hsaIS0>1wf;q@=T5o#t1m9*n&U-QF&nT* zf+JjQG(u+CREgX0%VT{Q!mV@ZHCaR~kL|7>Yl03=Hsj_%?<-f^All`XdTLD-*NXOd zivL`=Bt=JT@3>^EF7_d`*XSz;HHnJZH|x=N5FuVbp*qf9522loNMtiJ9A!iXukxnE z{HjIEE`2SG3}Y<2-=KFQ6>0;9jMzo*U&P|2diF=T2c0XZl*;gFnq#~Tyg09%x2U`K z9=S!48SS8KL1%8hM4@aq)`Y%t}Pigf4n2WoF|+-TaQdspeCtdVyn zzWI<$8LT!`Jsd57cE&cEA}fn6bL+vKQ+^?E5e}_Ck|d>Rfnq{6L|SldSoHGRbMzgj z@y0P7OJ~ zE0zBhEBM)+QC{m@%fGEHX&^IMhtm-*XST70Z@wy3=bIhmv%;6lb<2*fiRx6 z3WZ``iF&>+d4s}sG#nkrUJ|mAGgi>FiRta)(H2w_fa6CA%nsUW3~NA}&u20_W&=&KdjIHC!2g%OdnU=h0UA~tJ+`E^_%bwc#0>Y0*w-oJh{8&25O7-%D*BMGk z)Sek>d*;aID%+e{$peu)Hv)+@=N`4kHrTqd$px*ldCC~aDTL#8Pdq|QM(C4bUl!u| zVyb&bISjiOy&*ulY!ExvJ(eG1xNT5+K1bDZcJ%fRWR(qVVY;u=dXxQ^lB|A(%t~aMth!CF6nczaoKUigQhV5hw+O0yZ9O-+ zp$gJKzjP4fINXWSv}=~~Tx@lL1FTEYfr-NXK@9iV(aI%pD6g+y2~%lwUS86UNk(dD znC9uB!)P)3zkio%J$O%gGHFJD?LUpF6Ud%Y1PR z|5X{4(oIJ#KkoMI#!$Gd4_=%nYu*+#*%*Y!4Pwi=$9p3jR-1VA{9{(eNx!hk-uu2) z@noA#`O5@BSZl!Zi58C$VZNXvEJsJfW=41(!093yI86?cFoWTSb^~^Kl&c$CMfWCWcKhl_1=orQPL3v+1UrXIsDZB(kYQ zndvq%tLIIhZ;#+=4vTuzvXj*uz@W{tXng`nya8svN{CO*yih zYxcN9tT4{R!JaWr)y=TgpS4r*Qv;&-U6fcf6veWtW%S7X(fp z4MV}=%xZz&HV}(xiVaQWbStyF;}{0$3zwlPSeA1wBR>Vt$hb$>saL}_05F|{FjNsZ z8mjJO@*2}?XW)Vp7fODKpQ@0jbe8}GKk)!ViA|IMR$(rl(a)-FkzF+tT$g>?+gaK#H14#V1Rx*b|=HzrKkjK$8`T*)jxjr0F!=)%p>7c^^ z6mEt;o8eCv>Pvhff$BR}LR(Klbt%+Oaq`qcpZX}`CF!OFDov2Iop5cMCR&egmN22E z(G#^}BtwZR325jzD1uf5X#iG-8g3#&1t8?qSPWx&4>12;;ThABpP+72P&Wnwe@8>3 zQ{12>R5#>T7DfoC?h}uCqVT3d^{Y zUw>*>b>}%WKBtCZPzp@TRbSk2#UI)ze5+S`rs9_ZQ&iLQ9LtR`9%Vv_t(`DNS6Lge zrU+9dPw2G`ZIsyO=|vdKcM>JG8Sw87FLD`I>Tpe_#qOEVmm3+7YYELoShG{-IXBU> z&vG#5PBGK)|0L%!LzC-jpM|>Munx%{zi@n_eq&5^T1JMP!WhtLwPNU_iook6Kx5KP8%!0wEJpfz#>TrXImPZJkYgA- z6T{8|;iN2;GwVCMtbB|)=sP95j6BWAcA_Q^kc+PsxX+rh=%yl(zGJ%U=%WN%FU3*9 z{eenmVJe1kB!*s_2tAWdH%7A{F~)F#2YLXnHbxoOT-kRS8k#m~h>l?b?KFBH-GQ%K*96@+8!#gV~pvg0CV`h-^-Y z~q&!3Agk3t~nIBm}t%bCxDQO}s72lH# zO{yeNyKz8dre$Lwz4E$IjohD&VVnj2Y!2jHOo0;Y8>5GgLDHgq+GI$p01G;^p4w**zVGW3D^h*bq+#ol)-n%~KclT#_-dl1F)1|Lz_lr!Z6s3#$W2Kd zrG)!a!W?*$m11o?jgUWf?AuMCy3KSpsIUmlIgu13KAdO3u5wT)8GgYvkivk(xabz} zE3_q8$ZaO_uQ;sttxJAU9VO4HJI^+BMkxxxPVLdej>RAv+fG00UMn0@Fke)36yg*l8{>~#90xSoc8iC)gSm~(oM@GK%H`qZ zYImUx0jKwe=DFMsq{phIV;r-Jo%cHyS#K`2{Skq@Oy}cOf0F34y>E_B30%)O{_9s7 zns0tKR~>99l$_h6JGMe%$zz*E`!InyEEL3em|&Ne+AKBZ6%XM@V}R0ubO-~V>)ehV zz5VWCY|7s`%f<@15lZKVDYROaWR)slfw_7(W7k$O?pCQrxBN6Q!p-ujQk6R~hqz?s zPVsXTnDAX%`fZa-E_eE0l@8wCVd*(&-5A!W7bwh?#2hZvJ#r|@C3?@s-0Sm!#ev9G zw^bVPqQ;lYChtspJTmCb9hENlbz->aVwO&3yt<})_v&MpmvmcQ4NHM*d=a~MxlmmscR zWi#t*n29~&QrNDd7RF31dlhYVp1+X0eqavT5RYZBH!93>!T1>lQ|}TSC$6|b_^4N9D^9HP zH!o@1f6+KGKPYNlQeQ27>eQvMpO55GXPsS)jLaY5_n6bfU!*-gi=;zI=8NhKI+L$n zi&9v6w&op^58^>kdsd|dE-J_mp<(IS{a^K-bra3EApRCt)aL^WE}15Ex%dYeNW8j`pNn68JNTg zCeZc6HGiE;zjycR_+T?w@^Ms+381Z_4VioPxTg;cav!CHvoKm~n$pz2)B$(J& zJX_1R3b=#RehrG~yV_XW}*ZHVDud6aD9h4_p=%C8etw@lVqL++d6PI0#obTKhgYC-(enAi!RMF-4 z#K@sajO@*$%-fFJ`|0(G0nc{0EH`U$MN*$zRu#T)DI?E#BlNkcaY!GAhH(QX`HH(v zlcDL0YFYX#%ha;c@q1fijrWVNSY@#-0jD3+>#+kA$P}%o*(8?S`Pt6_^hN`RpeVlU zt-otA(DgE%5X`n!cU&s^$S*;6SZ?s;rbvDWqckkvb@EH0Em{Bl)ni5{eY1AlSA zj&da7Tx`%%@ZLXGIbDf$`S6b1C=m_)TD~^V@?!py#jY@jKz&yABFRtMb!3snuTxv; z&OlvK)1rWB;n2tYM5%s%sb{+x#~O77HchrKLtXqQDf_STDI?U@-#R*mu5!+BL3iHa z4hc{3k}C>Pldr$!#kB1DIeJ_h);)L&r>!3-Ld#jYE>-n3SB7=w(LJ802U7q#NIruT z49?!eg&O#EHvfWWymDdn@@!BF)dQ4cV+M6ziXh+!%&aTI4WHj4hnpYbIhi0`DtCma zj5?yrGl*x%zXWEfz^`e1V>t4BnAL>Ax$27DllSHuE=r+3L(YIdYW041FuNE? zcup$O_DFGA)c!mNAJOUyMho5M4P1G2amMZ>!g`@3NS5S5QXNRMV{1`c|M>-z1UKMC zHn`UIgP ze!UuoD`YPNrJO9u<7sc^@^MRAwCJnO!;=4!a(}zyop^)1Q^F1xO3r9;q11n+IL*lA zg86&Y{iU40?D|)4xDxeFo`Hw}WWeKcrn-Gb?Qe=%odND-JW#T1SZ<`=g z{*{b1q;)TQtQZuTd!4SnhlN&rPyG3aOL=7vBbv3r9|lAZyg>*O{uN&DMw}z{fcL;6 zSItIpq`)eapGw1`I=QFoQSUW=CJaxYyZWnh*EqHLUp*N7HHF|zKLoT?r>lTp@X@+! zzkloBbFB_*7&h*<%5CdC%(9}t=SFFVX(rnMSGjcT%g-U2VTdHz7-J3Qk;vZR=k$<2 zu_DN?p8pxD`-PgSgA0!%+~fzF7Q!|);YNL*OZFnE{_}XbuB;ia@Nv~G9tBp3dG{)H zS;3ax9@b04vsGl|S zF9U7|#qQsK=B&$I4ryU!Put+G#ekbfb7X8`Z}GQQQ?L}}@@xEsQl)Awj?fF=DCUEf zB)G@jr{sO7bl2Tg&S-ij4SQo|8O?_~WWu1S+(9jqZ*B!?VYwg!L-;_`uInIg`hhr; zcHStX@T%UQPOYBq_@tt;@w~E`(-VHYSg_g9ae8FejBVR1U#>Y@cR%y9)CaIM82pmA z)R|F;1qGk{bsLk zSv_FsjOdW(C*enVV@TE2hTT?Cz4ZjQ?A~bkj^GnP83$Y%jyJ;4IDI(2{KOF>O))JF zFYGnT;$-Z(P!Hb9Z%3s>{~YAIjc+|{6ud3B_O*mxq>N0@`qz%09SqtN5rEQfqm9~k z+6!Hx%KI+b08w03;R#~8XGxIJ&$pu7xTsFEHJGbN{pWU{wL0^8fqahr;`=0V!uJ!U zM!11{k49eQJj{S&gzl8Y`$K0{~wQK&e7;o@W$b=1W}ux3_mdZ2oBPQAqc z+;H{&4oD`IosjznJ3C6ucbnkkYpX5R3h3#YW!_OU9C^|n}l=Vm}J_{35ix6=+tDe%Fi1l0P< zYrXBJvOn_U-wjL$UjUU;Z81V|>4`7e7Q82kBWXo>p94NxtXGU8*em?jH?}4ql*hk5Dcv0Q#-qD`O1)(yw%REy8BWpRNR(TjKnbX6 zB?K?8F^k1U+P^-WYOcCvfFHjtp(q&r4qqx4e88~R|1Ci+vQEY%R_=hTNxDllevcx) zzAB}AKe#U?t-65R>bOo z+O$%==vFLlacq%f@Z+Ej-FkR;JBIv$JgR@?A{1#ShS=pK?z)j1huw=V8cy<!d2Q32=M-2<7#T?Afve6)x$@lhpJNn81a49PQmE%F}yICIxlPHNt z9rScCzEB1TQ%Q6ytDiGs^tLv-WzH)g)Fptv9d`OtKZ-(Pz!cee>eGY3`9%zr42eD* zG7sG9(fg7c3_5}D2RYr7{P!z7v5Vuu?G6q#0^pF&xHNAV(3Hxf-u>7r!{*1*Mf$H83UOyhF;dzZ z#CazroYqfp^HeG&=hO`R+F=e#(6F;qOoBfdh0ZE@V-_8;5QbhW2} z($D3X2lZeW$nSxxOc0JAj1APCc`6|)CG?LD)jYi<4)(USKME-`R88_*Z?I}%IS`{D z{;gN^;FH-a=d2GG5TjZbH>##1Tv8M6B3R&%;fjVaW@jmHuzx`*pRMYlF$s*rcb5MR zz3~S}Flz;D<2$yR@0vf9GJ&{)E%!7VJi9FA&d*&I-mDw&CxIsj=e#ipa5vs>S_j}h z*aR*Vfyf-4dp&9Mp<-S$L~a|~<=l397--*W{B>9WqnlSU-o}iPWlJHy7b=~G`<8uE zHqB&r;uqsB;AC(xW+Os8nP(a4poJJvbz0r=DSQ)4U=y;1{yCf@Uf78lQ2fd90S^wm ztY}i1H>%VY6oyw{4?6=7u8~B{t`*HS~xB~W@`|*$_{i+*w>kMwT{EncIDD$=c^V+ zHb?thlTIZZugi@!v0TM91jhr!-6KysB{33GUuIFewX%6_CFfqW{gdSf{+}~Km+N%m zkgC(KD70N{>Zi({LNKMg2ht+-2z>OK%kkm2zs|wsMaG9``maRA*N`7BLyfI{0Hyws z@>f!2{6{F9dll>r+ex&->?_$%I2!zX{AQs)_r{CDp(&tb`Poym4DHBM;pfW zE+tpySp{EXnARREGG5KivchtUIWG==KN;w!Z3D%+69jN`z={P+BR7N$#(1i4K5A~{ z-Xa5Rh&gb=Do2LfBh?0bd$_skQk_Bltb}v0$5z|C`H?6ECg}DFp9$rXKS#) z==0$PUl7M@&6!7pP{|&BQJD-Hj^w`OVkGU{`h$oY<91#GzDIf^1> zQY?3l0|}{}%|x(PjG7ZMY;Q(DiBIWGktCV-oPPeu}>->H@snu^CL zB(q5zN(>)+%9NFSQ>vJmzKSqwLzJFI;Qq}0Bd>_`@tabtDAO&GjC4-h*Ky()@6B{p z2NC1mHBy+)W=P>FoGw{kyQ{r?=)J+1pn=LhHE+o{Of(HMGyOHNXM)I%c3TzAYT(EK z0_;F%Sk)6J`bsRJ3{9S}4}%XgZObIBf;vMfPMxk;+o5KV0cdyUS{cnm?d zfqL8;M~}j`GG0PIJ%t`MG|0uMTH{WE@<1`q6Za{(0dRdN@&LGORGkAwMTuThrPq!e zOy^Am%0HQkH))Iqu}>DgHa2$3HC5S#a`p_)J0VU>8Iufe#;`RW6coDD?t?gEu)A1C zl|BsV4I1=p_Fd{Rgh86(1r;^6ZW4=Ah!bI85c)yt(d~&VuF<8& z`of3U|C~qTGC{851mE^p6T?dd4a7SrKXM2xJI3h@13qx`pa<$TR(k5DC`%ZjsdivM zQnDZE)>sqJ6~HX6Q5-+DpYlSI((USEm$OYE+~Nr1uJ82S@*5kf_h2@5X|Clx>o2Pz zoduI^jpzv`6WwrT6Lb1L*_~WBN<*sy(Ae*Y4%Z ze39VegG(tbyX)vZU9rq5Wo0srh+zj2A-#>EG}NTyXEu+^_Bwr{h0`d^bUcQiCCX5p z!1s%h&T*=hy`4v3thzmqAvirpGn>62YR6$y-CV|<6g z0v=3gaB4WNBW<@Gb||qQ52R;fnzGGsdz*^PSbLk^LBkx3D)q?@xC?@ZbcFKmkt(Eb zFk0!ED=V|MR5^>2E}h4TQz?S!_|DBrGM;u5?foSb$iND3ZCYX$wY6y#qQQB)v&oe4 zi74e-$Oti=x!pCmHezxb+acYggEcZM5)+4&(idT<%mQmNH zYkzX1TY;hmk6FXo%6OXsb~ebvYBHx418Z`oy#s3`;NLUZ>!~`Km=LHGi@|87A2WI`~;SHnekJ}?{NSKhUT$~})D*vilEbeUjkH0ZJx5E#<>ypu}h z_H)Vypv>%kTi&w9aLMDq`^#m-z78Ir?KKF?knyQHbfeLZPlM|sx||ktE@mK83ogn9 zH(3F&Us3IEon*%Tliz``L^fB8X(obG;|~>i21)e${d%V4fx<0^LTdd9~YtrP^ws0*%F~xmC z6kX3+54%g-oQ;XiTql6ai25VMEtPqBHy##&f(d_;EIvF+HfdE6kY^1sPn|snJsV`- zCk7i}{DxjsrfDsXMF`wY>?a4h&N6XPh3JKmBdX}R{GpLK64a%T@`1<*p=OU*(lXb zxq%`@;r)(!L?eq6ra{r0)Izw6w11xZ0m`=bUz6G<_sJr#UN#zHl&+lf-~Gug{ph@V z&6_Xk!|j&m6e8tRBvFI&vLMzEpOjKt`l^qPj3$--%;XJ3-J(x6dcr%rA$)rrh9Vuo z8=s2o&80jXZ4H(8V=Xu^T$C;2KiIgmXZhEb3jmnzHAz&_zI%ah)s}5b2KQK=06^~- zI5_777d}UfTlI02WE1!WtG_GwT9VAH)aJ{TwF!-Oh^pFz2S%Ce{zeB$squ)mQuZ%E z7NAE3Gi+AS#|Q}@Cux(|>C|HLv&7?tSC+5Nknj%WLRI~7#^C;>9~C+R^7+uPxLT}! ztk34}=8Lq5$9YI<(3lMmDpv16jsK>Ap<09;+Zy-yd396%89O7R!g8aB;&8L8cV|$O z$X3D8D^MBB|2V{zdh-|ReQOmZ>b$B*L1J5ihU82$9 zY+FXLAw_M_#2-D9I2hdB?@t2lx1d zNhS2Ssb5m)&m2GmG2GCBKS^7t45O}>Z!fKJODVqPk{lzDhZpmL1j6?#o<24LEj#Fb zXMGo;1pa&#>biV2bH*_Pwoqj=0y0`^y-hC15q4%bENXTyxn7-6nUL&zhb!)n2O+`; zy9=(KZ`nW!O3Y2|9TgSR3L=G3+|-$oJ4*}8btd&{;5hzx;Ymwit8tFwVY+jgr)}XJ z*_f>=w`p9?<%D|&^2MmXH_60^L`Zs?Pvoljs0p<={60&gT(xZtf>(V)j_xMM&`D&s z)VH-RV+wJll1UgMVBDn#{+Ph1fuva8?z0)YCt7AG*T3(y&ByDq&fzO*58gt;q`vjd zGK0sz&sBaMPH--c9k~yTYAWK95pea|x-#s&%I|LK*NyoTSK$c)iET^%)0_?v;os*j zndT@mo3IX%3L%PVG3>6dh+o6_7zpMYsWg9Ay{NHfp;7IBoIwvMmC?zj5^uD%m&vH{|+c78ZN!6_gsGjfa6~#jYU==!DX*F z=Xzg=9aFkw;+r&6crS6~(n*r2!XygyFE0x51UB>W_}h$jrCfPU`$&;IpF)zy@qKMX z>nHv*yeW5}g|%TXl*?be<2v&zX$*WeV}A9Die9j5FfhsTbmVE-^EgoKq#@9Ci>McN zqS|bk4wj2h)IWuR1{XHmLD-C4vPTkwv{}8r2N|5(>u6HJn4#mRb`6u3(yDbN)|v1b z2vakylGy8y?*6k~$@0eEUUWkOpnu2qpxE`vR`SNf7B5ZK!ENwXUD>-BA-aBx{=Gtp zOCI~M{B1AtI8N%ctj|;`k95(UILN9Tc-l0#ahP{do*&NG+=Q(^TLNlP@ngT&d6Wyk z#W|r@MYrM{3p>Lr5VhlSjn2XN(ev*IAIsm|!J&;HQr0O;-E=kdF%c{Tvmb{z*lXM?fAEbfv}RRjN0o(#tYUNKb8|H z`QW>%0H?M%ElhD8SRX6nb$5$xv ziUnKsMc)~g@-rzU5@dV;cELYkAi4Mhaz)I0U48Z7?7;gylTS$|m>Y~qg2Ya8oh$#N zoVmVy76`Ul`{f|>>j?qae_VT^YRVMe_t<7H+niXH`}4Ez+`B)@G4^6WYB=LH;G3K1 z59TzhJpYDG!2lTYiwM4Y_1#0$2bGdjEwCLMP2drQDX6^t^s*N$i7loe;ODZs6_0vF zQ7|YQDC9ylLGtNO|1wBP`-y4g@lH4xc@6^0L_qCULNJ08PXs$vI_ynEWq3CIj^0qr zYW|By5I|E$+2Ar*swG82BoFtBEkK$P;Bm@mL%{q%eWp#S!&235shX;9n!~scm3sd1 za`ToS7F~bO_d06=#_|?^9+WNImc0N!N9M0jpqQH2`g^gJZ9bM)+wwg|pG9)-tCE>$ znfHw9B$dj5(DRqoaf`z!DOPV^*86<09J|I-nj1LfMDJXRYB~4cF#`$v6i&j|Vn>8m zacn)&kjEOV7z310e()^h0*h*#%yDdY^c$Xx7#d!<0FENtGt{ksKMF8)enu)N?y6e zm)nb}#}_9$DrX;Sv4S`og%YO#b5rHn+U`_+hsR;uq?evjl@1TNk?t6H3SS>`@GYA5 zT^s@UZ(8$RxwjMRVy4U9CN{77!>B7%rNf>5>94c*IE(7-uHoq>OLL>>NJ<~EhmLHl zea?S1_lX;B$FLaX1?H3d4qBBIQBg)KT5&gIxKI}_>Ak!LAv@9)_hMC(rw#xIaNPs@ z2Zmf$i=WUhW?OaAvk`lz@+*aiqx5f)fQ1Y2OpqQ-JPFi z!l{p8zt){MN1ivm@$HLpJ5Q~NQpj^=^;Cs{cQJ@a1nHTTb)xjf3B=c^iQ!%nnyM@( zcO@0X2h9qNY9pQJ5O-uPI^0q1YJ(F`tZMS5d68{Wi9RgPM8wHCCkJuG(P&28eXLV` z;XZ5m#!7J9rb`i~%@Pq^Lvjuqalx*m*N(SQOhHniMVGa2zmm%N>_*6TX2}v~s+$6@)r@e8G;=D}2YAs`_(dol=)lncN%!Ofy_gAhIRtRcow5o8_zB#2_2FL- zUN#FncPGfeoNlgas_Z*~khlTKtjPg(@RmV9E#Cp%xuC=rmm&pZYK96p0}&BB-skWn zC7^6Kk<2PTs8Fc&czOjquLizCEdXw}Nilo{k2v^xfz1v3%Q-Yz7M@tuMmZkF*l0)J zFOcd>k+qjN_Kz~FkE5#t%;$RB2%o=k?fO@mDzRXqGoFx&=>;bucM0IQ=a}YVLJQyl zssO}$#epZ4E>zd60RF-RYv?K9OQ%!Lpi98Y*^>%TaXpZ+!B$HAEIMWroiV)5ES-Q+t{h1fjxK;=Wba|hf&K4NTyE2doo%?R{0 z;S6xUp#eUjAeL|dLO2;3;`9Po-@DEL{W}T%(tHhA;aB0^t(F2znHTz>@CATw2ZRT1 z(F(9L;+LF(xbCz-t%K?*OXl=I@)Bh!pNf1!^T~h#N;d-+LMX8>s8Z133t-Cz4~|Kf z81$Q!3GFF!0ie9M;3H!?D=(Pge%#?2PWn0U)sK6d;B5ag0H*y&Y8K|){U#ms-grHn z_p=lbxp?avqJ@ z?k%RQUBoA6!8I&90gZo7V}$ENkyW1&QB2lHM!zt8!NjmpP5Xix&fCJ)niazd@!dB~ zN#~4x9qgzKNkqpKj3=O}OM&ShbqN#;!>>Ow3}oTc7YNX5H(^K$fs0srEGCWoo#Bpw z;+oTw@Xap5U}FW%=}d&*5MF-(fdohdYGOQqpPxI-nD#a?$EBo0Z#2~;G4gF20n=S| z05IJO8R|sAcv^ayGnV0~?7|-`BW2SqtIq6+oceiff_OJ#*XK7eztr2w6jXxoX^K1A zbS|{@8v-v1IEdRnfH$}0?=4(cW=U{!n~Nh_N9eh=|LGj|6lmgLkq_13488rae}+($ z2J>!l`5ABB`h&{en?fa}1u_e01ZKeK$UDAWOOlWN;Z&S;ABi4r?;Z7h){!u2K)ZBN zaA9DQk+SE4uL(4$5*wXL+?y)1-ax6m*x%fMpPOy)b zQ@^u#QG-1H*}tnc3tx9-ruOWZkkGo>CUEgH;*RY5ZQsFWfAelQ^o7<~DBD@%m5|e6 z^LzfUr7{bH!i$Fd-g-wjvH~-*pAwDLw(Q6s^hZg&9Z^w>EX*n7AaeYfl)|FX*)Fy6 z3cHQ6o_4c$_MtMw{u`0(J(jrVGXD|!F0CM;#Mi04L!x5GKJ00@kMfEF3On2g6^DS% zV^7P=6$>VB*8~T9UT`ks&)Fa3(o!ZOl&*Qmq1f%ZoyCHFIwGj|K8u#9Go-OtpI+as z+ES2qF`aL;1$40MtyM>g-j90#>iGup&@bim91aDUt8HogcsjDM;Hea;*qaMA9{?_N z6&~mU%;=W3(!B=-B8!JVIviI;xdE9`D$Ii zf7fVwJ?Gl?VDAf)FDvCF?6!Q%vk@J!^CIjP<8A^{_yy&tl&!w~h#}3)HypgLwf}iv z#%I-Dlv`cev`1@i(RT;)3jRk`ORtl*-XI%ToIz}%Ca*PEvX(b3PuI6v2j>It3U96! zi-n=0Gk>B&>Wy7@DBrFu_LhxS4pE#Hxhw*)efquABmH?d#cn}pyW!uVtwEvx>2w~W zE)H`T2#fokqKn3aUKG9X2_Wl8WY=4rcOSZCX^LvaB?B;gEcmjQ9(^4V`w=pW}dB06mP(*Y%e&uR1#o zebk8?RV-W_yWe$y#e-V&pJC{%C;X2aFfIWmJg5Be>O=lFB*X>jEbzc%abnvwh56N0 zHxgcYDCJ5x#~kXCE&vx87u$kB^K<+72E`Ue&xPk@uC|qt#U+CGrQFsIT=}hk@}(AU z$tV9FY-@&e*|lpw`6lsUIo4$k9SmU|l;5`hzV32|!@0L*$18S5cOB$Tq>QeJc5Oz; z{XGs3YXy>2OIrol&6SBh#&piqT>uyl~iWX3oSv|-%N!xTk7jv&+Q6Df7@ zoj~N*VcQkk_>H-_HpOi{#{Ht`yVjV+)iwNEt7;FQU#4Zajeo1gpy#5s8UivY5{{ym zc4(;YHN@O|>5xQDNGh^O=p9J2j2<1Ii6p!|$U4y2I5g%&r$!gpK7TL0{$AP{$G;T6 z3AU{dF!iZJyI%la-~N#YDm|55WLg=WE0`B`D!vi0h_6A&-C(%mbyzZ(Cu>rgjY zht^Ph5O zNJc~v-p4kIC7UMs1(h_$ho^`xW8|Z7a0u1;Zy}WYq-`v#SH4jPh^Sq$3zmk#%!9zk$nI~?&?pEV4g-{e_e+72~_I}Uf zJBgSuXw9rk31^Cko}|4U*o5Um{uO#LU^j9H-POOfeK|#XJQ2@JH&2d!>p)L|vLDra zZ|+&mJ;6KtJm(*OIDVut|M>NZSf;}yO{A||x%l8PHi&%S_M!h2Y3<9HLg$uOD|2R* znQp=Ba(PL2$p@jaX}jhgn^q;PfwTyz$u(>3&e@pU(wSjCLI+WIn1)u`6ayv`*OM#$ z(~W^d(Ww^lHdd9C?*V%uLYg27l^;Df%nBvl#JQlwOI}uPtkT=J{xIHyM|%B=MdHd=W=aAh44+gHWa(i5WGiLR{Cck-=V&1SfcQ&>|%V&X>%j$>^{H?EA_ zFh7rMIKmpnj?%MgvQd#VGCJ@@hfRnE>JSsa(Te8FRC?5dA>T zMD4gHtE%nroz#h%o{arOevKNudgZ>vrkmHD;^&vqA?sjP4Vy8x^9Y-0OZ;htLMuQ1P^ zP2Wl5O88jOGGU|zOYz4oo3W%(=_xncnlhzQ`{etXUZIQnROaSln1t@uYTT;Uw39w4 z*S*3D@A7SKtax@IL2mQqU4yuC$x@xaclK^j=aN))T%U%Xf1lK2+7I?T;9g%avMz4j zD6Nm{OY7l^N$qczzTBeYZng>^F5h3G4!uzn({|Wf7uM)fdqH8WM&-q?#eX52*hJP$ zfU%$TwIy`}UPG4&LZ?vy6WYNRfw8H>w5B0)W&zQK>-|9V`8^Tg`dgRCeSW@wGJO9= zIjQ;Os`+;GRy!9Op7^kjGs+c?RDxBnef(7WqE9^)EyHPyQ;&#x^+ar<={w#d_V=Sc zah>=O6w4EbDiT(%5RqN_v*obPz)z&Yk2XOus0)wh_xPWS(~(niee~xi7wihi?uD%7 zzoQ8FXlVf~c?N$4v%3U|SEM!@MkA1A>y*lg%O3YLMMddm_c!pr62zwXtCwuVs$71m zy?067?YH`?i|I{9SQ9|Tdi9A6JtFM%$WQ*?>(vE$K%WPk&yylbo()p4K_NeBYv&>i z_}kaE(+IYQ{G_e=-@mNmv2V}cCMqhr7w#wr7;qI&+=lNvT~tn-xNM000@d{F$oZr8 zXY@~;I1xHm*8ly;|9ig(_Dx3h_6ic~+8MJ^v)B0fA8LCn{Dzy3J+YPH<|6>@K*j($G{}Kl)g2{H~kL%_2Arth=vk(Omn+8VQ+9J zVf=!{Oi=E~42kAl;?Y+`ZjZ>&GLX&VgQq2n{PCTun5mrJ$v`RDn&Oeh-rnj@W14U4 zpBLP?B6+E!9b;cSnr|~(zpT2WeN10OT3ePg_4S`mS=e=$y^P2rzL;TlJ66|;yv!-x zqkXEj$7k5{a6-pWA-bJ3>m)av;4!OeUxKR&u9_Slt4cpxK^?Il-|9T@t|(%vnHqTf z8I$NXS6ecxe4^Y#JQ>237Zg?XH_Y$^Ck+X8& zxvfJS1;-beAPE0G?xh|}v2BdD;Cg2S$E%p)QV)7}P1A5+gtbQ|Zk}Fxl9%7^`K8BF zXI4HsN!QU|?jLyAc(m)y^sO`dB-$&6tXHXIFJcboq679sT_)Q_pk?{Gy5*BlYbJ#^ zYJ;nQstX5Y72 z#Z}Z@L&1#Q9=^7w`Wg4iSsQ~#2i`3^$(%CouFs8YuJCNp%oN7~U8mJGh=PBv&h~X2_#8?y|T-K91wjYU9!3<5S&) z+#`!B-Mn6X?|V&(NK{0PY@W$-Z9Ji&o$;x9RvzvyQYUej$vwxcUzWRWB3tyt0Ygj& z`MZnsH%v$K={4%RKolL3Yhz%NOM5O%>ZEP@5p*744>`!lNMz zyI>yPQR>M{ya$vc_FGESE#C zT}+9#b-8wmvLSYNd+G@h!pzIQI^><{iFPL$nViAMOUw)(a;_J8B`Kcu#7_GXGaxN6 zU*giY7X1MiTtA)Q>P)p~%7uCHb-Ttf1{tiIuIS%L5R`tEY8M06+WIv zi=sNLMR%tV6Dm80IpHtV+dF3pPPI8b`})XEJMu$j&F7B6&03@^%{I&9t9{sS25Euc zoC4XxPj?tA4>T`ocF7uk(0}1_7=A^J0+EpY;dLw}YugU$sdWw^MUo*ni_eWJn1d(1 zCBBE&gj}FKsfl4R_kDOiWO;amW|R6yb3eB;D%mG%!y-s-cSHxG*m-KE*7iz8oPpX0 z<*?14aC5z;xaZdEs_{E3Sh+6`glm1gTNB75>Bwbaa{q&1z$2E!{i>-!1*5H3@bSu% zyz4itPI#97i2evmwrC#x%-D;GmVpTAyL%Vz6JswGTI$X2?K@!S+uU@txHI3r_d?uI zdo;1NAh5=v_ku%x&Ow_GIcw0I;2J(V`}Dw)AyUFni2AkYyFS%}dxs+om2bZ$t?WBI zjJJw-oAqwUHk%f1EiM!`g?nm5*jwvUZ{w`)-Clt4|Ll@wY{ipD!#hYvR-PgbVrm{L zk!rFhHcHK{CqI|$jtEQG2t)$nOs8ji>B@)lNMsS*HX?g_AWvM#4YP>B@9t+8|MgUn zRxeXevcZFY4w`;~^Lo!s&cejs3za#LtE16jxKO-2w^ySqQrk2+&s?7wO~btp8t z#*l&Kz2ov|JbWN*W=v~fawe|3${h4xzZXuv-fYvMfEQA14JYPInt>-ZJ8zt-mBm;W zSMXux^4r* zo#_h+%=h%8-uIU=sr_X`QB&pYnvIP3y;pOn!MJskhjxEG^_e`_zQOd}KwnxI3-{1x z_}gTBRUJLVN9n4uWz8GgQ~n2)fIjfM!WI&bZOaaYW3U}Ju~D+h?elLSV6tz z(#H(fc1~0YvCr}uuaLXcDXAT>wEo4(V&aF(937QU?xyuG-h4^x&z>CgYH4<5exNQ= zxgBWXYpU7SlKXtt-ICR4@B8e?W;CUYwj|?o-;;k+TkGkzi`Eq)sd^#=MQ7 z0btkfWnG}B*NNqTC$zV-?<&l=9Pk+6t;xMzGveG&xaIP;a2ywRJ7U|=El=9$JK=T1 z4=*!a#}pr}=VjbDMkKmt`N%se+ZdV7ZdCNas%461)0{na?RFRbYmfMcxovQ`D9xZ& zP-OKM+Te5>z59hq9y^0$&#J8MzLJ>ze|CP}Tm4C2QuXdW_wCNTo9=V&soLf9x2t`7 zb6@uB?>u&>^7S|Sns2}`vD&}5B(hv?+obHLPB#nxEUw&`nf|+Pr(T(Cc~R{9pWl-- ze)N8-y^(8df6Q=Z8L;O27Mb#Xt99o0oc)vSW_-PycDFr{U+&NMB|4Y03vRFf{H^5m zvBz%9;*XWd@Bga<>=}T|9?&kRcrOu#ST@mMUt$Ss^w)WKKVR3rP|XNxbeNq(f!4z zmfp#k$cY^AaX)$D!>gJ&^LOr6DifmNcow%P3Le`d4Et}UDQ^;`ux$J5o%Wt~$(69A#_4wWM4*070ChvQL-wABvCm7lZ$PK9IZrCnmeiwemp~{-^yvdH3daj=jD)hcXAOh+e9 zN%~|pkVf>nuxpj!qWMo(|4`qfgzQfXP&aP*_aE8J<*K>Xk(dz9h%v1S$@}+RFjx_e zbzD?^_QHLw_W5&UwZg1>)-`^akA32QmukA{+(e8gS4lW^ss_}%Bg9Rk!au7{q(iQT-|3`?@SP`&s$bRJ zE$j|(dXumBR9W!T<7IaCF>)Mjtc;`ru1~{(`V%VHV@DyyU>~v9>?jYxRcc6xwJt_h-FNo^( z2uyQ9Q}=}pTui1N-cv?!<8-mgsB6WCrvs-byTsf#p_d!eo428}ce*LVaLclk2{|fy z?w#o#c&Hqr*$U#FZJAX^LeR)A&N8J>YR6oVdcg|+l-zH%p#oE z{z{7Pc$vJ>dNg%e`W>iNzR%g2M7Nq_fwVLe(xbf{ku){uE^=Q6Ye}bcmcMm?#b>jU zK3skFlV#48E8}dLY6{If{$z@6k}U*HjfyzKQ{1y+J-V(?laX$Xm2@jt^MldW-JITl zUaqE!!yjIj^B)?m52oM<(vD(nb+@AYRpBcQu|(HOft@elYQ=-fNQ|G1$6LAre9yd^ zqIbaeOk#Zwwg=J~E==wfZ;E-g@!`u3fytgQ+BT!hYkpRM#9G95JhNxqz}z8upP#Yi zlaatMMNhw2xdDBInA?4UkAufqZ27Po4#(MTIiI}frb^1ki>IYu zvx_4!J-Lez!)gll9kj9E-@f`-s4j8Bm4#!H#IOsx+>}?JN8eJH zQT~Tx(uh(8(xNwJb}Z_iEC!iInbX}{d|6x0v4lUM-9sMU9-n?WQM|!-cXaW#J{779%%f88fiPMJFf1VoPZ{7h!0EHaVITlRT|f63I70OOSHPwj+oZ zymxa4cw4 z@^=`U$wwVB#vJB+{phYdiq3`G^mipyeWa908R=JEtCHJVyxn{cgvmqbAN}I|%B>T} z6U9e0T7$w6ob&$FXK!AGwmh?MBoS)nC}Tkg0VB}0ojl0SNoUq+#Qt%TQyiB(lVe^+0FUHCi#GZx{>xYabcs_9nTOPtAj3)A~1?)1%q(|%c%+}k z_8i}GnNsNEm=&|krYx&g%Q?E1FtfTZ}sWCIAl!gqfK-bj(E8z;83^#C!pB|e}9>b zxO9@&$&6d>`T3F47o6tO<)wnY4C$nYK%RVr0NAuv3!n&5wtFm-D%+c5=wQX^H>W77^JOi&V4;i@bL z-x${FT2vWVllWj@tY_8xI=)-woj5tD6`|}-oau=bUixu_%<#w_dR-QBc&;L63eBT& zXH2aLvcqVE#%0>ly*QFhUch=u`YRclgdjVeWbQ#nW>a>5DLk}<%ANHLuCi&`f+HQPA(W;# z(YEzW%g54BmWAe_Nt!BjFTQB+Rs-A9j=6wnjhQ!374Iq9!r6x6+ zgb+s72Zz$&m=I)B-IPHBfys&FJ3m)uL=UU@g{1Cxcba(WRZe#cX%6P4Oqcd*mKw-U z74!_2+2|MGNM;JHem9T^eUq|~JpWkTT9(M2{gzs|@xpsJB1)>yPu?V&nzqed2TpH2 z+2tjr(0bI>@1b_XEecfuV-vN?Sm8<>$)m{lFgA`-WWlmWMrV`Qz8@<5^Lly+(36l@ zTRJr~FNYd9M6%(ld33cA=xw!CCOX;1xP*Sqj{H7{t?!^D9LY#aVJK&?rD4W{nD2a- zw0k|L++(|xLsiOH3-{Twshtg%l&Or%^c+3;mk;dBda~2)UFh%VtQ@Z-O_H(!QKBH} zBsqxRL$Ee&DY`>+4K>I`%BQn(BOMqnHZreB`KY`|%~~JosT^dQdV*$^|DeV;R(5}W zZb}gYvnSRJO4kkwU7pt0O?4v14BH})V-85huB7Zh=s8iYXy z^U|R49zqaO6gO7E_q;F?Ij&DxQpv^&Zsj_!=_9XvYi{)^iM$j7xdCjK?MJRoe)Q~& zJ*j}9Z!Z7}>U$NGpfC_(aE*bPyyWkmlEU$$RJHjQ6)1C{kNoNDS*<-Sb92YtOCOE2 z6Id;i&lu6{J*2iBv4(5#>#~E5M8;C*bKWv1DAUkPE{;HNu07~#r#aAaF;88Y&V4?V z=appAgKKq_|J@1NV4AUsGSg?;9I z>0ZC}MsIc>DK9d#>q^rV9P}ZTJ~(cb(6Cpp>(|(Gbsu7DoRh1HOGb9Nro_F*$5r5i zwG@J|$Yj%BNTgFNE0g=0`^=D?G4La@5=i;9`yM{trJKdy4cew1^sKxy726?w*T~tD zZQL?F`K(~TpR@6D2{xd4E>J@vmVWt-?|ID;S1n{6ejC%PW74aKEJad1RqA(@gu&GH z=K=c}^P_X5W!rky5S&iTRAhg7wwHK-7O{lh-z1ZOIM9_`XQX(C-d>TO-F7K=cGg+n zPNt1fX3C#X5~b)EeH~rlp{J~7 zU~V}Fl#JzA(Qnrnw#4O4DJa>bPP!fRvck4XP(qNbt1tgN%&iTpMGdv(Gd1n#$icRuS6!Huv~YG)qIf zNPZfG@&mnI^%ZHxECe3eND85h#j+lw=Gt?-pf>s|dRq68EGH)3-Jv#|YttVq8yqZ> z-%j7mTLV<94vdj#9fcWYExE6ulqNsnvC^`=DgR9+eEOG2%wfi=`YesKNnBd9k!AiOSyo%fSvag;smk*-0D50+#<$nr?K zLMiJ$=B1_3P@iWlK!8VqVs{ zv-K{!y!?&jh)w3blPlPH*O``~+gBRaxd-C8j^ucLq9=kX{a`60IwM<6ze=ARQFPa< z+i^9`1k5;|Y83Tf5;mJlLA$Y}EH5wX;E zg5|(0SMotLSDbH|po|9<3|w{#K-Bl#choJH&ggN^orP2M%u=Le({r97jp=FICbIi8 zXYmJv?90`HI!l6${WvgV()9G$Y)&gnq|_5D4KwW-QJ7Y&agTejY;{dh{m)x!gPpb1 z>oVLEu#$!;;Vtg5=PqZcm5={PsPj{xw>2w7RKrXU|)?z+=ucA!BLCByI!yK1 z-)dtW4T%3?`8XNq{QnhWHZs;4}Ji6O+8E32xUW6kilwmfd z1|C{8%pWTad)i%)(Jb2INX=#GsHk4QbH5y&2%fKd4k?JLHJ_{PzEe&Q2Q#%t4-QonBMtRG) zx+_KZEzz1!me)?vRr&v_Uo{t9+;D`t?2d8fCFG&@Z8znKSbk^W`6#xM@%6@A!;kZc zw+l-K0wi8KngIUEucv)in&tdee7{+?<@^q%8&&u<>s(8_a5wJZckF+61S`2S#j{1X z$f&vSyw9JoHmy~*4#bXFgZ)*uQn6=vw%&fOE#CMC_%b(!zwvssJiYZwc{f%5#-pCW zQv4X~jrf<+@$S0&%R%+|W;0axO%JvZj_)ldC)x&G zvYC616B%{dnI!%v*j&)F-6W6GBCCS0o5+n;-mKgOfsPyXF%0iBmeEmsATn+1*7 zH~Kx%^_>Adv1z_54M}oowp$jjHndrN7_qV-K&#=`^nonP`(Fn;^mRG_aFkxgU(VL&6FIUJHCzKJKry;WN%mr1Px_c)QIH-j`&*$#C6N~p%thpm7IADi_HRlV4unB*EtQ#T@qKB~D4n^Z&TQ`BXJ zrEk1MC17*amg1z2wxQ{ajaiyYQpZ7CzH*`*taQO;Ky-eHx0bhP95FIw?_ zr@alg{JIp;6Er-kD|W&1SoEKq^R}LVSrgAcEOVEFnkH`4(OjB^D?l5Ec@#-bk+4CP zQ=~_;&*YJ;Sf4FlR`c$olkkCOyRz22baFO^+fs;|Pt{#0qI%jPpSAtuY=mjJ@&14- zOh%oty7Z{D6@A3Tm6p%tOM4>T(=I}iV=a4vS*6O$88lT(|sGjBLZEDU)Q9a*Rz5dh(K#ncPgt_6se=IWxR z4T)6~O}fe1cTOysA2!~y4jqZze)J|sXuwmTsh3>It5IW{uRJ|H!bmAW@}?2V7Edig zkZTrtm^zy2E zNzRVXyV2$!c;{;pq*#dQkxdUmt6g;~>OL>u-F@%@aZUK1<}YiL9aZSNJf(1>^RJ(k z&~g-}6K)J!UGmdsFEOxFDea}a(lYDMw)kRl9lGg)6nZfZcovb27lKzBIu))myk_ym z&*+QTjKCaH{M-mj1ihrKZq;WWZ2GKUqu7^G7>@hnp*t*B*NJLsLsMYL^KowGqxd$q z>s0wmEjU7#u1J29WS->5tDf=EdaBftcB7P>+o=W>oObD!ZklfZ{TcliPk$;aG0H#o zQi+hh>B0Olm9~rAqv$2#i7T(PeFI=^r_y$r zzrH>ogYF_e=RQ-k!(kP!^*)QVnNrEidu;K@;@ahBH$3M29#)yo*-i|jdK_+$gkAR@ zy%y#S@~DI-ea(^eoWZuZA_+^F%#wO5yr#A~T`-wi#xxx<cDodvuL;Y3 z>CgglxtzWpovE>UX=nfTwv%LczQ4cD+MaukBv*14>A43l8%78*izVk1iakR6hF3sm z4);vI!;hAO_&hJSs{ZAfdW!Xn=U8vlxR3a+teQV*0S~A*5?U`58;GY>oOj50QbW0w zAsgOqeN$)sr#e509je>Y3)LgN{JPI-z>-&HQ641#ePUs+p-( zBtMo=QF5PZADA`{?({r||zeq?)aAG*1b zY;0N9(bktiG7b~CWIEpNuN;_lDR(1ZLzMOuHw+KWjuj_qdD=~lS(p4%1n)J5bl0-I zB`V@LI|OZp=YrduO|^EoU?rh$!+8=(u&SX*S8MKTGuzQwC1D7&miR*^yxF?_oJ5=T z($vFd&h6VSm}{uexkVS96C+`bFP*fK6K*ZCX%O3I5O({@m3y0CI`iS{gzp)Zh z-sRMmgcZBFM?RPe#i5$}7L*oWzOl~8rcF$=JeseDNu%ABP&8f=T z)>fz09CPin1I#9!4s}BCm9NMqxJp^Xlya}CuR5n|H;|;Jc0YwCA(ZR6@Qm}B$o<5=kwlj3$3+Pc1cm!68!6i zbC4X+@s>WV8ASx;;oh6K@z%mY2cWH-LiG_RtUKbJZ=O89E1Vd^qg6E8E>Bm^;5>7q zTL_uaq>k(jAmVu1}gtFVMI<`O)&C=wGF!@FukYwL6O*tAhWhJ){&=<*g8 zI|o#-CX3Tr9tZ|09{GDE{@wrvN_Va~Qi$3W+dPLoifrsX`DvIbB&clgbRj{)kRFL= zL_9Mq?Otg}k2-<4+R;~EY28jm!e}ZpzauHZ4u7_$^*KJhfr%q{on2>1g!)}1mUR6J zqiKG`+7Q%^r8nnyrDP8$l{BVt@cwSP`s}#yF8`_N>}?rH_x{P1miPTb%n@I*umQCi zIHID_!&ch5vc5!RCYt5CQb*xNZd_YSXO2uXby+5UD#ox)%5@4poOQ^r0yIXW-C@-f z^^I6b=HL=a-wxHstuDwq(ZFbFI{s50mzc>RfgeZFZLmf z58K-ww07U=>mojF9d*XIoB2$A+a2Wyzg9=r*sY})gB_`7C-yh)bTaShiN2j_Gco-x zrM-(*r2)aZoM>CO)PE~_IsQQbH5mfck3_iIc_w6cww3W+)*EHx27(5pk7(|rm#`VG z27Ssghe4m|(qBux9D;VSE$v=ycnv;Zrhr1_d-2Ravli!IgMVF`zg?oSRNaHIzl)${ z8RzNPS+=Q%@H`xZK#>Fe?(_e*;-TdKNLU6))^Vnu)oe4zf7i}*G-?e=XWN9`D#Ogq zzZ3?B?1%{Hb~&yrMDPAZ;>gv92%59{eB{^xjr$xV#jc+DY`vh72~z+0+hX5_WIrR5 z{~HGakk=OP60iB|LWU*wdx0bDR7i~E0vGqg3rmjwzk6}i06s7eh&8&n1f1JkXK$Kv zNw@D@ddGT{+*OeHDDG{u7aZ#qnlbwX`28=8n`&dWq{}j?ZHnyLj0GtBf%EchXqCLq zKbAg=o^ES#5#6E|~Ey&q3;XYwYk%xJS8x zat&nZ=SwDt<36d-HMh>E;NNSNWp+50Qs~yV$x@G_#m#)>}&ar3U>~ zK|}~MMVPgBoYZ)qN7V2+owXr(CB-l3aLvEF+?LfvW0(>{-o)3yHkLlJWQ}1tPo-IE!Qx?1mTw!FE$_tDhDKdUDtS z-ifG#O75s-PV2&7TT6G05GsvXe{M#{xWY_R1OURHf!)a*j_~(ekRdT>iG*Rxj;FQp znXT(e)JBJV9T7E~a*H&zm9~5Om40wvdoD^xX}hJwwv?Jz)~9LGS@M<-HP2UWJ=$1? zHOpCR*i)90?~Kb2x%iysZ>$$)7O0y)TO_~lKv3mpB^iO;^4l1?qV;MqHTh_RCLD1$ z@=?)^3&jJP#FMHzcBt&l$?`b!QJo4n;v$6;6;rBN+K*dy4^IuP?Ry7DsIIRwDcOAO zas^G@-Pkgj5SrKLsKdA{y+Hml3~Xri$2Txmi^*XdduJBD6-!%X81lutUG-jvo<8xV zj_f_%Ug(>B&|qQU>9y4qIqm<6i@0ivZMlD~@KOAg&?6UezdpYLnkv>s)(0#~k(l zN)B$r`BGu;cEzZ`JWLC0cv@-anw;I+@f$)#0~%l8xSjZ`?1ba{L=b;oyjh3tdwfDcubLpI>!0U6 z`0h#lKuZpOL&@m*$o`W5>yySppy{cwN874>w*EcoA-mt)MN=9=drF-hs>HYX00|Sf zR#UcZ8i2Pd=z|6Xq`03 z8II`9FQ~Yyva!vFe0^r(dOah-*5bKfwu`#@S1lJGg#AQIb-7}~9kP_W4SC|MT&|Bz zn~zm{PGfk~Kaj>o6A8 zH6^re0A0W%G2FtEq{91e)$cy{9m3U5+>WV($mfM|aP?JjV^ND5bP}dDptXgQBNiEV zX@VoY+;xfBkQa$t=^4bT{#mb=`Ui*%tEmHFJei5N?Xsm7RnvbEv8i5lTki~e3X8m{ zr6ai&@q993kHiO&DT-A}EtsN+(Ko~z^Q@znb|}m!Y{lw%zkT((={dV@T+FefOV7@g z*OhKF`)R9>;=~8?t)mlri8Jq*=PpC{D&07v2AHM)9N&S`#g5+&kX_Oeq?LMR_LIcy z(rl}KN>yKuiKqehc<3PcqtYt31A1Z9-pE71JPXw9wXLnZ`>o=MPb5#+;E8FV^J{kg zl6*v4-_{;_p|YSes_3|rWz(}ye~bm3>X-@;?YgUZ5g6^glvS}+rgvjRy^{JoZL+Vp z^X4uC#erhgNn_9y&l!IcGiEOjcg6LsZVi+8xKz07(Ry@D<#c%c@i=7O8bgQEGSlTS zIAU%#eVN*0X`wL7ICaJ|&yq*oXSrL+QC;Gait*y4b>bS!vv(><_|zXq&A2$ay|{*; zJ})VleS$44ndNxpKWT*G(6OvE=Z@uT$Y=l@z+R+2GBZ%ecfb{!9x z^vn;!^P?7N*0$AJaV(#H#JpzpM@Gh~Pnpbn|LDIL!MXz+nlf2=q<>fG2jipU>{0bq zhDZ6+w4^_uoi}6d3dG%bd};q(skylOP$kCXkh$6~63USn9y9k1|Ib%pTlOne<{5m- zYaPkY$`L5oTFLNM8gBJkl4R%#M^Fr`lX%lrS?AXn9_4*1%_wk_xo{!bPi5FOEcquT zSY6TmUW zxUmf7yp8jrA^fKW$1#b#Uh=fio0N5>1Y~5L$)z%A&92;1N7X@|DNl2rb6q8O@l!`r zY=W?Rev!c4g(G}~eNw`>>i~gR3Rsn`rZneX8?f<%Bb>vy78;Z27H8 z!yXMb66RRAGvPwX4e4l2H=BKnoUy%Y9sqTd6AO^*9+VfB)bMU)x0&GVHyBX|@h?hd z{iqwWsHlUPUP-qKOK3SG`5PoyZfnh1OsPhIL@u;)f8DLr`1X)VV1=$$dc;5{ca`UL zU|O)^ewlt+TlEUc>60?_m&=e>>MKt$#w31WPBLm zd_&wY#S^ke=|=uXtpq~4y zr{(obKC=5UVoPOs5bXteT*Sw(d1 zuzqvgkI42@aS7Pudb;u+J|8a*I{cN=mq3r>YU(x*>d7u>TQ#NZN{N-Yp;HtIVP^?D zUgOZV;%QtPzz%`8IemVC!KlLf(c`)=nVFk3m2kAl(>@n;O2?_${fd?nt?q$6xA+9m z-8j2fzhfUbnfmcSLRg7+YjKJ^Ba-8(5|))(+U>qIk&u*9c6yCgNOG)vqZiKnXsZQ% zx6*L!1yGAF&n~h@N_T8SQ{{?jp8gxIThvgp6_2r6?NTK8SQ(Ego3>ouyNhXY)vaAq z8f3tjSkmVV0GYAFy^P_JyJvDE+9cyz>idkWu`EYkTuKM;O94^RsmbPt7Z1RQC z239|L&BQ7I7R4{AxP$YVhY_bCBjWpikyub4K#RO^NB;C@9;f9SWX>MlY5R$*d%UN! zZSLCr`~--~zjhP8utY7(t$;=(Gy#afZfA0SPIv#40woE)yzN$++MKVW2xYi`t5E&; zuR7gtIjbmMC)d%|1=nFuv8vC&2PU`vxk_-d9)eyj#kAKMV_b?avQ&iLyf$DIg&BA~ zcM^U?d|lb|V667L#Qm2xq4y^*-=B~TA2_ZE4RpUK0C{RSH=Po^;I7xg2VPZ}3wfJ; zEK+Z~_!u`xM)`)O`dhz&wa4L80{uW2w$Xw%C=A?A1qNyue>I3iR`2uv~ zy}t!}EE5{ptWF5zF=W2T_1n)%61mq9wlJ9?6m~!7XK<+5!kC+0X`wJvE@8;J9_?~v zr_zlJJA4ouo`#m}=L{o@PH$SAqIaFLV}9GIJKo${^VY($NMXvw)y|-DFWxr8f00w% zCH+Rr=sNd&PaHMwO=;!%*E+jE97o=nt$$m8PnYa6X8YcF*we{U4v}`>0s~RMi4*t> z@dddz7dZ~stjKIc28_FVvrrk4$r2#MT}tzVYUzhfFolt%ikI$zAXq(k{CBY0@4MXl zI_lnA(BLvA8`xWI7eetuT>a1c#E-*PfArkpGeGE9j8$KTc{#WxoeUZ&z>F~I8Be=T zX<6k5g2pE>n5w!^_31iXd;#(!o5GPm@aU+#)G1u~UeBpGC3N$S7;RuB&AMLz)@K8qo2}7rSC{(J#u_!GP>nP zo6N?B$Bmf29Pe+AymGxH^ta)qs561M!$r}w^Dbd~6M!+jtqa|Hun$mFJ+PoONre*) zPXFH`7+Z+54zl^(cw&bp)kJ6#AH$`HzYNW*v=`nNtzbdDf$%KHr^Z;_5>w^lHNc>(NJMXLd>b zQxhWAhep>e++Aquoc66g3rEOTpZx`racMCS|1Y81 ze^b43Y3IGA*)O|mq7*q@A)Z_xaAfMQMb@c?4=5H?+$&M~fpz{YgP!)@g!lsLi;P3` z+)+vW`w^I(>NunmqI}f1CEHkHSioa%K_7Yjl{M;?tyQr--NAjIre9j)iY94@nW>S$ zh8nnOZzW?rBYZHcYsA7?6=y%a^!i>21Ib~;hm1VW@&5SdAN6Qun|qrD(7Ajn3iNg2 z^+Tq22_X^8UIuiifr_)?jS&5ZX(htJR&1Pgxd8ksOYVdaW0;W5*YfN-4}Q({sbrdo zceQk6cU88~gw2^pOTY{2^!GKhP)`rZIg6pg&k*@ zu7%@E%IU#6zz@etrlCROmmK-rDTFXy(bB|F*xQhh!`JfYglUJ(&6G?-WM#2KOd`sf z2RV4w8;C^E_`)MYRLdZV8eohLAb_F!qbw(j3xn`SM6`&BplU? z=zm0o>Y0Xrg47x32;iNq+^DgGHwE2OZ{3BXI)kOCKz>xv1sz^gkjk3?yZ|nj8Y6() z6--G^AQL$`#i*I`&4MaGc9f$#x19}!L(i}vT~5A(kggo@hp0g2IdExugJa&(0j=;9 zXG6d7X5cd2TF6B$aU6z#r5PB4a3dYFi%|#VyW5Sx z;LfQ262NL(%UftP!&<1CUDG}S<}>yOI`d!g7Wzh?F)iuyZU;9?n?QxWsZ8TWJr7*R zuVLP^7LIDk&kZLw3A#NUW@NR~_%$I{N9{yXadf)%O>wBeeeXPlqjD-039NU}ic;B2 z!cjYo3a2#ignm$9fqoj_Rha!`s(_y&&lYqO@q#Jg^5lF$rd${kJohY(OB}O@=2vi@ z2+PNLk^=j9C~}UycVE~?)VXv)Gt)va&PLe>eY6*3k2lSrGPVk}-_@{>5}Sp?_RM=j zrr4>d9vd6f!4dp0q7|wqDO!y)IFz(KJXNGxoz(?7KplOzRXxR@q7vJ5fc7sYLRNG@ z-!S2F?03*NtneX-z~lze5@>l&e=RCOq-Ey%F4Q}26lXWkSOqzEN|H^i=H&uHT~3jl zSR8kap^dN1GWekgMZ=og1iNy!nuL@UvYcJT+bd}>b77qH%beFryr_$WEAv zN`%sYCEOGoUbA5MBuSa7*EyDf6A{7PRI>+QNC9 z-jd#H^te%L7PF9kthhu&Hgh84}GF} zGu=n1xIGuA(U^7y#Jg=;8XDUER zS0?;`lIBwEe~)iMpAWrEK}}SazFMui>8sr6ehW7x-?*B*$>%cZl#@u*0ph?&%Efub zfQ0fJu`wp!4=N1PP^ujK09Ih2ttmee6VN5C_~P7~qJO1Fr%hZ@!G0UU@+-Laz{n0q zd$3j&{7BfMU)Fvv-+bgcY?^ctw#*wk9+J4a-|yn25mHiguSk&m$t~qhbM1O}`^DPSeHvSy1 zXz}T7-^(M9Th8QD)`dl~Rfg;8vHPA(3p)ZFmL>lFRia(LM*<2|0@xi4sl0&|t2y?a zTyyBt;1r>(vWu`qm9$psAL;iYZCv63-Br*wrms67!!1+Hg%8Z27XAL)cPUv58u#t) z?vJzy+o+g!L-Ev6VQX8M{)^&;mjF|SEX~Hew3NSwuyX|R1)!m2;ktu`K z;jyt<+uG6>w4xnC^fg%Enp6dOvExg|CWL`rl_W(vrWln}?34hlv z{+3!1U|-93_xFW(-imM1>mAUHDvx3Ks;n79T0D^)!NNZIPYzd_e0|EQzgNIPLsl90re^l8u+r_1K-pO z@Q;rpF|kYt7&tqOJ1ML!cKWebnt<}iz;pDxUg3Z#R+j8cpS1VwR`@3It|Yc$HMuAU z5)i?1S=y5^29U%!8c*)N?| znv@rzUfRo7Q`}P=Ha(F3ULPYS`wEL;J-XH7HT}+#+g*kU+GUOo-_i!C169|fJ*!@vtWf5(j(pGWJJX0q6LJb!Fk<#u79Yo#Bi1SnUzD%oCU zmV3V1+kETm&F0j0+Z6M%e;`f)>~^V;fSiUQWdGC;cLbt;jcL|Pr&5z4rq+6N)$FNn zDWfhOnI{w4#EpU_(O!qI0GpmHyRYtt+rhyUni`Ql(G+T*XEY3C6}hdcEf+U^di5WD z0EZpd>?5#Z$HqTN(+P9?NeS88@nfcWJsc49ilOTi&2*9D$7IkW!DCOp;Dsvx>U&QFL8@W-Jo)`7Y-QM&K>!YqfrA4IQ zR+$OR^K}9d1sjPmg>D0pd&hUWjQ;BGLGPzQtN~F8qbmwmuf6AshpYef5~4hLmHrlT z`X)t25(o$PdI?a$A>iu6ztd@Ke>-0&H2b_xAV`uS+FCeS#H?HW4LbqR8pL?R zrr*IpJxLL?Dxap-;yBv0g9ZYkV&TkHm3=TEP0N0hbN`W2VCSuzeRqJ?%jYUCJqOj%!5_2k z!;c57b7=)YFKE<+Ay`s0r1o1&I^}1%E3-d&OgMe9HnromH_Nk3^T)LL;RRq5u?m2- zvXc0o4sKmpo7r4SGX)9zQh2`x=vPWS&+(-Y;qx{xf7> zs!a2s(m9LYsDC#`BLMqAW9QWPXYza% z&YzJ%+aQPAKoa+ZfajQyVJTtgnIfymSj9A#L9b!jt~P4?k4GAMx)12Gj_!FA2+SX7 zeeFG88vM}6fVF_tZ2{(TDu4=VF0fL$`$*qfsn4Z>r%0Q`0otYKq6+qhHfE?|aA1|b z7Bj>Uc$%8Vw%rZN!oGxW8X0IV?3(NZjfNsh+CuRi)-wmv&+q3%2(U$sj*xMpJ<8|^ zGT# zA;n38rfCw}6rD0(9Q?q_ z)6Gb+82G@5W%`}gk)goxmmuO$c@E!jrrrz10f@_ZT~WYR(qg~ifFkSrX|BG4q}+)b zioR!sG?uY7WV&eZX|Z7EH*7^kVVZ4NL#gzv+JI$r%vMVcJQe%(r|$*E$<+4p5D@o4 zvNGJ1za`^TfuQT)WF>D#bC1fOu?~~{6#8D(?H>B%;yl7Em#&_eQj~j`g zKH3R#N#s-W!?pqaaYkeZo{|D$Yxg$)r^4zTP2PlfALQKn6o@xp9>|Hx1F;&@ z?*;Qu+9fUsnLs(a^8FU&#ebIy-(dU7>_hcY@zRgOwby=SV;bZbPj~)U*H;FvgUr39 z3^r^6ZK(d`>j3av-EMoCnnk(KmD&&BL>LKscG2Z~-(9&DGABkelsS;Rsc3b{S8(T5 z?~cE#1cjwpct=T`^^e6CsS#)@lmpB2SLh5bCAdA?y{|dx>%ig&)(3N~|5jzW177#I zCN5>AVS>s{^Q+w=^jO{`_23y%XL#?L&Ay!D^L^fbgRMiKIwtxWK$RSYl~7SbRG zo3s0eF|XV?&}wO~yzmPp=`$i*lh8Aj(*avbTU0m<36>q%A!_edQ|g=2bmOq6Ai7$5a(dPc$8dlGULGMjHlR=P1K3@R0AV2;0DVZVuO&ZL zPM;PatPJ6p3__||VY~vHQWYOzE7nqhpP+1mo@Q1A`7rqb!?e}_;nAqI#)BwuzB``_ z0VloL0^am6t6VreOjnq)Cx#)xPkFRGXUBV!c%HNp#984LU3M4`q8C`m7ATOIuL4?x zGh;nS4uA%QSpr@Ep-<(gnRSL#fud!;5w`$V^wTiWtEaAt7klRpnCJbAYa-lGpD`)P z=MFXE^~o@}N7J_j(Lo(k~wlm);j zqO1U7bKCnrFY_~`IVc`_a~69OQi zIfe*86ut$Jn!)1~0A-j1phX~>5qW_qf^-0oXP~i?ZBslg$xh%6L%K?}IFf8O_#{Ys zYb<;KMhd}&wU4k_%T;)K&5?{eevQMj?jl!yjRC+`x1|6M`X4_>1&#oI>dpe9i6RUU z2T-Jfmtd?bbfwQ>ssL_+0GLlM7_40kBvcv6MkUfkv($lssB$2gK>jj#+q|ON(+7u| zkrknUU728deIpSzBx1hO0#FaB0yH@HfXHkF;30&uqrws>w4&bwpi&JSI&@A}^b~V1 zW3oV~b($*Z<}~t;R)Vkk^Y}-JJCrP05IN#=;dm6vX10YS%%4XWZKotjnFIl7=a|bu z>{XPA+$2)YkUanps~TC7WE(Ah&B0HlDCt-*Z^3QAL7NSLI2p^k{ZjE@VSw&~LfGfl=1E^ZS)w5s4SVM~^$_7OIPQb2|ZLrwW1wx}FIw*)}1yJH7^Ig*|-+2yFfCHuvJ0tbKrLm5D$X2NDq zFa;URH6>AoYG;F~yix$^Q1KAqXxY|F!Zv%9K2XICJ1X=hFj~>QbO5c+r6mpkMK}WL z?BNGM-erIUXA|JB0+kH7LVi%4lmrt3zKH3?SN1^e5np3#a)t3gb1J01Kba z4C4b(H)d)Fus?qwp_(28Ser&HFfN&CfH9^6Ql4kPB%U2$65?|(39%y_7|CrBlHOnx zARJA12hVY`!Iw>t=`sO;8v&I2w{+ROs0`gHNtWvfQNST$=kdmh@i8JR7sclS#|#Ua zeI)b=ofVpQ%zZFZ=0)|KC+c@U^S2fP#C%L0u*146&5a7W1ZJ7_25-bQA|@2Ud;)OmE$KeDA2xc73`W-xyQ` z)x_FUo?qH4-$<+oQS6h|O*ic4PUKDYRayz}DnX|^9f}tJD7O-|7gp(Ru>~pjKx4%L zCMeu8!VX$o_5Zq~s$wJetPlw#M=`EdYKV6*`IdK%OlL#01vT~y~pq!|)}E%GKWcW;5-Qz8y` zhg@9t-vsOFK$9$JGJq;7zDud>eb4K08_1kro|riFyY;bCPl3mY>L$m8G+D(;D}Zps z^_GHyCQt0Y$+G6$IO$~jLR-ZXv{lR{3{fGjKc>!aYy+(!Pg`j~90(oPtHt#3`4({z ztOXRK=Hw5A1U2pM2G259Hf#|v%I_RmJ5DZ>B_>3qDHd?O!81VWf(88bbG|ZvHgEp) z-7g7vtx$zkx|T=|7Az`|HARUyW~ue+hl@&W;`;+*1i+u@k&FZ-A^^ug3I3my*|Bko zd8)c;9!v}_Ucg_q?6)ZXC}+0f*Ld!N_b0RRZ^n1!0VLBZjBgCRx#=6$p6jXtdV$cY z{T9K+SRl040kGa6uMKG+2um$Vz4THHCnI>w!OkQr~IJE0< z=szaAi7AL3boWJt8G|RL@b;!?dmn!3Hx2Pa{q1{&K715k6>tr}*?YY;0Gs`8kN%TY zSnV=kTiu`?b;^@^9_0u${}QxczG34-?Ot zQlUEFX4;8qjJ6M&+Fi8xzg2)07hfD#=+jbwk^>eiYTz{#fNs{0?eJbfC;|M#F-S9@ z7QS&C;7)Ph@84R=54WLd5oswXA44#*Mj3hvZ4c!BvC?acq45!E^9LP*i-83yH0%T2 zKG>rF`W7WFMFA+CwItxxDNoI=FaR)C4z2ky^&9=j5*f6BV+)MjIG`(0i8$%%^i3o0 zelA2uq|xP`YXKdClM(lN6GD57e@uN@|D5Xve-G3U2sMoWlztM4*^;qU@f#a-HiMS{ z(A$|MfDdI31Fb{^=Q}Pg`%$o%fKCv>Qjs76UBJ=QHQjr?TaW`%-&)lq&HxJR&>|a9o2JnaIi<=@fSc%ZNPYSr4A*z2X^9^j0rVnCSc6_vHAk^d|Fv|J_g&g+K{yEhb6oqqe^=0RS_OE_4xi~@Ry%A3>K?Z~ zHo~NZncw~!kA^m@SKfNtn~4={h2wDs%*M4%({w)r1i|Fr5QKADRo=Akthfv`%@yxQ zopUdXGNVP+DL(%jZDjCeRPOobkBNQV1zkzK`BW$*%Ia_Z@6Rst@ZRAgYqji|vv?K? zO&KiBfSIpz-p_p(4APDA?1RtSm#_`?%^OjIeNQrX{`Y5-&zQhx+m(D_K`2~GVYK;5 zGYge0ezJHssE`e1ACxkj}9TWq-kLSFsf@`ME+L3&@9CXV4LTIa~4NlwXPFqy)0D+v?l3a@s*1|v|!tA&yRJOz?Ls7NoKM% zE1Dkr|Gnx`Kosble;(k@ivwA@W8MJrdbq(!;)l|IjSN+I0`6J3Y35@+3xfBh#xNh5 zP>X%n|4IC+s1*l3i<|C~%C6KRhxnF7U2fP`tN7c6e}%?upo;6d!zcA61qGjtYbyi9 ziN5+zH$AyBZv^|E`>hK0-J-G+p4)1H0fDpUKgTE$_0oh3oz?Xn>)Df!ud+v#NqR)C z6D*}2v$7#S70Y_o0%u)WS-JKE>%9_OWIJT=o5>xG$n-Dx0- zX6k?F-nEqRKdh?9s5dp*ZBJ-#zFyjNchQ@@e@_gffY@hSVDm}MAyXT@rqdoR z>*S)14l3G2UjC{$MOF8+rQO80`~7|W+cp5$Z#mdeo74ws52u!Eh_u5uYXIFbU7A|B zJ_v6LrSVqZNnXQFmcqmLs4J&(X@^CZZqJLB$H*ZI#jBv#hXQe<#avhZAV7)gJ%Uu zfkEql$h#T3AeDz!B8$kBP#S(Tzd~!fAfF96d^+nTU~O=?am~jf0%P^Oe&T}Mf~Ho$ z?dk2pm2@i){N!U=t6+1y?&!;FUZ);09H)R}bEn)%{>_ylrN7&o)qA*uLr+)94b<&Q zDJrS_touSJD1^se+)fy2!BIzoxpGeF{wOgS4_EC;^J=}6 zJ&3M#B_I@+DI$01x>fpQSdh`=(QN_(xz~1DXr43l7LOJG6EQUHWP%eyiM!|+QX9Li z_Rma4xNBX`_hjt6{yey2`x|(^R;*9*vjkvfsjSqycuS#ZC3u7AS-}eXDJ+d8emO-1 zMBmDJ`;Zn*J%Y5ZHn9O*{z1AQzG`&Div9*7M^3!waxuh_u;_4m{8K}hhJCzmTZw~H z=*ZZMFK36?G5ka#)C|)7Go$2;;KU$Q*V`{z0Zopu6D15V-8VS&dj@Cxfc_(#Lrw{0 zsnN1k(63I9xV9^)BfXjTM1J0>czbnjUX5Ry_asd+s=b9O7UT`Ak3B}`hqZ~))e94vkJW%HI6Ec$-8os> zra)7#4!lG}`(m|uw4`GdGyIoxSW($ut{sqBZZA7Jg= zN+ZuqO`rJ_B-$oec_+B}QbK$J#8^!@s-*qQbu0Q=gdW#%5Uc{cgE-ta+z&^LdZR%T zAonR;I|_ z_oT|(Sn=+vPa@fULxD|AJI1TobpW0Nzg&Xdy&Co zlQ`G>f|~QJJlMZ;+s_L{zM|%fW~~y$u5_%Ha{0ey=RH>b!?L`#z>sWWOrPO_&B^55aYPk(A`{zRRtsEB~n+=LT7LeStTnX0a^_~9P#x9rBBQNE#`Uk<7t)wOa zH*t#wHhuvs$lZi2&z#4?@}SO+@=5DPgZ5ZAmmHr4EwsAmC5>9@`&=m{xA}bXBt(>O z@hRGYyW!&1OH`()h|3TOdN1FwS5wRH5NriW-#VuyEFHEgt-N-hq82CTN5IlnK#hoi z0-q9KD8vXvMc(=|ZT8d?i*DnV=xs`!%0PRG;kXmVrr|-lG&{M!$pGWE4m*6%6k#-y z`<|D10n}e4?4w}A&Fk}!D_8GZB;m^A?FF@O4GgeK=ic<|LBrZTHQM5iX%9dLfJ~YX zjF#a?vr%d0AA|{YQ5IxtpC>dD2a!Km^y+9xskSLXRo71XIBmU-XLnIZ9|bfOK+RH`Mn~5gdbx8s&Dr@W`6)8*9DF|l?x04>`B9B3)XKx- zkfr3nI^Wng#yVsXg!zu?(yd-|%lv{?Yp%?r4`fl@a>OsJ*{jWbsI~h6@emdS`jrAa zw*!s7zjHOo_F;aCJ{A3V+rPSG9jKzpqef_{ftnWD-P~9CBtieidbQ4(200Y3cr|R^ zNj~Y*Pp)yUeE-%Q)Q|$^kK(WP~A5PN(w9Tmn5%9f5Lg zf=ZQbmUg={0#16l>BXZyhoA2|pg;Y3)>#Tw^Hsz8_qptSv3?Ov>dn{P3Eas`bN-JbNXnEw_7@h{lbp*c zukLT#1;+QNtAi5edcWyhLkuOkXyY)%{g}R3sKUJHcK1Bz)7gz)C~0>;M~Za(3NBUA}=! zxZq}StR_PTKN*m`IUEv8gA%K5v%SI$;ufsFVdPFMkcF}pdvVe`f09AUnk z0&8-(+_{Z?qjD+E>ds^bgc-icb^#~h-lr|oQZ7wN>+Hy0`TU=)*4B}iNbwRiET+tL z%ke82ZH#Yoh6HO{b}eP(IMaCXJotpq-~zya#&B||D`i%DtLnVw9FU)FR$bdfZtuR8 z2_r{m*J9gUp|*u;x^u}+g%PAP1*4JI8BK^uJ&b*=M~H8_JqA_pl+>H((x8*@F~9+C zb&g`ohPMxF`ttnC%uf>+oe*+UAc8%@YBem2^yZZ(65&913?|ZDl5>69Hx4n1CP(7&md^{6t{h?Bo{g(a?{y?G?J>z$Z z&>hvV^y2JFYMbYMCYswYO(hn=7Z4s@6&RBD&Mm!=b)cvPPhEikd;Pyei)W&GktE+> z1p@y%SC``y_Y?72kCrRpEE3GJ{hgU$@W6@XJf052>17j_BSAv+@KwqBqrgQ8Z*Que zI^u>LOc{Ee=-L|4_XJ8g1z!Y^3 zV?h%KJ#qE8@!JrAi%`oV1M$WS-w8y#rU6@Sd$T;RXgm-5)KHxj5@JG5 zMZj)&av6*uPusK!+!GLw3+{pYS?5CBZ#}xmWNuS3>jnoN<1BJM`w`_FP2>MGp&76$ zu-Y31Gz#MiKZoFbf4sz+JM!c?QB6#L7vY5II>q8EBH~sUo^Gj6nlz&_y>5k|M=1Lu zVh6ex$M6H1E8hblG1^OweGtNEEhLOqD+YSq4UwFkyXAkD4Z-;f2c3S&mu+uptMA$& z3euPIi;sW?WV`d9&iWj<{ELMoq~o|WBxHqP722Y1=D4g#YjB{tq$jTifvOge zCdm9Ln)ImZxt~6hzp{d)Gs=ZCJz_M`L#xZ6*73ilAt4o+@6@cjL+OnNSo1vy^EO6& zF5HEvXi;|1E4*PnxL8T-LKZHqq(_(yCaK25o5TSIYdF|H+Yo}AG^!Bxh(Mwr$e=P| zj(x|rwS+P2Dow26ikHz!J5#YD9)iVcmHe8;P}?6c|DoeP3={f>6?yE6J3{Ncvn{S0 zArGDk0;2VIWK6$eeTX}B;^Fif1oO%8+&;-j(~*x0pxx@vy)HtChzq_mwbCZ%217c@H+eH@9rl;}6ePS3dYzPsqNlq?mn$2pK1Dd8{O!OAU1u%1shM}~ zaPlPxgz0C2)2YBh$_!5F%j$xM>ZVNyftPz~^X>JAOid?yQ*u;`D)CgwY_m-i$6S@; zE6y^g8r=7CePeNNk+;9Z!~1ETr7LAaSAz1H`HzPNpvb0b@zjb2WjAZXH)pI7 zzed;3t(a&4QM%we1?CHYGZN>jAAp5$rwIIPKRxv1g91ZqnO^c8&*Tb-2*?ZBX+|YP zNG5*y<8GmG^xcrEE6px8`ByBb(g?V{4g1il)}A0taIYh2904xPPq!0$?hzt?9ObGp zGkA2j(U5X&(<&xQb7kOb8vVu=H@E{oV5M25boKc=VB$AX&N*ui9j3`zQ?=VPr4$;f zLOjX*KWi3ZxVH8pnXc*Yp?_gVhv5Y-0)H|_CpB;nWctizsSfd~{6b|5;ml6Bf^j5P zYJpV&@I+R<)HnyL0>dt|0TN&pi@n*?!+c7iMJaKoXQXz=TgG9EY?ZR~T$bPK6rJA&W3oyMSa}Ci*d=hGSkA zFLeG4k}cB-m~N&Yz`)}d`z3QXhgOWdylhk$NjgKG)4>{)pnKjYXupnW*Mxf$(>!tb z%Ibt%ke;M0U%~UzyzWX*hgD&&7 zt;z?dMtMT`W2RH;=vCI!@2hr$+<-{iLpQ;}PQhXC(Wi#+Jr{4f=5C0+os=SVRm%_I zXea;5d_I^fdt_N+jiH_X5^%`aYf9w~t4DIqifS+{>zNf`=FX~62Yv=K1^`fN3ItJZ zw+C@dtT0(0L-UWUop^zuJ^QwKw$xwv-7N|sttHga_gl~3!z*;ldH$p)^Lii1i+|ms zQa-r}3`r!l>`NfoE@oPy8m@Nd#61}o2lUXH(SV`3s*94nW>K=Y6$?lM+bg+eCgrCn z67W`I^f3@OM7C$ibQbNipMDjy0unD$?Wrx6^4-)40yg}=WF6@=F#TOr6CZCvT9xB$ zky`my1ov&d;JlRGTL>inPP=M^gp=n4o#XizD^`i>D$w|yv76&qg5>%EZ{D$R`?Q5laQ&htk+MRdIlv~Mj~3a)`lU?R zga$UM-MuaqKHAb+8L%$8z{42^a3@fov~k3cHy0 zCy{&o)IgqwwLGE)3b22qUw$|)XyqBKCC*#C-(L@KMP$1>0%4Tp)sLGM*tjEo^f+P( zda)ar<0Ax5)3}{4G!5`xtDVHC*y%$(XIG$OGM3y0*ghg-ok7h+bF2>HlZO61b2xJA zQ`6tsm&g?HVW6adwn0c05$g!o=R!u`nc^7{Dd#;(*Np)*Z{yz2v#b$Z`Sjs71RN&! z+^#eD*sX^9?WOMUHWVhKnGW&Wr`gYn;P5m3(aI1oT(NZs4Ymqu$#j(eYs%#Mzf+|i z&Z>VRo`+2>H%*Nfyj%i^D6tU&CKHB4-Z|yVU4kp!uNED3%PibG7^U zl45VDEKR#s+*2tPykvN;m^m%*Pf`VabbrRPGAF$!0g!EdFN={qRe}y7l6wF*+pbjW z=T7Tbu$sg^XU$W`@4Y?t|V7V(HBirffnpqLO@eM=|w4~&0@|lC02qEgCsJCoucS~+k ztCJVvn+_)dE&XO5Yv>8Tz{c*&ncOMN$3GJeEWY@GM$ah68+M2~57G&ns%G$Xa%Biw zwYG3PZ*mZ4HF;^`4o|1=_4gmfWSJJB##t(;zhiTS8z4 z$=fro2NN8NOCe28n#R1OI7|i!W_>!pd3lM4A^9Q}si3v;=(ZqTYjsek{$Cl=<$%Sh zA%~uS%oJNFJ}B{Lq!tHeFC8_w#2=1Cc=15njo0&CwaSrXfFo?>YavE~BC~T8s`X#^0h11-w z4V3DpKNSl9tjutDz_yc9BiI~*_7ZeCmQB)+tamCd{UqNuVY>NFtdZm4B!rP)miOr) z$=;TbI(nMm);^tIyvWm|jNL37ul8Cvb~;v1>Fu#0CTZ4%Y|DqZRPg6ClDumtBLg)^ zsN_t7)*8j@75MwZdGEA0zpGxWL;RH}T5%C&DbKPI z1sDVb;k5Pxy3{Wc^{#+5yU3zy9ND(M`6=g1Qz-vzx!|y3`PMg5BhbK+&+*Ir@>6Lb z)7Sc*s~zwmev;2(CkK>r$t8LBY&B|Ky{^ubmY|XDRA{sy;2#a*8lek|WuqrxI;aRc zuF}bGT=af@auhr=J0G8spE6$ht^I5G0g>&kolo@NuNHOS>LDGtl8h(Lo7(srEPECQ ze;X)sDhxQ1BWYPv01*yQtwGXJQMK>Yi5#%CW$DF7mI$9}?Oi4F_8_5P?N3+PrWJlA z9#QS99~;30D*H!2d?T7b)dhp4PIW%^c3CxX5e*glGfHqXILc$QE@2aA;&O+>2SsOm zexO%ecWQC@EdXMV>rRDdOkf}ApGKbyhbtgkFhKw0g8oKn1)T8~2r>}UaZPNmagFX* z$};_r4t9GEIJC1xzKj_b>&UJNbkhRnHiYk7v*i1zAra4?5Jp1L=oLo2UZ=)&z z=4{jm zEM%Mw==BzuT=G`0fdmAGg+hT~PMY~Rc%XQmq2Y8GjqL8L57Q|<7mYr@QbPX6egH*V zil+%ouYwi;1wJFs-nO&Q1=A{;n^g0H*hzLaNJFN8O;A01%kINuFhchtI&k4!s}R0c z%|O^&?vI&}%j?*}r?_{vmJl^dxJwM6?{Y|7Rcw{+I2FFV?Y^+Ui54TsG)*z&vAZex zcZ7G2gm#HWjkOXv6BFAC7)#JaP$&pq#1=Rn?H2UAE|3#^h2N&!IH{_~R@2Y`MDQ<$^PIR|RL1 z4?~NMFxXhLtsfWo=DZp`0mV&`z6no+RCD{4CPxv)<(6R8WAEsIDe4a;Kw!KXx z0>S?l%&@97f}J(MPWnD*hy7>4NGd|+b96;K47?%~z$yeX@*gdJCQz$YTHu#8j*iI* zOj@A>!glupUZC-Pun;H{Cj(Y53oVBuqlNYj#1F2f!S=Tbc82to`I!uN5_#SASOK4{ zgcnZt?d2_%3C5?X^O5n2%@OQoBe@m1SUp>frD8$Ba5#x`g)?Qp>`4>!2YieU9Ze75 z_pfZ?*UWe35x1|uL)^X%{zbtNFTntZKHqC1mf_mW(ZSd@)M}+&?(7{2JTgYD|UhEK>ep^TJEUU`ogf8uc$zMwv zFz|}n)`PB4g`M&4!yV#bdlgLNZ~1W3!8UNyl$4j0%()?|fT)xoJV~_ZsGJEEUC4-_ zx>YjeSmWt$&VTxm$@b^PlAes6dHc2yi>h(kKu7mKUU%lg)o->5zT7TAe@s;h6z;#i z)P-K1O{ELYIBo)o7C)BHuR#yEz|gfH3DECx0U8hbE;Yh*xZ_z2XiJd82}je2`V0=H z9lhEzRZP4zf0)E~n?DPE4O@=F@5+rO07swcCVZZ6CY)iyUnVLS#i*)g!bd{*11$h1 zqIq>>5w~Z;-||BfCWJ6!3^o4^+#SEBqAiPfi*gngryz^#-vs78uh$K<;SBs5^csK; zz#6>A@UeZ(i(Tlk7wCn(r-aaE5uXJ;2qwar>#NW*v%NBh2z>t0BG|I>GuV9BhL76O z6&5va6(>Nhw~w@b1B3IHjG-^5JP;N-_6aCd>U{=Cax?r-Hvc0S-N%@O(_y-^gfO(R z)lBHXzzS0Xp}~PzK81!*7DkzI(TeP6+|X18mAFdn-z_K?3YKCeF#0za#aQFcgm3j$ zLo#@5Zkrs5zu`$=e{cB?ZYY1;na^Y7=2gsIry6xo@K3NAlX4w$QQj05JA=4ghW+t0e*kvGMA&u zAMP1iNf8b=7eO0RifOm_2y#s$D-O`ufU4<2IvI=wvP)zE^qBOrUkGC{&`0jPBp_!C zD)Z1UkRz+QO%vy4mYKJs1Ph7TduAEux zboly)2XR%f`}EZCJ8%7{;Du6r4}GKA<>2%uam_(}gQBOa_H z{_;HXOt>5{R$oNbeo<#omghpiGCG8L(=-^YCaEOK<{&!WpEX5m8l7*x3j^GC#9jLl zA|;r9$GI>0?Tf6nZ|VH;tM0kqrrKx5kVp&lAiY zgGe!sM~t0p5VbN_CI}NIR8I0_P<)5R3Q<$>K7`qNRn4^`aYd}a#u(ud;m`=DC}-$6 zOarxyTReiysrw7^Ol~efCMsxsMg?HeLdxj{b9_WI+ z7!EzuU`?Y_6&jXwOig91{Vn~cEKZvsyVsdc)j>4*mvbye6 zG<9osZ81V3pwZWQCjdbBKnlg7`HIF7EmeE`=+yQbd7MAX2Uop65^b)1C%^wvOz@4#rY!$v;CbZqya}IZwnP_ zCc{ikG!U90^!Z55bcZK?)ss!p=;O4SjnDD>Toa!B?i8h0l}$wskKKyZ0iw$vyI3nQ zxB~}W0X1NWlxF|vrGW1S-J3U)^5YroM_%C_*Sc$~fJ!IH0L;fs28GPL!^Z)g8xAcZ zfw2VZDns4BEX`99QNYsO0k!L403d2qHJ5-xiz@%{=x?U(z5C?I6pM>rt&zd-aWL4A zyeb-hFwiyolpPa{OdxTTFNc;<6lUu06GZZ!>B9ixD_O>v@$}?R@~!)jduYhlM0ZACP@1gxXFitMqL%uV`~BaoCbFm!&`AJED}9a?BH+U- zhzMZ~)!s1Og}(tV0HJZl4%h%l>XmFK1wRawug%n~F1(&NL=h?A<;Id{rq zf~`e0AoM)+?CoYD{07(&QoQ8^2w+rSx<+=D#x1XlfC$qAztt&oK9b|dennn4KG$!N zd$aAQqr@pUsC<|+c{XIZev6j@Z)vsff;v!?G=xW>j;{w5Y-iZtP=?vJKDI>v<78E2 zl3dOLGUf(+&0tn2m_dsmR<)*SUL>@`2>LZZ^cR~6O4gWjw7Qa7*QHN#ZQ}O5xq#j! zhhELj79PBVh$8mqcMze}Um^1n>DQOHP|ou7D=wt}V5M(&#>NL}qUel=h6)KK{u(K4 z>eAii|2-w=r`gJ&V7MZzox+>?DS`ye$Ei7}A{cr^b6tvZw;~~H8)_k0T@?ZnAM`i< z|BjDTMv{MFipKmU&B)w5vl43=tOX)Cj}J+hY={YmxjF)6P2OcX7Xh;171FVMJL2F_ z9eWEd8H%ClIK4Y5$o!{rf1idrpA|^y!_Cj0lJ%Yh#MR3&Sz7ng?Ajtg{Y@5k^{DBI zQZ^Xvjo822zkj=byXjLrG@C)=1p?YAQ&*7@n17g97@Coe7C~3s61|J~oUIJ#O|h3j zQR$kdo>iV-4B}uKf&y#96wHV#=&Bslp=36M+7_QO1@=+~qIw-PsA^02_5Cwg58iHP zRoX!~KR6zE{zwY*S7quX8HzKd>U{1fy-HbRn$wUbK5|vAST7=;o>Xb=^dpE%Lxl1>pl)G^T^X@$Y=U?hJXR=Thus%@Wc$)5{4534m#S zrX2^70Tzb%>Rdw2+?}h)@trWWUUTC_xJg6w)7kdsTEmcky#XB&S!{p;m?qfahG2?( zgDTP@80I#xF%O&q8nj>u7c8RlZe^?%_Q}!cqW%|ZrSBJmyLCgXFxx5C$@(F>*7`*k zvMtU!$a=dkr@j*T{NVVDf4Y9;)L-AYs+3=SeffgCg(TAUM_v_$3AM%j@eCAC?ExFR z<;f3L*pGF>Jo_?*1=^;mO@Pr$|E{^gvIN*;w$d&VabhYx4`VNGw5&%B4nc+2(CeyF z8;Q}h4&nxYe%?%Y|8pGo)BKS zoef;~F+3=EwSNYz%kFA)?Q`ji&)LOV|Kau=c-{+#{iy(>x)X-_aMep z1V|*^VEH+@FUAKNGZh*_yNe`yMa;2=uAGTPlAiAeb?h8!prvQ@@N+knp=`d^+co~ejdV14}BtzBc-AqS7pn?FZZ{gZKBE4=ywS8`}~&^Z_fr~>UK z5XrXst})2YBPAqW0T6HM42dFlTNO#+?}n9^r&C~M;JG>#Yaj)(vM3OqYuzIXEGt3e zJP@))A}E8@7cBN9A>^+)X;FLG-aN!D22TyW39H_T>(rVHXnyWSm})iw8HCb^Opai9 zA#`z-jKk2Y*cmM%NiCp03_KIOGbA85XKP!5jBVTKC1FLx;86Q$tM9@=QxSV5N;Gnp ztx#pOIAV210o;2Y_>6WhwcBjDRD*JCu-`zj_ri&skw1Uqz}9vsBe#-fL_-zdw(CIc zxJbk%bAka@PvJ6%tZH1asE`^qj!?|KgMRQT@ggJaGtma>Y~}MsF^7L~H&DvBr%c`O z%cgVEViR#)2hTUBVGXf5D6#42_Mm?e{}HXlnKJWngJpy|Eb8q!Ru(l_UgubR%K}RX zAj`SsQPnKC;&~yPS++~zZjR}<5TIM1h71>M?}KiNALzx`Z|fV)6!ICM5*t*?B9? zI5g723YKoz9BT^S7>Cq{9kq)by|~hH$gmc|!3SQSf7|*fD&+ks32(tJ>*J;7O2b7H zi49x?9Tl$nD>wk_sLoaYUrgLQw6~1^ys&%qMx+}E*cR5yqJ0+O2$L~xlGc^xAOrCY zCCsG5zs4UqwsHbv$vfx*eYJ}5*?n43^n2R&v>#JlxFnkNPEV?B+-RwQ5Q5!o4C`82w{!qfHYhQ`!B}Mb&R&&&CWnEFe=@*32C-L4 z-Y(XX^(W!iy9+;2_`v`##k+^EGdyrO#05&1tuy@6;0V$J?-()ztkQOVaJe#5ep$ak z4PtbAzm;uZQ#uBSsvdm*k3wG^!XU8a8xQ?pv0~*SJl#`4B|IHMjbv#)8UyxO#_+6b z5pcSWK2Lo;+vsqe>Y^)Fy|EB52gUX`$41y9WcmXBiMxH(`?E0K8J0l zHrAOl>?DWOg0t!W}6nkYtD3n!M&BCs+v z?Eza6X)AdnNg{uBaw>3E3^WHSluupp2=yh;iH(eQJ)pwlKBkL^Ip91HmPZa=M31d4 znvaHR<}B0;k^L@B8Zug%HAP+PzV4#2#P+3E@m4f)Lvy%9{n?gy{TsS8k#P%SA*RsH zg^BI^4 z0{#C)N^>-%j?UyIfin>i(twqS5YjSaFsrW#X$uHr2QoGUupmcGMZ^5cH94??NrnaA zc0LpKP@j5TB<5_jEamuey8<|m->TL-qLEcklSyGgifp+9O)ygbz%yVLYmNE(Fp>jT0^3G&)hv+ zFp_3jgq>%x9ZN64NM-pXgpF23GqBPx3VEMlvs^CXp*bJ?1tQYcnI(ku^5%Bm;jG>a z5LJXp;xi&Dnm{k@EO0uca6s|% z(;pta@b%AOhM!8DJbCi7|9vKL^!XmejbDB7bHv8Gl1ER9A2mNY|J_OPb%(#!Tdbmb zPy4u*j!v4i>j$B&sS+!_ghjoYD%&2a_u`h(S68qTeszqNAd{NCD?QDf zl+*o*NyWEv(SeFx3SsG6jgH*0+W1t=f9QiB^&$%1`$5b}`MsCoqX$X?Yroa|A69q% zEd7ev{$c+z88ZwnD((m=&v5JR%@VO@%`pa($ZaSXdv(*UkeDF`{LsvQ} z@cpWxpSpL8oA#$LSbnaSDzOnsb9htT%Jx#vV%*)VfFr3rm2Szn9-ql{)aU%6CwpTy zWX?pLUbzzK*0n4P}~Xr5Lgc6!HvX>)csvI zVDnS@#A)*3rDJ)z16XEW%=`4RTo6j`rHAooB`KXZvi2#u=Z4sOi~H-|Uwe8?GVSF( zOjE1)%!KgXo)kTEinsf05WRFSz9${_fDXp0wLAd1oXTSE^yzF_SsEYQ6WxW8Ez>)N z*0Q$9p;)%qdwuuj{D%&oSUdmRi8JK1oAHJn9_eDHEi`X;I_dA{j}5$g+T^!vl<_@H zwWaCrbebEHx6k80){h!dVeJ+-;S-d6384!1@=_YHdTM%J!D%VG5-!|bB<0-=s;mq% zJ51Z1q*Ht)pAlt4mY8j|z7;=-YL|eQzY)rArQM5rT5|o(mCx45th^TVn(dq3TYMT2 zVNNxFha( zYN6fZkfK&U5FDO3pFl)+-Gh%os6qnwb3EHvp z^LkM7MLcyO|0Le65a*p4H^b?I7uaq6^eFEqJ$lGR-5|7-QGHtA*K*A*sOk6!nXN{9 zEGACN7(N4zu#o*tloQsYI1U)eU`-eFo+X&6|`w!<+zWIYl69D%G4BR zlX9+(x{-QfT{N?0N=BNPfa^JZXz4qAW4`rcE46-U zTU{*0Oq=i!*W*31u$THN26dnOhn-uTdEvJ3VNmZxyu7$+djp2$ z7sgK7Ew!}Z@4vDxPMkn7P){5&ksDi_%j0Rl^MkDy=i?eLf>kV5wn3k4*hOHRP)U-G zv#DmdW%7S>pgR`bl6A7xXZ|{g;)Ch2Z6~keUNSsu68H8M)@< zbymRCy51<8ymoU}Ys>uK?#2-g-w9~YrkcddB>KeaU*j%W7%C(`;6Ht_VqDpQs^Wf2 z{v{lCwqqRSWWdR8W)1EYw3+-##{NOf8>!<|IBZQQ-asL_%iPFU=c>P*Djc@Bn7b~n zB;Ow%J1&>SSHGvbL91&HWtSIy$E(*B&dMA4yw~()NNZ8QeP!?q=8q<}7XwBm*Up;M zA(U^ycrIVX^;q&ojIOhr><4!{t*<>5mVW2^wLymOz4uDd-1vL(nTH=s9OdiePxx)j zST$9f9{2EQq20dA-+9oeP?niWc6Z0-<9hFfc$>>y=5FcOGtp;#r+Y&;=5&FDM;dWn zB4)#JiM&b{s0Rwn`=Ovmim)&@78YJwRN~(Hgc%Zvo4jphSa4`-So+ti7mn+{_fn+e zA|vM|>s0yk$@vjq5cGb&hOZ9dS&>bgUuZslyiXrA4j47ejKIVe^S%QQoG;uvXT`hb ziE@W$elL4!lP5nHS3MuS*O7VLCxiBqOZ~lZCyBUAYNqJcB>nzCyTU5I(f#3UYeO%(1ezgSveb~ky}pTdMx_;8+53+5D0vy?=khqT9#+K z%TgZk?|U|0tmOBfXBaBz7T?Tw%|3r5DE!E_hh3C7c`p40`b1rDq+D(b?Iv+>_`e?B zH$u<*p)IB=`&=(40atTgun^p9+Q*4EtkcV6J><`~1T5>&kHMfuKWd|($&X%^F2uzX zH$Pn7d33%Gyz5A0Z6NRN_@KOas!hFRu>@x+@Us-KaU-0g+%CyJnaZ>X36q_S2`6pM zY4qP;{%%#2y536Oz*9@?3H4+|zDn$TACBB2Zni(V5)+x>!8D~dRxOIh_!?ntvx%EM zuv5RfYpSHdC@qO&V3q>A2M8!bg^HhT^E*7+sk}tor`y^a(o7ae2^vOq!78@;VGF%d zd;e9*83#(1E)}9z`UH&*^lPNW?&M0n7~h7hhZlw0%M8J1CW7SiJQhnzPIRc~HEt0c zC?fLGaWz_kS_NwEe*S&eakIegnDv<_UO$5;ry=GEV&r5A7Z&c2RH+gP z7cepR^ahWf+RQJhULe&UjUM-i)(wisRht>DJS=gwsM;g-^yu(>^q8Bi_UI?DY7k#= z;EkdUgzC(_HtV9G<~nOT;bB>IT2OS^r2B;UP1jC~Z7I4olX908lM|ocpfK@;nOP2G zPc7fc2k;iPpYiAd`w-hRT!Y#|?}ZZQdBN_4nHQ(39_CJ1N*aEI1T(gRg(KHP=3Z{5 zm*#s2SFBr;-Z|o`1;MoxvCx=kB1F z9o2JY$sBos^6aLDcVOz%lG7I@GKwuSX}Sj)I`v4lXToY13lGD*COV>U0kPo?vX!V7Olo8wrY@}MXFQ3S5=hW+ zc*lY53$rOTYFVkNAJzOo4E~I5qwkBW3^i_+^i)<0yHto(xK^P0814{Q*W?+2DbYwNn|LH8EWv zZC5Es<|froq2_vwKlOj0as|bSQRSctI|9utk*A*XVd(h;X|xf7-Q+9c-g~ z;pGd&^KbuZi8QfMq$8I`zN_@$gpKZAvk~TtW1Hz;>cglBOFg-qr zf;5TI-BGm?gR9QAJA|Q_b4SmwDX3)MVN+~(Vr;nlpgu<@z0d!+-a7oIH}bP~vyX7j@033X|6yM-=S;aTuPtsy@|6E~ksySn7% znHT6W4_1eAyzwR1z1VgOdXBC9<$KuXfm{fkr^KxD@^6#KpqYHsAJ;B=z{FqO9{Z<$=QaI;k;KW*L9}vy8MM z=7LhC$6(pv*Sexu!TJbZ83KiUd|WZE$3KIIn=3Dbpsn%Ve@OFAbcijcLJo0-SNuz; z%(UMv!Ak7mP_)cv8CO#4w5g3z75o_Aa7E25zU8_H4-hvxy2}`?r72LW^k}OqM%Hh6 z*rlz@*|8?o=4Y@=q%Ca2x?mSxAZV(per!&_yWV(5N7WrvpP0!iB$KlWb4eQcqzGFZ zf6qlYINm!j&~^u3-9ylH+l0f;eZeD+#VN^)NBZkmFNov2<5c7f6m*59NCxus=uLg~ zxP(dJs&5k#=d>(i_*&dUjM=H3e8($+r#h~HKvNqRKOpR8NwPOQ z`*|-l-uTdZ{PI;#RYpk6;sW_66iZ}zSCa%rYTH#5XjZ=WE;BLG!S%uW*VFi+`uoH& z7Gl@lwZ2nHMhi~~{W1(Ag&pjr-8=PzjMO~Ocb4_3l5e##IwmNcAYZuNQ}cC>mBOq3 zi4!PcdUr!{+I@as)=x}#cO=q|Q~df%eQtSq4F72h6C-OZi0s1`91}M7J$YmL{z;Gz z=U3iF^@W4JC!E{pS!qMD6e6N%G#}>+G6abn?kRs|JE(NL(;1?+qH%|7 zfIU8Em$*kaJKeZ)aetX{RKa*Js{luB%}QYLh^qzz3#koup*O zdH9|jq_M~N&1o0lf_1vPv%62GWyy0CL*aVIy_fFrg1gKX1Y^Ek5E8#ra#dnCIjU*(c{IuZ6~rH9my+ znC@(wx%;@D)>6;NKH+Lo{;$S9F3rg;9uLp01YO4UURL+D;(nXm(=i!*=f!lP^?Y4L(F%*B`d?^&RXxb@bHX zSqCIL|IQoNlv1Ww?4BKtq~}Y*=PH(lb(`BW!Vp!hQ*n-NJh4E^Y+N91Rt_;V0W!8*Jd&D&kK*6og-`1=zZ~4(*A}B+0kAO@D#?cFFlM;i!(kX(cIKBm4!04 zKTSFNxKCKqmKPf#wVsB}==QK-pZ)CarIDxKp{)fGxVtAYH?NJOJh1GJ?yNC4^p~A8 zFWT-q5bWjG0X(o@tFC0KJ7E&#ksA{kI_GkhHK>@RVfOdeRxES*IrUOLWXAOG%pRXh z^~CTM(!*VuHV>DmGfgacr(*!k`IbkrI!@H7KokJb0Arhh!VZpY`WY_$dS&;5pCVW? z&^McUhFgB&D_eTCFJxQ_JUV@hk5kTqSTES_xNMct+qM76xIvbjkLq&+K+^b-xlvl{ z6ymB+i;U>eHkwgg1jzmKDfOU7hH9Q#x*Rd^!*O>)$V2q+`S*QkXDd^}ki1|^J|(>c zpU*E3Ub_QXW>pI{&n1umB2ODCH{-tAO273thyALAIdOfd%-`aKLgIwPCSOABL3~_< z?F$mgRfw2dse_-Glo?WN5i{9yYX6GHbv|*_9PW#6SQ^qw_N@dF*x-8IBEPbPuOq*S zaTssyQj``U4;fTD%{++x-P<+)j)-d4$tEtl>CN^b==1eDf|{91_r%=^iDv7)-)-#) zEcT=bRL_P*j@k-LsD=yTM=*6AIhz?61S`b*U+5v8P}qSau9({EdTB-?HSb=a6S#>Z z_QWjQz&(IMxi7;qgeTu#{~JZuUZx3Oh_fT*s4B0KPtlzhuS!)4<(Dv^YwOHH^1IDP zX-5&ixj^y0dGIQaaIZQf1epcho0%|N*~3oq~VM;fzsh9k>QQ#vbJo^APHdvKrlG~5mR!*N52Ff(B!Rb{Y|wsqY5ffQ7i+02@jq>0DS<|a6M9|*rXgC zFvcxB2X!_k=D=POOd(;E31?)~Su|A^*{Zz4XMRuM>fPeEz)bIaIICrYe&AApl=yx=%My z$}(2z)`84+VbbZjS6h3h3*u8@52ogp%Z#G=M+qSp2eMUH)xF120yTPc->!tQ3i+7! z&#Bw>6lgEQT*NWIh~kui!Kd-ve6}gkG8ET%_jO9}TE*?!?5#&IW&W|Uaqfp8y&tD! z#o>J$;oK9!mY7?Kr?m-*o|byEBZ<>ugyMD>C7yXArx1<3iNV+kG z+Vre%VZWr0^s4qs@A-%EIo9t!blFBRE@|2|%UrxHY%Y=#pbte1dv^Kw-3&(F_*auF zx8u$&=ATC}iVfXMFUSvFlj^t0#v1$3-R2+RUTL#Ou7r(_7%J#%Z<>!`rX)JW91edI z`~MB+^@=E--8u%y64k^ElbwnYh~K^z-@r+=I$T-7UIE~znVO*&=^GQNFlpA9UYI%I z5rx1lEix&@G|$4ICzscRmrMPu;!+iEU~uS0^^kvN>oZbc!aE@;<34{efq|nJ!WDj>SRE#Rv1N;c4M>wud+v)YcLy3T2OD#SH5ZN^YF*qE@i~; zU$CEjcAcAOQ7KJj-Z5Wyie2tBp>gHle?DLpw>r&-%{mwFeh9%Z)!XNC^JY^|WFt}I z*_&uBBT9ByMxIuIe-hDzr(?n)DN-BlxCSi|J2~oym9Vh9E8V$0!)9RHJ=)NA+e5)Y zchf2se}?1wcz^&w=KY5Ny_VO!^MIbB$3sR=Q*igj&wIXBlOX{;keQ7X)(*yCR0RfV z)0%)Ag1l-48tiV{9 zOgIRS?2g`hYfh)qgkPR-prCEO-O@vn=^u{UzXLL@brgq`F%-K(F=2U3&#Ol`T_pE% zmv!O0&wL2)Fp4QlY9*2z@MbE^i5^UDX3J9={ zZ<~3dNdGP~uh_M4EOkT7PprBBYwz3RnQs68cjwSSr9w`jn-~$H2(c7J4ogLetW1e= zN>0-@a+ffKLI+z1hft)P!ZwvdZXr^WVaj0+%^b$C&A!)E`aJIL-|xTQ@9}*+zTZ9e z&-=aYeO=ez=z<`O7W*UPEs}-ZkJ_!poJazDOs> zjM@4n!jcLFOJ=)7FP2F9f*{36ZN{s~hU~y_gC7!bWLv$~jA0{%Jc0mfC&rIdX19|{ zj+TYZCX8;0YCUxk=ZJPZdE{eA-~Me^8R(G3LhUzml>>z{ygWbWneC+7q+n}VFQ{dI zrWvy^(k}#U=YRumIt@Vsc2E<@Jsp!A!plP41xcg~Zm@~B$nj54s`D$+;pFef=i znmK#IjJ8>JNwB{q+MDW+Pgf>ZSnZfIOxAs)bI#{d;h})g5hWIh^3)kb!o1XN?-{9G zFlW87$Kjrq4+$Xo@_6^Qa*P%@1|8vTZD%s>JWORd`Y7HxS1N<+N|!@^Wa@u0j%)Eh?q2M@X zhPrG>dqMIUZ$a~0ze+XeKqvfgTBvXLOUvi2JD;WvJm$|5k|P`2Qggdcd*1GB&7RLQ zHv1Uj^MCHdY(k!Q(*7hdCSW+=WEU9@)J29vGzxW?5zgzp1>&xN;?Vt2))2&9059C= zU=Ls8G}IvI?eKYc@Gv`n*ThHZkG-J_*5JNj%|9TvTPV%K(dNpzRF=}qu0;bD7dg0Yg z+5i|1a2hy{z&12L54AJRP>k1T;?t&u=?ffGnMa=`k5pi6_5ofU{?l&$MZFz)s_>DO z?BwOXDL7eyL!df>Agjh7tdVe8tY2_<4Er^DD(gUwRlgC2AA*Y8CkejU{|Sz|BhTVE zg6MMMd27j7*SE6pY?%NKZ=cmIsEN9JBXLcJzl zYF0?r-M4LLey3pUIu6nC16kxaCpCk{+pV+}kY?FD5c@z?)oLnnaRdtds$f_t#hVu{ z3E)90gE?|T3$$*s+sOC~5t_Aun;>^rBB0#6mxTNqm84#`^y_Wd=t}nU`mKc`p5MuY!nz&R{(1we43PYcMK}tjXxq_HDuBXA6B-xZhHjCB{X07 z5u(SJt&W?gIo4sh9(`G?R~qxP2}IUkzhwSzZmdx;;{miw6Q7|XtN*v+ZTva*%a_21 z^pwCToSx?cDh=rl{GNl|V-Kv40rejk?8zv{i#5$ikh|9}5*&a8a?a;(kw58q8hH-q6}BkuRg55XMR7 zWdh7(;;?~VwiyW&?KX8uvIQq=E)2ugW?5~G%!08pS<%? zK(}1CFR0&D`_}TyEYm=WE1PF^#|GvF(044_cA@lK$iRB?$rSR{Sb;mEl6o?}L7z4y z$4fIq{z_~n!}WfP&>Hx#7kK|dx1vLOXO9B##rN1Yso~@oL?R{$Faypu+yR|clZx&r zK`mkuJ9c|BuivO4{Xr_NDT`2yE_p*X79)W8+p0+_N+0YD3c zA*f&a7&H<1wG$!eK?FhHG)GnbBpjJR^Q{LtLEKbRy9~7g8Tg!VZzT0X)lmVn0RcKl z$+}BP;CH3_3-HaMm26;7z=}UkdS0sZo^wiL5xz~vX+cR{+RplS4AJ+gO_eiru>W59 zkV`(m-7v<$&H&+rKcC)C?fhCs#hfbi_>w1R)Q;w+eoA_t9X9^!KqJtC|p%ZK$LSpz$+}2QbtEqa70z6nT{9q9Ln)kpXSGOBrU< zPqLZdS7iOLZae7)-HX|E9fNQhyU^3skvv~zMp_I4OWW3v<@v{-uYM7B8jHOcF7iwV=?zjA;YMBE7)`7&nL^)QRl(*b=7sLo|U|YcW zd9b|%47gHMa;s_&CO_*hyUExSU_9@a=cX~!@_E?=& z&i=2J`omj)B?!M4iUxI{H2#pn*qY!+Rd?2xgJBMoh5NtOQSG}1dN%xfmlWssKR;VM z)-zJpP3uMliXmGjL9s3Lls7*kOPEWfkd zV4_<#Bv)YAs7Ps>{JcET!RCy7#eWe%BbY%vdx9?D>zH8%Nk1X@=H%gOXkI zF5SU`++37?H&?fGWFQCdiv}$kYT%8YN_19tgH&ysSo!h1RgdzWo=%Eo=K1F>bai(> zEgVytvWmyi-k+k8?tyr=hKVcL=x7^YLZE(;)$)<&S zjCCjL=A4^X3Ze>1x7CZ$zcw%s2&y?FIfft+ZU)DMXmLgLfT)U(k4WvGnwnBgx#7G= zVf7NzOxwuuf+Fd3K(Ax|g>X;^PcJQA98Xr(w47MXKWV)~pn$XT!&M0zjHTotZ`zYT0*FkenaY zTNx!~)7aR!e3h6OFSIY-P;_HAW~w@x%q0t(ulvupeeElT%ktQ`xcJ&K!V@v>rZibP;+nb4@%tj6qaz45tQ{JrZeY~6?X971H+mCY-ZQQPY_2%C3^)g1>%;p+hv*_dA$ zSy#6o=`b-+P~8%sktaSNqUh4`xMRw)mtHOj2Jgv;Bmh9RCO6VHDpF~^;q}of; zMyJ=^%f_a$I?pkm#n;TUTCzAt^(?@aZj2OK-cNu)vUPt_XBS!1Z)8jWy^$CZmleD9 z26#5_vHG?w8{X?-|0S9)xOxz{`nbE-aY-BCVC~iZlYji3l-co5Zt(XXEzvhUmO*~e zH-C_q6_z-?KO6!C;`+0Z@V~==F6RFvq51!rtJLwrqoI03jA>AE^TVF9)AyepSy%y) zi9B7`(fm4cjYgrLy0L~11ai%=i-tTf&vu3ENv$wln0@KdfDcp|Ic2|A(zlmc1i6s{ zelbt%Iq$X|DlfH!OhISuvulKt`AR!cZDvCVQjdiM-(56f^MktM8aLITObMo^o)V<( zDkuD#6(27KkuI&)b%K9|*1t=!low6CpuTCv*60z+;d&QHgie=C{(_=OyWQ3mOPqM= z6+9h!J9h3YZT(1hL-A>*qFOp+v-BHWk$H-}aakIF%d&{mYuN0L^;}bF$U55=q0EJh zOV8x(k7C~7BTaFIknQeo5{gh4){x9azWjm_uQ1=XtQ%L_Gi^DkLw~i(wuGPJ2hpnZK{O z8b_~GgRJlpMm~AHOAPYoYQwq&|J9-pj}P!+aXp7Zw-1JM4q74+heTzOK@&TIu1fOE z23I}&S)T9V;)W8qfuiWV80*UrnVYwDQ?0?}HiY4%b`!-RpZ6Qosrl}<=08RIDHFiG zA*-R6&wpcpJMPr(sAq>LYb?tMwtV63HXIIa#kGJ?23PD6~89Nj%7#Zcl zizFcr&ukQdya_sH*yz9V)Y3>mf3q%Ps|>N=~R zieB=8ovHm^~^hklKz-VH+NdeiTbT?DCR zzV0vLDV*wxE6=2HMM%xBvF^F%BLU+KY8j{K!sq9a)1#Ee{H57L>Ndn?iRMDB5m?@I zaYtoHVb!Xo4^IKQ;IE_JD;7Y*KVQ`%;8us92apJ{4NEx?I4Qj)HY*j)45z8A0O9qu z|BvrQtdz9bCU&P6Ob&$L6aRbmzkwX$H>Gj?dmobdIgxSvndn!A5+EP5cqjBjIkxwY zdpo}u_RB2qhYF=}`&vXv{1n0(S1yItg5u?1*-?=>3)9%uPv#Q&rXG*kkJ)E^;ewP- zTlmKn()Sd#Kfk}SXMDm?NSe3HL7mti4OQMjK7;6-;Q!QZdeorA9xZiK5f2-vmg#@wb_H%~b~P1kFR2-MsNwj9Q8RaV2)y-)(%bBbr+ZBig*GeDDgwBys_&Hvz`JU`5B zjTHF*t)K@i4s5}Cd~^dC>QxFa-$tVTQSq(3WGj@zUTKBPuSCS)ox+tm4w6>ezHM10 zTgio{-F&C)JA(0Y`@<)_RDv2VlKNM1>9kqe>Td^43+~1Kk;k~XY~R7te(wWS=^8<> z4NSX@wxXtPOd@^Z-n?Rv!Ur=yVJ18reSJ!*-RWURN&t^%#c;WZ3HC%lnLIB}#zlPF zS$SnkqVq7}J$AC_Llv!b1ShGdnlk=u8nDmD09u8|R}0jv0smc3CdNc&M? z*1hxm^cF^(&M0@SR7~l#(GkMk=RPR@+YSnD;dYfa-F*x4^D}BAU1a8rALpCdd6R!A zY;cn_FSPe7_9*<;!lmA?u=bf70JnSP;H{(>-cpG2{7+QP$fV z^K_hYlYjl)FcKHdO8E1UDRcTA(w=J8cX^KPG#^QRip6QvXGrDxY*{e+Ok!#JA)<|S z=zE!Wu|9gG({3%hL!9gJt!{t6wBrO0k}@+6%zfM3Gc&?_?w4;vWVE$-_AcQ3`TN^- z4zn$ZLxg-5#h3PGqq=L$|53<_->fzL}vG2kUcA3=$;hn$w+G zCzW0rm{*Msu-8w&)ZpCiJljc_hUF-heTyo4+QYvI^K|Q5??WKM8%US6_1Mhai+{fX z1o_hkyz2WJ$>jAyg{1ks#I`9cr)563%?85~AD)ZYj?il2PQA3!RD4W10_~J|uu3?l zVK#zsWS`x0@xzxJwBD_zIt`8*m26|eC|B^~aFaTVOeyERwf^*jyw94XE%?WFgHg)7 z&=(0BqcR(O#OD_RlTbKky5>|z5a*?&)nT?F=R`K6WqWN}HP1R8his_`c1G_)Ba)La z%~dipQ_=AcTCTERs?4uFV>!?H&^)@GXUrhFWxB>yQ+S66ce}|#DyLviNQ1RjJt?xe zRN?8yVgsQ9$MhE4xOUPy7AiP%njO*7GH>Ql<4^Uh-A+5^@}mO=Q9GnnqIK>{$oukH za-&2hOuoE7+U*r9deS8ercge+d>_m)Px)fej=(k(|A)J?)E^?m2kpj2m&HK?uehPz z4o;|+$fTT1U#TsZRf0|6&#&`YzEO--^tLEkefw&8m0icYm^LOe9;<_^z=EekF9}Q9 z=qEr+;-5@Osi!To9+_?=r4Dll9mn7!94=ebHIBuzbt6AHpN2vU6$I&R;K-^UL|BH{R#=z|5o}kscbfOS+$$b|H3J!_7uK)1u3VN7v zxKLWC$Okfq8i-vW2&E0tw({zG*`5}e?q2%?91iqagdH?LWg#)Vp<>#((}=Z)S*{kWw)^7SrcVB^o>6CSiWF zeNDz{=+(ItJ!1U*z#Ays8+_qCl_2bkmfH7(FBxeTMK)v#%rNgM&fUIly0IcyW$eu` z;k@=WGwET133nljoVu`3JY{T4URx#VjNI$(p?IJ!NJeYf%$v^hDE@L2tjhKF;HYNH zPP(XT{)3q?3*AdfwtVd^?&a>DpLVtcaK6dpC3cGaSmO3b_uMn)5>;>N*~_)(zrcf% zj@HAtAF3@QnT-jH3BzT~09knBfg}5f2Z)B1@!zbOtYrt?zb?4GChVpZ=18s66I0J+ zoU!H7kG{9{(R>ln%Q*J8=bF}1iql074kmu&)C&21gnS&jowI!sEFOX4oQ}=Xpn57! zy{BvTPF;&{N7})BrMwttHfCL!X(DgD?W4MIxj>iCbEf#dMVdQg0Eq)y-DV<4dkKw$t z<~~8E?7g1j(X{z$Ttw*y1l&p!v*yUD>l>o*nnv(ky0%044dt5c^>>3V&+~Fca5Ws_ zj096hR^8fb$LNa}oi<6q+EL0AVYjcA)p&ZiHU=imh3%Wsz#{r*+vd|Nd}C&aEzK3d zQ6~|I`SUTOH*S79wzqb3J+o1J_nHdqOIF;g1e2!-@d@I1ESA(8nBJkrk>LU1$QNc2 zUP$hn>BjMzy$$;|d1O=uGP^S_ibnJBI4wQSsmZFdO!!w~Zr}duunD#aoAKhxQV9|5 z@ZCSw1hgX7TxNa^T+g~wVr6@8`Nmz>k6WHt-{kb}^UgyXUOuy0M>qO((?imx1^egw zTtR8J_^Lkry|hrEnisM5#!NYAojhs$PQpuAcW`ffq{daU!&c!6N%}xtT*MERKqS;t zPi)-r%4G13OWP$$HFC_9^draK%`1*6qn*F>vRylpTwjOm@O}2vh5z_Iz)?YhT>Ou# s{Qo+;EG~<|UWf(cq%+FKOtJ$2>306eP{J^%m! literal 0 HcmV?d00001 diff --git a/images/store_remain_memory.PNG b/images/store_remain_memory.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c8e6032fda34c7c53e054c7a0c498df9bb9e5c4e GIT binary patch literal 61006 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 literal 0 HcmV?d00001 diff --git a/prometheus/README.md b/prometheus/README.md deleted file mode 100644 index 1a76709b..00000000 --- a/prometheus/README.md +++ /dev/null @@ -1,89 +0,0 @@ -###Canal Prometheus 简介 - -Canal server 性能指标监控基于prometheus的实现。 - -关于prometheus,参见[官网](https://prometheus.io/) - -#### 当前监控的canal相关原始指标列表: - -| 指标 | 说明 | 单位 | 精度 | -| :---- | :---- | ----: |----: | -|canal_instance_traffic_delay |instance延迟 | 毫秒 | 毫秒 | -|canal_instance_transactions |instance接收transactions计数|-|-| -|canal_instance_row_events |instance接收rowData类型events计数|-|-| -|canal_instance_rows_counter |instance接收events包含变更行数计数|-|-| -|canal_instance |instance基本信息|-|-| -|canal_instance_subscriptions |instance订阅数量|-|-| -|canal_instance_publish_blocking_time |instance dump线程publish阻塞时间(仅parallel解析模式)|毫秒|纳秒| -|canal_instance_received_binlog_bytes |instance接收binlog字节数|byte|-| -|canal_instance_parser_mode |instance解析模式(是否开启parallel解析)|-|-| -|canal_instance_client_packets |instance client请求packets计数|-|-| -|canal_instance_client_bytes|向instance client发送数据包字节计数|byte|-| -|canal_instance_client_empty_batches|向instance client发送的空batch计数|-|-| -|canal_instance_client_request_error|instance client请求失败计数|-|-| -|canal_instance_client_request_latency |instance client请求延迟概况|-|-| -|canal_instance_sink_blocking_time |instance sink线程put数据至store的阻塞时间|毫秒|纳秒| -|canal_instance_store_produce_seq |instance store接收到的events sequence number|-|-| -|canal_instance_store_consume_seq |instance store成功消费的events sequence number|-|-| -|canal_instance_store |instance store基本信息|-|-| -|canal_instance_store_produce_mem |instance store接收到的所有events占用内存总量|byte|-| -|canal_instance_store_consume_mem |instance store成功消费的所有events占用内存总量|byte|-| - -#### JVM 相关信息 -> The Java client includes collectors for garbage collection, memory pools, JMX, classloading, and thread counts. These can be added individually or just use the DefaultExports to conveniently register them. -> >DefaultExports.initialize(); - -详见:[prometheus/client_java](https://github.com/prometheus/client_java) - - -#### Quick start - -1. 安装并部署对应平台的prometheus,参见[官方guide](https://prometheus.io/docs/introduction/first_steps/) - -2. 配置prometheus.yml,添加canal的job,示例: -``` - - job_name: 'canal' - static_configs: - - targets: ['localhost:11112'] //端口配置即为canal.properties中的canal.metrics.pull.port -``` - -3. 启动prometheus与canal server - -#### Prometheus expression 与相应 use case -* Sink线程空闲比 -``` -clamp_max(rate(canal_instance_sink_blocking_time{destination="example"}[2m]), 1000) / 10 -``` -**sink线程idle时间片比例(向store中put events时)。若idle占比很高,则store总体上处于满的状态,client的consume速度低于server的produce速度** - -_简单说明一下range-vector,通俗来说表达式会用时间点前range-vector period内的所有samples参与运算。_ -_range-vector值如果太小,图会碎片化;反之,实时性会比较差。请结合scrape_interval设定合理的值。_ - -* Dump线程空闲比 -``` -clamp_max(rate(canal_instance_publish_blocking_time{destination="example"}[2m]), 1000) / 10 -``` -**dump线程idle时间片比例(仅parallel mode, dump线程向disruptor发布event时)。若idle占比较高:** - -**1. Sinking idle ratio也很高,则总体还是因为client的consume速度相对较慢。** - -**2. Sinking idle ratio较低,那么server端parser是性能瓶颈,可参考[Performance](https://github.com/alibaba/canal/wiki/Performance)进行tuning.** - -* Delay(seconds) -``` -canal_instance_traffic_delay / 1000 -``` -**Instance binlog消费延迟,有两个注意点:** - -**1. 如果Canal已经消费至最新position,且binlog长时间未更新,delay的resolution受到master_heartbeat_period的影响,目前频率为15秒。** - -**2. Limitations: 如果store满了(恰好store中的数据包含最新的position,且MySQL binlog停止更新),且client暂停消费,那么delay会不断增长。当然,满足这些条件的概率极低。** - -* Binlog接收速率(KB/s) -``` -rate(canal_instance_received_binlog_bytes{destination="example"}[2m]) / 1024 -``` -**'Sink线程空闲比'与'Dump线程空闲比'都很低,delay还是很高的情况,请查看binlog接收速率是否符合预期。** - - ---To be continued... diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index b962b9a0..71051db0 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -7,6 +7,7 @@ import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; import static com.alibaba.otter.canal.server.netty.CanalServerWithNettyProfiler.profiler; +import static com.alibaba.otter.canal.server.netty.NettyUtils.HEADER_LENGTH; /** * @author Chuanyi Li @@ -32,7 +33,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { .destination(destination) .type(type) .request(request) - .amount(amount) + .amount(amount + HEADER_LENGTH) .latency(latency) .errorCode(errorCode) .empty(empty) From e879216fa4448c82931145dcb96a22280196fa01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 17 Aug 2018 10:40:51 +0800 Subject: [PATCH 079/360] fixed compiler --- .../java/com/alibaba/otter/canal/deployer/CanalConstants.java | 1 + .../otter/canal/store/memory/MemoryEventStoreWithBuffer.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index b328feb7..c7a90d30 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -15,6 +15,7 @@ public class CanalConstants { public static final String CANAL_ID = ROOT + "." + "id"; public static final String CANAL_IP = ROOT + "." + "ip"; public static final String CANAL_PORT = ROOT + "." + "port"; + public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port"; public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers"; public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty"; diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index b276832a..a1ff4033 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -545,6 +545,9 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } // ================ setter / getter ================== + public int getBufferSize() { + return this.bufferSize; + } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; From 10114f4a9f648ca99c735793cdaee172f910d15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 13 Aug 2018 10:16:28 +0800 Subject: [PATCH 080/360] fixed issue #727 , support rds binlog dump --- .../tddl/dbsync/binlog/FileLogFetcher.java | 4 +- deployer/src/main/resources/canal.properties | 5 +- .../resources/example/instance.properties | 5 + .../resources/example/rds_instance.properties | 32 -- .../main/resources/spring/base-instance.xml | 39 +++ .../resources/spring/default-instance.xml | 23 +- .../main/resources/spring/file-instance.xml | 23 +- .../main/resources/spring/group-instance.xml | 25 +- .../main/resources/spring/local-instance.xml | 147 -------- .../main/resources/spring/memory-instance.xml | 23 +- .../exception/ServerIdNotMatchException.java | 2 + .../parse/inbound/AbstractEventParser.java | 14 +- .../parse/inbound/MultiStageCoprocessor.java | 3 +- .../mysql/AbstractMysqlEventParser.java | 8 +- .../inbound/mysql/LocalBinLogConnection.java | 57 +-- .../parse/inbound/mysql/MysqlConnection.java | 25 +- .../parse/inbound/mysql/MysqlEventParser.java | 47 +-- .../mysql/MysqlMultiStageCoprocessor.java | 57 +-- .../inbound/mysql/dbsync/LogEventConvert.java | 22 +- .../inbound/mysql/local/BinLogFileQueue.java | 62 +++- .../mysql/rds/BinlogDownloadQueue.java | 99 +++++- .../parse/inbound/mysql/rds/HttpHelper.java | 1 + .../mysql/rds/RdsBinlogEventParserProxy.java | 161 +++++---- .../inbound/mysql/rds/RdsBinlogOpenApi.java | 330 ++---------------- .../mysql/rds/RdsLocalBinlogEventParser.java | 33 +- .../inbound/mysql/rds/data/BinlogFile.java | 10 +- .../rds/data/DescribeBinlogFileResult.java | 11 +- .../mysql/rds/data/RdsBackupPolicy.java | 19 +- .../parse/inbound/mysql/rds/data/RdsItem.java | 7 + .../mysql/rds/request/AbstractRequest.java | 69 ++-- .../request/DescribeBackupPolicyRequest.java | 7 +- .../request/DescribeBinlogFilesRequest.java | 15 +- .../inbound/mysql/tsdb/MemoryTableMeta.java | 3 +- .../mysql/RdsBinlogEventParserProxyTest.java | 21 +- .../inbound/mysql/RdsBinlogOpenApiTest.java | 27 +- 35 files changed, 553 insertions(+), 883 deletions(-) delete mode 100644 deployer/src/main/resources/example/rds_instance.properties create mode 100644 deployer/src/main/resources/spring/base-instance.xml delete mode 100644 deployer/src/main/resources/spring/local-instance.xml diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java index 0d586cd4..cdb1f685 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/FileLogFetcher.java @@ -168,7 +168,9 @@ public final class FileLogFetcher extends LogFetcher { * @see com.taobao.tddl.dbsync.binlog.LogFetcher#close() */ public void close() throws IOException { - if (fin != null) fin.close(); + if (fin != null) { + fin.close(); + } fin = null; } diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 0fc65c9b..1cefb7d9 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -67,6 +67,10 @@ canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MO canal.instance.tsdb.dbUsername=canal canal.instance.tsdb.dbPassword=canal +# rds oss binlog account +canal.instance.rds.accesskey = +canal.instance.rds.secretkey = + ################################################# ######### destinations ############# ################################################# @@ -83,7 +87,6 @@ canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml canal.instance.global.mode = spring canal.instance.global.lazy = false #canal.instance.global.manager.address = 127.0.0.1:1099 -#canal.instance.global.spring.xml = classpath:spring/local-instance.xml #canal.instance.global.spring.xml = classpath:spring/memory-instance.xml canal.instance.global.spring.xml = classpath:spring/file-instance.xml #canal.instance.global.spring.xml = classpath:spring/default-instance.xml diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index e0cf0064..f72b65fe 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -12,6 +12,11 @@ canal.instance.master.position= canal.instance.master.timestamp= canal.instance.master.gtid= +# rds oss binlog +canal.instance.rds.accesskey= +canal.instance.rds.secretkey= +canal.instance.rds.instanceId= + # table meta tsdb info canal.instance.tsdb.enable=true #canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb diff --git a/deployer/src/main/resources/example/rds_instance.properties b/deployer/src/main/resources/example/rds_instance.properties deleted file mode 100644 index f04b3832..00000000 --- a/deployer/src/main/resources/example/rds_instance.properties +++ /dev/null @@ -1,32 +0,0 @@ -################################################# -# rds openapi binlog -canal.instance.rds.open.url=https://rds.aliyuncs.com/ -canal.instance.rds.open.accesskey= -canal.instance.rds.open.secretkey= -canal.instance.rds.instanceId= -canal.instance.rds.startTime= -canal.instance.rds.endTime= - -# local binlog dir -canal.instance.parser.directory=${canal.file.data.dir:../conf}/${canal.instance.destination:}/binlog -# position info -canal.instance.master.address=127.0.0.1:3306 - -# table meta tsdb info -canal.instance.tsdb.enable=true -canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:} -canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; -#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb -#canal.instance.tsdb.dbUsername=canal -#canal.instance.tsdb.dbPassword=canal - -# username/password -canal.instance.dbUsername=canal -canal.instance.dbPassword=canal -canal.instance.defaultDatabaseName=test -canal.instance.connectionCharset=UTF-8 -# table regex -canal.instance.filter.regex=test\\..* -# table black regex -canal.instance.filter.black.regex= -################################################# \ No newline at end of file diff --git a/deployer/src/main/resources/spring/base-instance.xml b/deployer/src/main/resources/spring/base-instance.xml new file mode 100644 index 00000000..e6c500b5 --- /dev/null +++ b/deployer/src/main/resources/spring/base-instance.xml @@ -0,0 +1,39 @@ + + + + + + + + + + classpath:canal.properties + classpath:${canal.instance.destination:}/instance.properties + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index 6b455c83..07b9109d 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -84,7 +65,7 @@ - + diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 377428a9..58dd32c9 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -70,7 +51,7 @@ - + diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index 44f1ab3b..47d3ba33 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -76,7 +57,7 @@ - + @@ -174,7 +155,7 @@ - + diff --git a/deployer/src/main/resources/spring/local-instance.xml b/deployer/src/main/resources/spring/local-instance.xml deleted file mode 100644 index 930ecb7b..00000000 --- a/deployer/src/main/resources/spring/local-instance.xml +++ /dev/null @@ -1,147 +0,0 @@ - - - - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index cd387b8f..428454db 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -10,26 +10,7 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" default-autowire="byName"> - - - - - - - classpath:canal.properties - classpath:${canal.instance.destination:}/instance.properties - - - - - - - - - - - - + @@ -67,7 +48,7 @@ - + diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java index 35a6b790..05baee02 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java @@ -8,6 +8,8 @@ import com.alibaba.otter.canal.common.CanalException; */ public class ServerIdNotMatchException extends CanalException{ + private static final long serialVersionUID = -6124989280379293953L; + public ServerIdNotMatchException(String errorCode) { super(errorCode); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index cd50afa4..fcae1317 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -8,8 +8,6 @@ import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; - import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.math.RandomUtils; @@ -23,6 +21,7 @@ import com.alibaba.otter.canal.filter.CanalEventFilter; import com.alibaba.otter.canal.parse.CanalEventParser; import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import com.alibaba.otter.canal.parse.exception.CanalParseException; +import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException; import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback; import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; @@ -97,10 +96,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle protected int parallelBufferSize = 256; // 必须为2的幂 protected MultiStageCoprocessor multiStageCoprocessor; protected ParserExceptionHandler parserExceptionHandler; - protected long serverId; - - - + protected long serverId; protected abstract BinlogParser buildParser(); @@ -179,7 +175,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle erosaConnection.connect();// 链接 long queryServerId = erosaConnection.queryServerId(); - if (queryServerId != 0){ + if (queryServerId != 0) { serverId = queryServerId; } // 4. 获取最后的位置信息 @@ -289,7 +285,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle runningInfo.getAddress().toString()), e); sendAlarm(destination, ExceptionUtils.getFullStackTrace(e)); } - if (parserExceptionHandler!=null){ + if (parserExceptionHandler != null) { parserExceptionHandler.handle(e); } } finally { @@ -441,7 +437,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle return logPosition; } - protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, long startPosition) { + protected void processSinkError(Throwable e, LogPosition lastPosition, String startBinlogFile, Long startPosition) { if (lastPosition != null) { logger.warn(String.format("ERROR ## parse this event has an error , last position : [%s]", lastPosition.getPostion()), diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java index 96e063e9..4ab68e39 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/MultiStageCoprocessor.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.parse.inbound; import com.alibaba.otter.canal.common.CanalLifeCycle; import com.taobao.tddl.dbsync.binlog.LogBuffer; +import com.taobao.tddl.dbsync.binlog.LogEvent; /** * 针对解析器提供一个多阶段协同的处理 @@ -23,7 +24,7 @@ public interface MultiStageCoprocessor extends CanalLifeCycle { */ public boolean publish(LogBuffer buffer); - public boolean publish(LogBuffer buffer, String binlogFileName); + public boolean publish(LogEvent event); public void reset(); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index db21a9e1..941f5ebc 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -134,10 +134,10 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { protected MultiStageCoprocessor buildMultiStageCoprocessor() { MysqlMultiStageCoprocessor mysqlMultiStageCoprocessor = new MysqlMultiStageCoprocessor(parallelBufferSize, - parallelThreadSize, - (LogEventConvert) binlogParser, - transactionBuffer, - destination); + parallelThreadSize, + (LogEventConvert) binlogParser, + transactionBuffer, + destination); mysqlMultiStageCoprocessor.setEventsPublishBlockingTime(eventsPublishBlockingTime); return mysqlMultiStageCoprocessor; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java index 0358dfda..5cf5bb9d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinLogConnection.java @@ -4,25 +4,24 @@ import java.io.File; import java.io.IOException; import java.util.List; -import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException; import org.apache.commons.lang.NotImplementedException; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; +import com.alibaba.otter.canal.parse.exception.CanalParseException; +import com.alibaba.otter.canal.parse.exception.ServerIdNotMatchException; import com.alibaba.otter.canal.parse.inbound.ErosaConnection; import com.alibaba.otter.canal.parse.inbound.MultiStageCoprocessor; import com.alibaba.otter.canal.parse.inbound.SinkFunction; import com.alibaba.otter.canal.parse.inbound.mysql.local.BinLogFileQueue; import com.taobao.tddl.dbsync.binlog.FileLogFetcher; -import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogContext; import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogEvent; import com.taobao.tddl.dbsync.binlog.LogPosition; import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; -import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent; /** * local bin log connection (not real connection) @@ -40,7 +39,6 @@ public class LocalBinLogConnection implements ErosaConnection { private long serverId; private FileParserListener parserListener; - public LocalBinLogConnection(){ } @@ -92,16 +90,13 @@ public class LocalBinLogConnection implements ErosaConnection { while (running) { boolean needContinue = true; LogEvent event = null; - // 处理一下binlog文件名 - event = new RotateLogEvent(context.getLogPosition().getFileName(), 4); - func.sink(event); while (fetcher.fetch()) { event = decoder.decode(fetcher, context); if (event == null) { continue; } - if (serverId != 0 && event.getServerId() != serverId){ - throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); } if (!func.sink(event)) { @@ -168,9 +163,8 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogEvent event = decoder.decode(fetcher, context); if (event != null) { - - if (serverId != 0 && event.getServerId() != serverId){ - throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); } if (event.getWhen() > timestampSeconds) { @@ -189,6 +183,9 @@ public class LocalBinLogConnection implements ErosaConnection { } else if (LogEvent.XID_EVENT == event.getHeader().getType()) { lastXidLogFilename = current.getName(); lastXidLogFileOffset = event.getLogPos(); + } else if (LogEvent.FORMAT_DESCRIPTION_EVENT == event.getHeader().getType()) { + lastXidLogFilename = current.getName(); + lastXidLogFileOffset = event.getLogPos(); } } } @@ -225,17 +222,29 @@ public class LocalBinLogConnection implements ErosaConnection { @Override public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException { File current = new File(directory, binlogfilename); + if (!current.exists()) { + throw new CanalParseException("binlog:" + binlogfilename + " is not found"); + } FileLogFetcher fetcher = new FileLogFetcher(bufferSize); + LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); + LogContext context = new LogContext(); try { fetcher.open(current, binlogPosition); + context.setLogPosition(new LogPosition(binlogfilename, binlogPosition)); while (running) { boolean needContinue = true; + LogEvent event = null; while (fetcher.fetch()) { - LogBuffer buffer = fetcher.duplicate(); - fetcher.consume(fetcher.limit()); - // set filename - if (!coprocessor.publish(buffer, binlogfilename)) { + event = decoder.decode(fetcher, context); + if (event == null) { + continue; + } + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); + } + + if (!coprocessor.publish(event)) { needContinue = false; break; } @@ -244,7 +253,6 @@ public class LocalBinLogConnection implements ErosaConnection { fetcher.close(); // 关闭上一个文件 parserFinish(binlogfilename); if (needContinue) {// 读取下一个 - File nextFile; if (needWait) { nextFile = binlogs.waitForNextFile(current); @@ -272,8 +280,8 @@ public class LocalBinLogConnection implements ErosaConnection { } } - private void parserFinish(String fileName){ - if (parserListener != null){ + private void parserFinish(String fileName) { + if (parserListener != null) { parserListener.onFinish(fileName); } } @@ -306,9 +314,8 @@ public class LocalBinLogConnection implements ErosaConnection { while (fetcher.fetch()) { LogEvent event = decoder.decode(fetcher, context); if (event != null) { - - if (serverId != 0 && event.getServerId() != serverId){ - throw new ServerIdNotMatchException("unexpected serverId "+serverId + " in binlog file !"); + if (serverId != 0 && event.getServerId() != serverId) { + throw new ServerIdNotMatchException("unexpected serverId " + serverId + " in binlog file !"); } if (event.getWhen() > timestampSeconds) { @@ -327,6 +334,9 @@ public class LocalBinLogConnection implements ErosaConnection { } else if (LogEvent.XID_EVENT == event.getHeader().getType()) { lastXidLogFilename = current.getName(); lastXidLogFileOffset = event.getLogPos(); + } else if (LogEvent.FORMAT_DESCRIPTION_EVENT == event.getHeader().getType()) { + lastXidLogFilename = current.getName(); + lastXidLogFileOffset = event.getLogPos(); } } } @@ -410,7 +420,8 @@ public class LocalBinLogConnection implements ErosaConnection { this.parserListener = parserListener; } - public interface FileParserListener{ + public interface FileParserListener { + void onFinish(String fileName); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index 54483e1f..eb3c0e31 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -13,7 +13,6 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.util.CollectionUtils; import com.alibaba.otter.canal.parse.driver.mysql.MysqlConnector; import com.alibaba.otter.canal.parse.driver.mysql.MysqlQueryExecutor; @@ -40,20 +39,20 @@ import com.taobao.tddl.dbsync.binlog.LogEvent; public class MysqlConnection implements ErosaConnection { - private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class); + 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; + 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秒 - protected int soTimeout = 60 * 60 * 1000; // 1小时 + private AuthenticationInfo authInfo; + protected int connTimeout = 5 * 1000; // 5秒 + protected int soTimeout = 60 * 60 * 1000; // 1小时 // dump binlog bytes, 暂不包括meta与TSDB - private AtomicLong receivedBinlogBytes; + private AtomicLong receivedBinlogBytes; public MysqlConnection(){ } @@ -338,7 +337,7 @@ public class MysqlConnection implements ErosaConnection { public long queryServerId() throws IOException { ResultSetPacket resultSetPacket = query("show variables like 'server_id'"); List fieldValues = resultSetPacket.getFieldValues(); - if (fieldValues == null || fieldValues.size() != 2){ + if (fieldValues == null || fieldValues.size() != 2) { return 0; } return NumberUtils.toLong(fieldValues.get(1)); @@ -478,8 +477,6 @@ public class MysqlConnection implements ErosaConnection { } } - - public static enum BinlogFormat { STATEMENT("STATEMENT"), ROW("ROW"), MIXED("MIXED"); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index fefb1cbe..b585d9ab 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -47,32 +47,32 @@ import com.taobao.tddl.dbsync.binlog.LogEvent; */ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalEventParser, CanalHASwitchable { - private CanalHAController haController = null; + private CanalHAController haController = null; - private int defaultConnectionTimeoutInSeconds = 30; // sotimeout - private int receiveBufferSize = 64 * 1024; - private int sendBufferSize = 64 * 1024; + private int defaultConnectionTimeoutInSeconds = 30; // sotimeout + private int receiveBufferSize = 64 * 1024; + private int sendBufferSize = 64 * 1024; // 数据库信息 - protected AuthenticationInfo masterInfo; // 主库 - protected AuthenticationInfo standbyInfo; // 备库 + protected AuthenticationInfo masterInfo; // 主库 + protected AuthenticationInfo standbyInfo; // 备库 // binlog信息 protected EntryPosition masterPosition; protected EntryPosition standbyPosition; - private long slaveId; // 链接到mysql的slave + private long slaveId; // 链接到mysql的slave // 心跳检查信息 - private String detectingSQL; // 心跳sql - private MysqlConnection metaConnection; // 查询meta信息的链接 - private TableMetaCache tableMetaCache; // 对应meta - private int fallbackIntervalInSeconds = 60; // 切换回退时间 - private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 - private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 + private String detectingSQL; // 心跳sql + private MysqlConnection metaConnection; // 查询meta信息的链接 + private TableMetaCache tableMetaCache; // 对应meta + private int fallbackIntervalInSeconds = 60; // 切换回退时间 + private BinlogFormat[] supportBinlogFormats; // 支持的binlogFormat,如果设置会执行强校验 + private BinlogImage[] supportBinlogImages; // 支持的binlogImage,如果设置会执行强校验 // update by yishun.chen,特殊异常处理参数 - private int dumpErrorCount = 0; // binlogDump失败异常计数 - private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 + private int dumpErrorCount = 0; // binlogDump失败异常计数 + private int dumpErrorCountThreshold = 2; // binlogDump失败异常计数阀值 // instance received binlog bytes - private final AtomicLong receivedBinlogBytes = new AtomicLong(0L); + private final AtomicLong receivedBinlogBytes = new AtomicLong(0L); protected ErosaConnection buildErosaConnection() { return buildMysqlConnection(this.runningInfo); @@ -756,16 +756,9 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE Long logposTimestamp = entry.getHeader().getExecuteTime(); Long serverId = entry.getHeader().getServerId(); - if (CanalEntry.EntryType.TRANSACTIONBEGIN.equals(entry.getEntryType()) - || CanalEntry.EntryType.TRANSACTIONEND.equals(entry.getEntryType())) { - if (logger.isDebugEnabled()) { - logger.debug("compare exit condition:{},{},{}, startTimestamp={}...", new Object[] { - logfilename, logfileoffset, logposTimestamp, startTimestamp }); - } - // 事务头和尾寻找第一条记录时间戳,如果最小的一条记录都不满足条件,可直接退出 - if (logposTimestamp >= startTimestamp) { - return false; - } + // 如果最小的一条记录都不满足条件,可直接退出 + if (logposTimestamp >= startTimestamp) { + return false; } if (StringUtils.equals(endPosition.getJournalName(), logfilename) @@ -914,8 +907,6 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE this.dumpErrorCountThreshold = dumpErrorCountThreshold; } - - public AtomicLong getReceivedBinlogBytes() { return this.receivedBinlogBytes; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 31dfc595..63b0f0eb 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -5,8 +5,6 @@ import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.LockSupport; -import org.apache.commons.lang.StringUtils; - import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; import com.alibaba.otter.canal.common.utils.NamedThreadFactory; import com.alibaba.otter.canal.parse.exception.CanalParseException; @@ -32,7 +30,6 @@ import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogContext; import com.taobao.tddl.dbsync.binlog.LogDecoder; import com.taobao.tddl.dbsync.binlog.LogEvent; -import com.taobao.tddl.dbsync.binlog.LogPosition; import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; @@ -53,7 +50,7 @@ import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; */ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implements MultiStageCoprocessor { - private static final int maxFullTimes = 10; + private static final int maxFullTimes = 10; private LogEventConvert logEventConvert; private EventTransactionBuffer transactionBuffer; private ErosaConnection connection; @@ -141,14 +138,18 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement super.stop(); } + public boolean publish(LogBuffer buffer) { + return this.publish(buffer, null); + } + /** * 网络数据投递 */ - public boolean publish(LogBuffer buffer) { - return publish(buffer, null); + public boolean publish(LogEvent event) { + return this.publish(null, event); } - public boolean publish(LogBuffer buffer, String binlogFileName) { + private boolean publish(LogBuffer buffer, LogEvent event) { if (!isStart()) { if (exception != null) { throw exception; @@ -169,10 +170,11 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement do { try { long next = disruptorMsgBuffer.tryNext(); - MessageEvent event = disruptorMsgBuffer.get(next); - event.setBuffer(buffer); - if (binlogFileName != null) { - event.setBinlogFileName(binlogFileName); + MessageEvent data = disruptorMsgBuffer.get(next); + if (buffer != null) { + data.setBuffer(buffer); + } else { + data.setEvent(event); } disruptorMsgBuffer.publish(next); if (fullTimes > 0) { @@ -184,7 +186,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement blockingStart = System.nanoTime(); } // park - //LockSupport.parkNanos(1L); + // LockSupport.parkNanos(1L); applyWait(++fullTimes); interupted = Thread.interrupted(); if (fullTimes % 1000 == 0) { @@ -217,7 +219,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement start(); } - private class SimpleParserStage implements EventHandler, LifecycleAware { private LogDecoder decoder; @@ -230,23 +231,13 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception { try { - LogBuffer buffer = event.getBuffer(); - if (StringUtils.isNotEmpty(event.getBinlogFileName()) - && (context.getLogPosition() == null || !context.getLogPosition() - .getFileName() - .equals(event.getBinlogFileName()))) { - // set roate binlog file name - if (context.getLogPosition() == null) { - context.setLogPosition(new LogPosition(event.getBinlogFileName(), 0)); - } else { - context.setLogPosition(new LogPosition(event.getBinlogFileName(), context.getLogPosition() - .getPosition())); - } + LogEvent logEvent = event.getEvent(); + if (logEvent == null) { + LogBuffer buffer = event.getBuffer(); + logEvent = decoder.decode(buffer, context); + event.setEvent(logEvent); } - LogEvent logEvent = decoder.decode(buffer, context); - event.setEvent(logEvent); - int eventType = logEvent.getHeader().getType(); TableMeta tableMeta = null; boolean needDmlParse = false; @@ -347,7 +338,6 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement // clear for gc event.setBuffer(null); - event.setBinlogFileName(null); event.setEvent(null); event.setTable(null); event.setEntry(null); @@ -371,21 +361,12 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement class MessageEvent { - private String binlogFileName; // for local binlog parse private LogBuffer buffer; private CanalEntry.Entry entry; private boolean needDmlParse = false; private TableMeta table; private LogEvent event; - public String getBinlogFileName() { - return binlogFileName; - } - - public void setBinlogFileName(String binlogFileName) { - this.binlogFileName = binlogFileName; - } - public LogBuffer getBuffer() { return buffer; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index f5ff93ab..e161e34e 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -10,7 +10,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; -import com.taobao.tddl.dbsync.binlog.event.*; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.Logger; @@ -41,7 +40,23 @@ import com.alibaba.otter.canal.protocol.CanalEntry.Type; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.google.protobuf.ByteString; import com.taobao.tddl.dbsync.binlog.LogEvent; +import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent; +import com.taobao.tddl.dbsync.binlog.event.HeartbeatLogEvent; +import com.taobao.tddl.dbsync.binlog.event.IntvarLogEvent; +import com.taobao.tddl.dbsync.binlog.event.LogHeader; +import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RandLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer; +import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent; +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent.ColumnInfo; +import com.taobao.tddl.dbsync.binlog.event.UnknownLogEvent; +import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.UserVarLogEvent; +import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent; +import com.taobao.tddl.dbsync.binlog.event.XidLogEvent; import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; /** @@ -872,11 +887,14 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private TableMeta getTableMeta(String dbName, String tbName, boolean useCache, EntryPosition position) { try { return tableMetaCache.getTableMeta(dbName, tbName, useCache, position); - } catch (Exception e) { + } catch (Throwable e) { String message = ExceptionUtils.getRootCauseMessage(e); if (filterTableError) { if (StringUtils.contains(message, "errorNumber=1146") && StringUtils.contains(message, "doesn't exist")) { return null; + } else if (StringUtils.contains(message, "errorNumber=1142") + && StringUtils.contains(message, "command denied")) { + return null; } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java index 224852cc..eb227303 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/local/BinLogFileQueue.java @@ -25,13 +25,14 @@ import com.alibaba.otter.canal.parse.exception.CanalParseException; */ public class BinLogFileQueue { - private String baseName = "mysql-bin."; - private List binlogs = new ArrayList(); - private File directory; - private ReentrantLock lock = new ReentrantLock(); - private Condition nextCondition = lock.newCondition(); - private Timer timer = new Timer(true); - private long reloadInterval = 10 * 1000L; // 10秒 + private String baseName = "mysql-bin."; + private List binlogs = new ArrayList(); + private File directory; + private ReentrantLock lock = new ReentrantLock(); + private Condition nextCondition = lock.newCondition(); + private Timer timer = new Timer(true); + private long reloadInterval = 10 * 1000L; // 10秒 + private CanalParseException exception = null; public BinLogFileQueue(String directory){ this(new File(directory)); @@ -52,9 +53,24 @@ public class BinLogFileQueue { timer.scheduleAtFixedRate(new TimerTask() { public void run() { - List files = listBinlogFiles(); - for (File file : files) { - offer(file); + try { + // File errorFile = new File(BinLogFileQueue.this.directory, + // errorFileName); + // if (errorFile.isFile() && errorFile.exists()) { + // String text = StringUtils.join(IOUtils.readLines(new + // FileInputStream(errorFile)), "\n"); + // exception = new CanalParseException(text); + // } + List files = listBinlogFiles(); + for (File file : files) { + offer(file); + } + } catch (Throwable e) { + exception = new CanalParseException(e); + } + + if (exception != null) { + offer(null); } } }, reloadInterval, reloadInterval); @@ -69,6 +85,10 @@ public class BinLogFileQueue { public File getNextFile(File pre) { try { lock.lockInterruptibly(); + if (exception != null) { + throw exception; + } + if (binlogs.size() == 0) { return null; } else { @@ -94,6 +114,10 @@ public class BinLogFileQueue { public File getBefore(File file) { try { lock.lockInterruptibly(); + if (exception != null) { + throw exception; + } + if (binlogs.size() == 0) { return null; } else { @@ -130,6 +154,9 @@ public class BinLogFileQueue { nextCondition.await();// 等待新文件 } + if (exception != null) { + throw exception; + } if (pre == null) {// 第一次 return binlogs.get(0); } else { @@ -170,13 +197,16 @@ public class BinLogFileQueue { private boolean offer(File file) { try { lock.lockInterruptibly(); - if (!binlogs.contains(file)) { - binlogs.add(file); - nextCondition.signalAll();// 唤醒 - return true; - } else { - return false; + if (file != null) { + if (!binlogs.contains(file)) { + binlogs.add(file); + nextCondition.signalAll();// 唤醒 + return true; + } } + + nextCondition.signalAll();// 唤醒 + return false; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java index d1716e18..e132514b 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/BinlogDownloadQueue.java @@ -7,6 +7,8 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; @@ -14,6 +16,9 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; + +import javax.net.ssl.SSLContext; import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; @@ -23,8 +28,16 @@ import org.apache.commons.lang.StringUtils; import org.apache.http.HttpResponse; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.HttpGet; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.TrustStrategy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,7 +55,7 @@ public class BinlogDownloadQueue { private LinkedBlockingQueue downloadQueue = new LinkedBlockingQueue(); private LinkedBlockingQueue taskQueue = new LinkedBlockingQueue(); private LinkedList binlogList; - private final int batchSize; + private final int batchFileSize; private Thread downloadThread; public boolean running = true; private final String destDir; @@ -50,9 +63,9 @@ public class BinlogDownloadQueue { private int currentSize; private String lastDownload; - public BinlogDownloadQueue(List downloadQueue, int batchSize, String destDir) throws IOException{ + public BinlogDownloadQueue(List downloadQueue, int batchFileSize, String destDir) throws IOException{ this.binlogList = new LinkedList(downloadQueue); - this.batchSize = batchSize; + this.batchFileSize = batchFileSize; this.destDir = destDir; this.currentSize = 0; prepareBinlogList(); @@ -83,11 +96,12 @@ public class BinlogDownloadQueue { if (downloadThread != null) { return; } - downloadThread = new Thread(new DownloadThread()); + downloadThread = new Thread(new DownloadThread(), "download-" + destDir); + downloadThread.setDaemon(true); downloadThread.start(); } - public BinlogFile tryOne() throws IOException { + public BinlogFile tryOne() throws Throwable { BinlogFile binlogFile = binlogList.poll(); download(binlogFile); hostId = binlogFile.getHostInstanceID(); @@ -121,7 +135,7 @@ public class BinlogDownloadQueue { } public void prepare() throws InterruptedException { - for (int i = this.currentSize; i < batchSize && !binlogList.isEmpty(); i++) { + for (int i = this.currentSize; i < batchFileSize && !binlogList.isEmpty(); i++) { BinlogFile binlogFile = null; while (!binlogList.isEmpty()) { binlogFile = binlogList.poll(); @@ -150,11 +164,37 @@ public class BinlogDownloadQueue { downloadQueue.clear(); } - private void download(BinlogFile binlogFile) throws IOException { + private void download(BinlogFile binlogFile) throws Throwable { String downloadLink = binlogFile.getDownloadLink(); String fileName = binlogFile.getFileName(); + + downloadLink = downloadLink.trim(); + CloseableHttpClient httpClient = null; + if (downloadLink.startsWith("https")) { + HttpClientBuilder builder = HttpClientBuilder.create(); + builder.setMaxConnPerRoute(50); + builder.setMaxConnTotal(100); + // 创建支持忽略证书的https + final SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() { + + @Override + public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException { + return true; + } + }).build(); + + httpClient = HttpClientBuilder.create() + .setSSLContext(sslContext) + .setConnectionManager(new PoolingHttpClientConnectionManager(RegistryBuilder. create() + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)) + .build())) + .build(); + } else { + httpClient = HttpClientBuilder.create().setMaxConnPerRoute(50).setMaxConnTotal(100).build(); + } + HttpGet httpGet = new HttpGet(downloadLink); - CloseableHttpClient httpClient = HttpClientBuilder.create().setMaxConnPerRoute(50).setMaxConnTotal(100).build(); RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(TIMEOUT) .setConnectionRequestTimeout(TIMEOUT) @@ -187,6 +227,9 @@ public class BinlogDownloadQueue { String name = tarArchiveEntry.getName(); File tarFile = new File(parentFile, name + ".tmp"); logger.info("start to download file " + tarFile.getName()); + if (tarFile.exists()) { + tarFile.delete(); + } BufferedOutputStream bos = null; try { bos = new BufferedOutputStream(new FileOutputStream(tarFile)); @@ -204,6 +247,10 @@ public class BinlogDownloadQueue { tais.close(); } else { File file = new File(parentFile, fileName + ".tmp"); + if (file.exists()) { + file.delete(); + } + if (!file.isFile()) { file.createNewFile(); } @@ -245,17 +292,45 @@ public class BinlogDownloadQueue { @Override public void run() { while (running) { + BinlogFile binlogFile = null; try { - BinlogFile binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS); + binlogFile = downloadQueue.poll(5000, TimeUnit.MILLISECONDS); if (binlogFile != null) { - download(binlogFile); + int retry = 1; + while (true) { + try { + download(binlogFile); + break; + } catch (Throwable e) { + if (retry % 10 == 0) { + retry = retry + 1; + try { + logger.warn("download failed + " + binlogFile.toString() + "], retry : " + + retry, e); + // File errorFile = new File(destDir, + // "error.txt"); + // FileWriter writer = new + // FileWriter(errorFile); + // writer.write(ExceptionUtils.getFullStackTrace(e)); + // writer.flush(); + // IOUtils.closeQuietly(writer); + LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100 * retry)); + } catch (Throwable e1) { + logger.error("write error failed", e1); + } + } else { + retry = retry + 1; + } + } + } } + Runnable runnable = taskQueue.poll(5000, TimeUnit.MILLISECONDS); if (runnable != null) { runnable.run(); } - } catch (Exception e) { - e.printStackTrace(); + } catch (Throwable e) { + logger.error("task process failed", e); } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java index 7b641ade..9ed865fe 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/HttpHelper.java @@ -155,6 +155,7 @@ public class HttpHelper { .register("https", new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE)) .build())) .build(); + // ---------------- 创建支持https 的client成功--------- URI uri = new URIBuilder(url).build(); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java index 88caca53..3c38ed0c 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogEventParserProxy.java @@ -4,93 +4,109 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import org.apache.commons.lang.StringUtils; + import com.alibaba.otter.canal.parse.exception.PositionNotFoundException; import com.alibaba.otter.canal.parse.inbound.ParserExceptionHandler; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; /** + * aliyun rds的binlog parser支持 + * + *

      + * 注意点:aliyun的binlog会有定期清理并备份到oss上, 这里实现了一份自动下载oss+rds binlog的机制
      + * 
      + * * @author chengjin.lyf on 2018/7/20 上午10:52 * @since 1.0.25 */ public class RdsBinlogEventParserProxy extends MysqlEventParser { - private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址 - private String accesskey; // 云账号的ak - private String secretkey; // 云账号sk - private String instanceId; // rds实例id - private Long startTime; - private Long endTime; - private String directory; // binlog - // 目录 - private int batchSize = 4; // 最多下载的binlog文件数量 + private String rdsOpenApiUrl = "https://rds.aliyuncs.com/"; // openapi地址 + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id + private String directory; // binlog目录 + private int batchFileSize = 4; // 最多下载的binlog文件数量 - private RdsLocalBinlogEventParser rdsBinlogEventParser = new RdsLocalBinlogEventParser(); - private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { + private RdsLocalBinlogEventParser rdsLocalBinlogEventParser = new RdsLocalBinlogEventParser(); + private ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread t = new Thread(r, "rds-binlog-daemon-thread"); - t.setDaemon(true); - return t; - } - }); + @Override + public Thread newThread(Runnable r) { + Thread t = new Thread(r, + "rds-binlog-daemon-thread"); + t.setDaemon(true); + return t; + } + }); @Override public void start() { - final ParserExceptionHandler targetHandler = this.getParserExceptionHandler(); - rdsBinlogEventParser.setLogPositionManager(this.getLogPositionManager()); - rdsBinlogEventParser.setDestination(destination); - rdsBinlogEventParser.setAlarmHandler(this.getAlarmHandler()); - rdsBinlogEventParser.setConnectionCharset(this.connectionCharset); - rdsBinlogEventParser.setConnectionCharsetNumber(this.connectionCharsetNumber); - rdsBinlogEventParser.setEnableTsdb(this.enableTsdb); - rdsBinlogEventParser.setEventBlackFilter(this.eventBlackFilter); - rdsBinlogEventParser.setFilterQueryDcl(this.filterQueryDcl); - rdsBinlogEventParser.setFilterQueryDdl(this.filterQueryDdl); - rdsBinlogEventParser.setFilterQueryDml(this.filterQueryDml); - rdsBinlogEventParser.setFilterRows(this.filterRows); - rdsBinlogEventParser.setFilterTableError(this.filterTableError); - rdsBinlogEventParser.setIsGTIDMode(this.isGTIDMode); - rdsBinlogEventParser.setMasterInfo(this.masterInfo); - rdsBinlogEventParser.setEventFilter(this.eventFilter); - rdsBinlogEventParser.setMasterPosition(this.masterPosition); - rdsBinlogEventParser.setTransactionSize(this.transactionSize); - rdsBinlogEventParser.setUrl(this.rdsOpenApiUrl); - rdsBinlogEventParser.setAccesskey(this.accesskey); - rdsBinlogEventParser.setSecretkey(this.secretkey); - rdsBinlogEventParser.setInstanceId(this.instanceId); - rdsBinlogEventParser.setEventSink(eventSink); - rdsBinlogEventParser.setDirectory(directory); - rdsBinlogEventParser.setBatchSize(batchSize); - rdsBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() { - - @Override - public void onFinish() { - executorService.execute(new Runnable() { - - @Override - public void run() { - rdsBinlogEventParser.stop(); - RdsBinlogEventParserProxy.this.start(); - } - }); - + if (StringUtils.isNotEmpty(accesskey) && StringUtils.isNotEmpty(secretkey) + && StringUtils.isNotEmpty(instanceId)) { + final ParserExceptionHandler targetHandler = this.getParserExceptionHandler(); + if (directory == null) { + directory = System.getProperty("java.io.tmpdir", "/tmp") + "/" + destination; } - }); - this.setParserExceptionHandler(new ParserExceptionHandler() { + rdsLocalBinlogEventParser.setLogPositionManager(this.getLogPositionManager()); + rdsLocalBinlogEventParser.setDestination(destination); + rdsLocalBinlogEventParser.setAlarmHandler(this.getAlarmHandler()); + rdsLocalBinlogEventParser.setConnectionCharset(this.connectionCharset); + rdsLocalBinlogEventParser.setConnectionCharsetNumber(this.connectionCharsetNumber); + rdsLocalBinlogEventParser.setEnableTsdb(this.enableTsdb); + rdsLocalBinlogEventParser.setEventBlackFilter(this.eventBlackFilter); + rdsLocalBinlogEventParser.setFilterQueryDcl(this.filterQueryDcl); + rdsLocalBinlogEventParser.setFilterQueryDdl(this.filterQueryDdl); + rdsLocalBinlogEventParser.setFilterQueryDml(this.filterQueryDml); + rdsLocalBinlogEventParser.setFilterRows(this.filterRows); + rdsLocalBinlogEventParser.setFilterTableError(this.filterTableError); + // rdsLocalBinlogEventParser.setIsGTIDMode(this.isGTIDMode); + rdsLocalBinlogEventParser.setMasterInfo(this.masterInfo); + rdsLocalBinlogEventParser.setEventFilter(this.eventFilter); + rdsLocalBinlogEventParser.setMasterPosition(this.masterPosition); + rdsLocalBinlogEventParser.setTransactionSize(this.transactionSize); + rdsLocalBinlogEventParser.setUrl(this.rdsOpenApiUrl); + rdsLocalBinlogEventParser.setAccesskey(this.accesskey); + rdsLocalBinlogEventParser.setSecretkey(this.secretkey); + rdsLocalBinlogEventParser.setInstanceId(this.instanceId); + rdsLocalBinlogEventParser.setEventSink(eventSink); + rdsLocalBinlogEventParser.setDirectory(directory); + rdsLocalBinlogEventParser.setBatchFileSize(batchFileSize); + rdsLocalBinlogEventParser.setParallel(this.parallel); + rdsLocalBinlogEventParser.setParallelBufferSize(this.parallelBufferSize); + rdsLocalBinlogEventParser.setParallelThreadSize(this.parallelThreadSize); + rdsLocalBinlogEventParser.setFinishListener(new RdsLocalBinlogEventParser.ParseFinishListener() { + + @Override + public void onFinish() { + executorService.execute(new Runnable() { + + @Override + public void run() { + rdsLocalBinlogEventParser.stop(); + RdsBinlogEventParserProxy.this.start(); + } + }); - @Override - public void handle(Throwable e) { - handleMysqlParserException(e); - if (targetHandler != null) { - targetHandler.handle(e); } - } - }); + }); + this.setParserExceptionHandler(new ParserExceptionHandler() { + + @Override + public void handle(Throwable e) { + handleMysqlParserException(e); + if (targetHandler != null) { + targetHandler.handle(e); + } + } + }); + } + super.start(); } - public void handleMysqlParserException(Throwable throwable) { + private void handleMysqlParserException(Throwable throwable) { if (throwable instanceof PositionNotFoundException) { logger.info("remove rds not found position, try download rds binlog!"); executorService.execute(new Runnable() { @@ -101,12 +117,12 @@ public class RdsBinlogEventParserProxy extends MysqlEventParser { logger.info("stop mysql parser!"); RdsBinlogEventParserProxy rdsBinlogEventParserProxy = RdsBinlogEventParserProxy.this; long serverId = rdsBinlogEventParserProxy.getServerId(); - rdsBinlogEventParser.setServerId(serverId); + rdsLocalBinlogEventParser.setServerId(serverId); rdsBinlogEventParserProxy.stop(); logger.info("start rds mysql binlog parser!"); - rdsBinlogEventParser.start(); - } catch (Exception e) { - e.printStackTrace(); + rdsLocalBinlogEventParser.start(); + } catch (Throwable e) { + logger.info("handle exception failed", e); } } }); @@ -143,7 +159,8 @@ public class RdsBinlogEventParserProxy extends MysqlEventParser { this.directory = directory; } - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; + public void setBatchFileSize(int batchFileSize) { + this.batchFileSize = batchFileSize; } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java index 356a3520..2e79c536 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsBinlogOpenApi.java @@ -1,51 +1,20 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds; -import io.netty.handler.codec.http.HttpResponseStatus; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.TimeZone; -import java.util.TreeMap; -import java.util.UUID; -import javax.crypto.Mac; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.compress.archivers.tar.TarArchiveEntry; -import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONArray; -import com.alibaba.fastjson.JSONObject; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsItem; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBackupPolicyRequest; import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFilesRequest; /** @@ -54,13 +23,7 @@ import com.alibaba.otter.canal.parse.inbound.mysql.rds.request.DescribeBinlogFil */ public class RdsBinlogOpenApi { - protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class); - private static final String ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"; - private static final int TIMEOUT = 10000; - private static final String ENCODING = "UTF-8"; - private static final String MAC_NAME = "HmacSHA1"; - private static final String API_VERSION = "2014-08-15"; - private static final String SIGNATURE_VERSION = "1.0"; + protected static final Logger logger = LoggerFactory.getLogger(RdsBinlogOpenApi.class); public static List listBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, Date endTime) { @@ -106,279 +69,32 @@ public class RdsBinlogOpenApi { return Collections.EMPTY_LIST; } - public static void downloadBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, - Date endTime, File destDir) throws Throwable { - int pageSize = 100; - int pageNumber = 0; - int pageRecordCount = 1; - String hostInstanceID = null; - while (pageRecordCount > 0 && pageRecordCount <= pageSize) { - pageNumber += 1; - String result = describeBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime, pageSize, pageNumber); - JSONObject jsobObj = JSON.parseObject(result); - pageRecordCount = jsobObj.getInteger("PageRecordCount"); - - if (pageRecordCount > 0) { - FileUtils.forceMkdir(destDir); - File hostIdFile = new File(destDir, "hostId"); - if (hostIdFile.exists()) { - List lines = IOUtils.readLines(new FileInputStream(hostIdFile)); - hostInstanceID = StringUtils.join(lines, "\n"); - } - - String itemStr = jsobObj.getString("Items"); - JSONObject binLogFileObj = JSONObject.parseObject(itemStr); - JSONArray items = binLogFileObj.getJSONArray("BinLogFile"); - if (items == null || items.isEmpty()) { - continue; - } - for (int i = 0; i < items.size(); i++) { - JSONObject item = (JSONObject) items.get(i); - String oneHostInstanceID = item.getString("HostInstanceID"); - if (hostInstanceID == null) { - hostInstanceID = oneHostInstanceID; - FileOutputStream hostIdFileOut = null; - try { - hostIdFileOut = new FileOutputStream(hostIdFile); - hostIdFileOut.write(oneHostInstanceID.getBytes()); - hostIdFileOut.flush(); - } finally { - IOUtils.closeQuietly(hostIdFileOut); - } - } - - if (hostInstanceID.equals(oneHostInstanceID)) { // 只选择一个host下载 - String downloadLink = item.getString("DownloadLink"); - String fileName = StringUtils.substringBetween(downloadLink, "mysql-bin.", ".tar"); - if (StringUtils.isNotEmpty(fileName)) { - File currentFile = new File(destDir, "mysql-bin." + fileName); - if (currentFile.isFile() && currentFile.exists()) { - // 检查一下文件是否存在,存在就就没必要下载了 - continue; - } - } - - HttpGet httpGet = new HttpGet(downloadLink); - CloseableHttpClient httpClient = HttpClientBuilder.create() - .setMaxConnPerRoute(50) - .setMaxConnTotal(100) - .build(); - RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(TIMEOUT) - .setConnectionRequestTimeout(TIMEOUT) - .setSocketTimeout(TIMEOUT) - .build(); - httpGet.setConfig(requestConfig); - HttpResponse response = httpClient.execute(httpGet); - int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode != HttpResponseStatus.OK.code()) { - throw new RuntimeException("download failed , url:" + downloadLink + " , statusCode:" - + statusCode); - } - saveFile(destDir, response); - } - } + public static RdsBackupPolicy queryBinlogBackupPolicy(String url, String ak, String sk, String dbInstanceId) { + DescribeBackupPolicyRequest request = new DescribeBackupPolicyRequest(); + if (StringUtils.isNotEmpty(url)) { + try { + URI uri = new URI(url); + request.setEndPoint(uri.getHost()); + } catch (URISyntaxException e) { + logger.error("resolve url host failed, will use default rds endpoint!"); } } - } - - private static void saveFile(File parentFile, HttpResponse response) throws IOException { - InputStream is = response.getEntity().getContent(); - long totalSize = Long.parseLong(response.getFirstHeader("Content-Length").getValue()); - String fileName = response.getFirstHeader("Content-Disposition").getValue(); - fileName = StringUtils.substringAfter(fileName, "filename="); - boolean isTar = StringUtils.endsWith(fileName, ".tar"); - FileUtils.forceMkdir(parentFile); - FileOutputStream fos = null; - try { - if (isTar) { - TarArchiveInputStream tais = new TarArchiveInputStream(is); - TarArchiveEntry tarArchiveEntry = null; - while ((tarArchiveEntry = tais.getNextTarEntry()) != null) { - String name = tarArchiveEntry.getName(); - File tarFile = new File(parentFile, name); - logger.info("start to download file " + tarFile.getName()); - BufferedOutputStream bos = null; - try { - bos = new BufferedOutputStream(new FileOutputStream(tarFile)); - int read = -1; - byte[] buffer = new byte[1024]; - while ((read = tais.read(buffer)) != -1) { - bos.write(buffer, 0, read); - } - logger.info("download file " + tarFile.getName() + " end!"); - } finally { - IOUtils.closeQuietly(bos); - } - } - tais.close(); - } else { - File file = new File(parentFile, fileName); - if (!file.isFile()) { - file.createNewFile(); + request.setRdsInstanceId(dbInstanceId); + request.setAccessKeyId(ak); + request.setAccessKeySecret(sk); + int retryTime = 3; + while (true) { + try { + return request.doAction(); + } catch (Exception e) { + if (retryTime-- <= 0) { + throw new RuntimeException(e); } try { - fos = new FileOutputStream(file); - byte[] buffer = new byte[1024]; - int len; - long copySize = 0; - long nextPrintProgress = 0; - logger.info("start to download file " + file.getName()); - while ((len = is.read(buffer)) != -1) { - fos.write(buffer, 0, len); - copySize += len; - long progress = copySize * 100 / totalSize; - if (progress >= nextPrintProgress) { - logger.info("download " + file.getName() + " progress : " + progress - + "% , download size : " + copySize + ", total size : " + totalSize); - nextPrintProgress += 10; - } - } - logger.info("download file " + file.getName() + " end!"); - fos.flush(); - } finally { - IOUtils.closeQuietly(fos); + Thread.sleep(100L); + } catch (InterruptedException e1) { } } - } finally { - IOUtils.closeQuietly(fos); } } - - public static String describeBinlogFiles(String url, String ak, String sk, String dbInstanceId, Date startTime, - Date endTime, int pageSize, int pageNumber) throws Exception { - Map paramMap = new HashMap(); - paramMap.put("Action", "DescribeBinlogFiles"); - paramMap.put("DBInstanceId", dbInstanceId); // rds实例id - paramMap.put("StartTime", formatIso8601Date(startTime)); - paramMap.put("EndTime", formatIso8601Date(endTime)); - paramMap.put("PageSize", String.valueOf(pageSize)); - paramMap.put("PageNumber", String.valueOf(pageNumber)); - return doRequest(url, paramMap, ak, sk); - } - - private static String doRequest(String domin, Map param, String ak, String sk) throws Exception { - param.put("AccessKeyId", ak); - param.put("SignatureMethod", "HMAC-SHA1"); - param.put("SignatureVersion", SIGNATURE_VERSION); - param.put("Version", API_VERSION); - param.put("SignatureNonce", UUID.randomUUID().toString()); - param.put("Format", "JSON"); - param.put("Timestamp", formatIso8601Date(new Date())); - String signStr = generate("POST", param, sk); - param.put("Signature", signStr); - String request = concatQueryString(param); - String url = domin + "?" + request; - String result = HttpHelper.post(url, null, Collections.EMPTY_MAP, TIMEOUT); - return result; - } - - public static String concatQueryString(Map parameters) throws UnsupportedEncodingException { - if (null == parameters) { - return null; - } - StringBuilder urlBuilder = new StringBuilder(""); - for (Map.Entry entry : parameters.entrySet()) { - String key = entry.getKey(); - String val = entry.getValue(); - urlBuilder.append(encode(key)); - if (val != null) { - urlBuilder.append("=").append(encode(val)); - } - urlBuilder.append("&"); - } - int strIndex = urlBuilder.length(); - if (parameters.size() > 0) { - urlBuilder.deleteCharAt(strIndex - 1); - } - return urlBuilder.toString(); - } - - public static String encode(String value) throws UnsupportedEncodingException { - return URLEncoder.encode(value, "UTF-8"); - } - - private static String formatIso8601Date(Date date) { - SimpleDateFormat df = new SimpleDateFormat(ISO8601_DATE_FORMAT); - df.setTimeZone(TimeZone.getTimeZone("GMT")); - return df.format(date); - } - - /** - * 使用 HMAC-SHA1 签名方法对对encryptText进行签名 - * - * @param encryptText 被签名的字符串 - * @param encryptKey 密钥 - * @return - * @throws Exception - */ - public static byte[] HmacSHA1Encrypt(String encryptText, String encryptKey) throws Exception { - byte[] data = encryptKey.getBytes(ENCODING); - // 根据给定的字节数组构造一个密钥,第二参数指定一个密钥算法的名称 - SecretKey secretKey = new SecretKeySpec(data, MAC_NAME); - // 生成一个指定 Mac 算法 的 Mac 对象 - Mac mac = Mac.getInstance(MAC_NAME); - // 用给定密钥初始化 Mac 对象 - mac.init(secretKey); - - byte[] text = encryptText.getBytes(ENCODING); - // 完成 Mac 操作 - return mac.doFinal(text); - } - - private static String base64(byte input[]) throws UnsupportedEncodingException { - return new String(Base64.encodeBase64(input), ENCODING); - } - - /** 对参数名称和参数值进行URL编码 **/ - public static String generate(String method, Map parameter, String accessKeySecret) - throws Exception { - String signString = generateSignString(method, parameter); - byte[] signBytes = HmacSHA1Encrypt(signString, accessKeySecret + "&"); - String signature = base64(signBytes); - if ("POST".equals(method)) { - return signature; - } - return URLEncoder.encode(signature, "UTF-8"); - } - - private static String generateQueryString(TreeMap treeMap) { - StringBuilder canonicalizedQueryString = new StringBuilder(); - boolean first = true; - for (String key : treeMap.navigableKeySet()) { - String value = treeMap.get(key); - if (!first) { - canonicalizedQueryString.append("&"); - } - first = false; - canonicalizedQueryString.append(percentEncode(key)).append("=").append(percentEncode(value)); - } - return canonicalizedQueryString.toString(); - } - - public static String generateSignString(String httpMethod, Map parameter) throws IOException { - TreeMap sortParameter = new TreeMap(); - sortParameter.putAll(parameter); - String canonicalizedQueryString = generateQueryString(sortParameter); - if (null == httpMethod) { - throw new RuntimeException("httpMethod can not be empty"); - } - /** 构造待签名的字符串* */ - StringBuilder stringToSign = new StringBuilder(); - stringToSign.append(httpMethod).append("&"); - stringToSign.append(percentEncode("/")).append("&"); - stringToSign.append(percentEncode(canonicalizedQueryString)); - return stringToSign.toString(); - } - - public static String percentEncode(String value) { - try { - return value == null ? null : URLEncoder.encode(value, ENCODING) - .replaceAll("\\+", "%20") - .replaceAll("\\*", "%2A") - .replaceAll("%7E", "~"); - } catch (Exception e) { - } - return ""; - } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java index 05a1935e..aac14d99 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/RdsLocalBinlogEventParser.java @@ -28,15 +28,15 @@ import com.alibaba.otter.canal.protocol.position.LogPosition; */ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements CanalEventParser, LocalBinLogConnection.FileParserListener { - private String url = "https://rds.aliyuncs.com/"; // openapi地址 - private String accesskey; // 云账号的ak - private String secretkey; // 云账号sk - private String instanceId; // rds实例id + private String url; // openapi地址 + private String accesskey; // 云账号的ak + private String secretkey; // 云账号sk + private String instanceId; // rds实例id private Long startTime; private Long endTime; private BinlogDownloadQueue binlogDownloadQueue; private ParseFinishListener finishListener; - private int batchSize; + private int batchFileSize; public RdsLocalBinlogEventParser(){ } @@ -65,10 +65,9 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements instanceId, new Date(startTime), new Date(endTime)); - binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchSize, directory); + binlogDownloadQueue = new BinlogDownloadQueue(binlogFiles, batchFileSize, directory); binlogDownloadQueue.silenceDownload(); needWait = true; - parallel = false; // try to download one file,use to test server id binlogDownloadQueue.tryOne(); } catch (Throwable e) { @@ -91,10 +90,12 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements binlogDownloadQueue.notifyNotMatch(); try { binlogDownloadQueue.cleanDir(); + binlogDownloadQueue.tryOne(); binlogDownloadQueue.prepare(); - } catch (Exception e) { + } catch (Throwable e) { throw new RuntimeException(e); } + try { binlogDownloadQueue.execute(new Runnable() { @@ -163,8 +164,10 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements } // 处理下logManager位点问题 LogPosition logPosition = logPositionManager.getLatestIndexBy(destination); - EntryPosition position = logPosition.getPostion(); - if (position != null) { + Long timestamp = 0L; + if (logPosition != null && logPosition.getPostion() != null) { + timestamp = logPosition.getPostion().getTimestamp(); + EntryPosition position = logPosition.getPostion(); LogPosition newLogPosition = new LogPosition(); String journalName = position.getJournalName(); int sepIdx = journalName.indexOf("."); @@ -181,9 +184,13 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements } if (binlogDownloadQueue.isLastFile(fileName)) { - logger.info("all file parse complete, switch to mysql parser!"); + logger.warn("last file : " + fileName + " , timestamp : " + timestamp + + " , all file parse complete, switch to mysql parser!"); finishListener.onFinish(); return; + } else { + logger.warn("parse local binlog file : " + fileName + " , timestamp : " + timestamp + + " , try the next binlog !"); } binlogDownloadQueue.prepare(); } catch (Exception e) { @@ -207,7 +214,7 @@ public class RdsLocalBinlogEventParser extends LocalBinlogEventParser implements void onFinish(); } - public void setBatchSize(int batchSize) { - this.batchSize = batchSize; + public void setBatchFileSize(int batchFileSize) { + this.batchFileSize = batchFileSize; } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java index e6b354d7..bee87521 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/BinlogFile.java @@ -6,7 +6,7 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; */ public class BinlogFile { - private Long FileSize; + private Long FileSize; private String LogBeginTime; private String LogEndTime; private String DownloadLink; @@ -69,4 +69,12 @@ public class BinlogFile { public void setFileName(String fileName) { this.fileName = fileName; } + + @Override + public String toString() { + return "BinlogFile [FileSize=" + FileSize + ", LogBeginTime=" + LogBeginTime + ", LogEndTime=" + LogEndTime + + ", DownloadLink=" + DownloadLink + ", HostInstanceID=" + HostInstanceID + ", LinkExpiredTime=" + + LinkExpiredTime + ", fileName=" + fileName + "]"; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java index d8d54e44..d5b6820d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/DescribeBinlogFileResult.java @@ -5,12 +5,13 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.data; * @since 1.0.25 */ public class DescribeBinlogFileResult { + private RdsItem Items; - private long PageNumber; - private long TotalRecordCount; - private long TotalFileSize; - private String RequestId; - private long PageRecordCount; + private long PageNumber; + private long TotalRecordCount; + private long TotalFileSize; + private String RequestId; + private long PageRecordCount; public RdsItem getItems() { return Items; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java index b6ca7c53..e007671a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsBackupPolicy.java @@ -9,15 +9,16 @@ public class RdsBackupPolicy { /** * 数据备份保留天数(7到730天)。 */ - private String BackupRetentionPeriod; + private String BackupRetentionPeriod; /** * 数据备份时间,格式:HH:mmZ- HH:mm Z。 */ - private String PreferredBackupTime; + private String PreferredBackupTime; /** - * 数据备份周期。Monday:周一;Tuesday:周二;Wednesday:周三;Thursday:周四;Friday:周五;Saturday:周六;Sunday:周日。 + * 数据备份周期。Monday:周一;Tuesday:周二;Wednesday:周三;Thursday:周四;Friday:周五;Saturday: + * 周六;Sunday:周日。 */ - private String PreferredBackupPeriod; + private String PreferredBackupPeriod; /** * 日志备份状态。Enable:开启;Disabled:关闭。 */ @@ -25,7 +26,7 @@ public class RdsBackupPolicy { /** * 日志备份保留天数(7到730天)。 */ - private int LogBackupRetentionPeriod; + private int LogBackupRetentionPeriod; public String getBackupRetentionPeriod() { return BackupRetentionPeriod; @@ -66,4 +67,12 @@ public class RdsBackupPolicy { public void setLogBackupRetentionPeriod(int logBackupRetentionPeriod) { LogBackupRetentionPeriod = logBackupRetentionPeriod; } + + @Override + public String toString() { + return "RdsBackupPolicy [BackupRetentionPeriod=" + BackupRetentionPeriod + ", PreferredBackupTime=" + + PreferredBackupTime + ", PreferredBackupPeriod=" + PreferredBackupPeriod + ", BackupLog=" + BackupLog + + ", LogBackupRetentionPeriod=" + LogBackupRetentionPeriod + "]"; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java index a2cfaffe..81c484e3 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/data/RdsItem.java @@ -7,6 +7,7 @@ import java.util.List; * @since 1.0.25 */ public class RdsItem { + private List BinLogFile; public List getBinLogFile() { @@ -16,4 +17,10 @@ public class RdsItem { public void setBinLogFile(List binLogFile) { BinLogFile = binLogFile; } + + @Override + public String toString() { + return "RdsItem [BinLogFile=" + BinLogFile + "]"; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java index 976968f6..f43523b5 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/AbstractRequest.java @@ -1,12 +1,18 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; +import io.netty.handler.codec.http.HttpResponseStatus; + import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; -import java.util.*; +import java.util.Date; +import java.util.Map; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.crypto.Mac; @@ -32,8 +38,6 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; -import io.netty.handler.codec.http.HttpResponseStatus; - /** * @author chengjin.lyf on 2018/8/7 下午2:26 * @since 1.0.25 @@ -49,42 +53,38 @@ public abstract class AbstractRequest { */ private static final String MAC_NAME = "HmacSHA1"; - private String accessKeyId; + private String accessKeyId; - private String accessKeySecret; + private String accessKeySecret; /** - * api 版本 - * + * api 版本 */ - private String version; + private String version; - private String endPoint = "rds.aliyuncs.com"; + private String endPoint = "rds.aliyuncs.com"; - private String protocol = "http"; + private String protocol = "http"; public void setProtocol(String protocol) { this.protocol = protocol; } - private int timeout = (int) TimeUnit.MINUTES.toMillis(1); - + private int timeout = (int) TimeUnit.MINUTES.toMillis(1); private Map treeMap = new TreeMap(); - public void putQueryString(String name, String value){ - if (StringUtils.isBlank(name) || StringUtils.isBlank(value)){ + public void putQueryString(String name, String value) { + if (StringUtils.isBlank(name) || StringUtils.isBlank(value)) { return; } treeMap.put(name, value); } - public void setVersion(String version) { this.version = version; } - public void setEndPoint(String endPoint) { this.endPoint = endPoint; } @@ -169,7 +169,7 @@ public abstract class AbstractRequest { p.put("Format", "JSON"); p.put("Version", version); p.put("AccessKeyId", accessKeyId); - p.put("SignatureMethod", "HMAC-SHA1"); //此处不能用变量 MAC_NAME + p.put("SignatureMethod", "HMAC-SHA1"); // 此处不能用变量 MAC_NAME p.put("Timestamp", formatUTCTZ(new Date())); p.put("SignatureVersion", "1.0"); p.put("SignatureNonce", UUID.randomUUID().toString()); @@ -193,6 +193,7 @@ public abstract class AbstractRequest { * @return * @throws IOException */ + @SuppressWarnings("deprecation") private final HttpResponse executeHttpRequest(HttpGet getMethod, String host) throws Exception { SSLContext sslContext = SSLContexts.custom().loadTrustMaterial(null, new TrustStrategy() { @@ -202,24 +203,24 @@ public abstract class AbstractRequest { } }).build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, - new String[] { "TLSv1" }, - null, - SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + new String[] { "TLSv1" }, + null, + SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); Registry registry = RegistryBuilder.create() - .register("http", PlainConnectionSocketFactory.INSTANCE) - .register("https", sslsf) - .build(); + .register("http", PlainConnectionSocketFactory.INSTANCE) + .register("https", sslsf) + .build(); HttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager(registry); CloseableHttpClient httpClient = HttpClientBuilder.create() - .setMaxConnPerRoute(50) - .setMaxConnTotal(100) - .setConnectionManager(httpClientConnectionManager) - .build(); + .setMaxConnPerRoute(50) + .setMaxConnTotal(100) + .setConnectionManager(httpClientConnectionManager) + .build(); RequestConfig requestConfig = RequestConfig.custom() - .setConnectTimeout(timeout) - .setConnectionRequestTimeout(timeout) - .setSocketTimeout(timeout) - .build(); + .setConnectTimeout(timeout) + .setConnectionRequestTimeout(timeout) + .setSocketTimeout(timeout) + .build(); getMethod.setConfig(requestConfig); HttpResponse response = httpClient.execute(getMethod); int statusCode = response.getStatusLine().getStatusCode(); @@ -232,14 +233,14 @@ public abstract class AbstractRequest { protected abstract T processResult(HttpResponse response) throws Exception; - protected void processBefore(){ + protected void processBefore() { } - public final T doAction() throws Exception { + public final T doAction() throws Exception { processBefore(); String requestStr = makeRequestString(treeMap); - HttpGet httpGet = new HttpGet(protocol + "://" +endPoint + "?" + requestStr); + HttpGet httpGet = new HttpGet(protocol + "://" + endPoint + "?" + requestStr); HttpResponse response = executeHttpRequest(httpGet, endPoint); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { String result = EntityUtils.toString(response.getEntity()); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java index 0fbdfc47..cf51961a 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBackupPolicyRequest.java @@ -1,27 +1,26 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; import org.apache.http.HttpResponse; import org.apache.http.util.EntityUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; /** * rds 备份策略查询 + * * @author chengjin.lyf on 2018/8/7 下午3:41 * @since 1.0.25 */ public class DescribeBackupPolicyRequest extends AbstractRequest { - - public DescribeBackupPolicyRequest() { + public DescribeBackupPolicyRequest(){ setVersion("2014-08-15"); putQueryString("Action", "DescribeBackupPolicy"); } - public void setRdsInstanceId(String rdsInstanceId) { putQueryString("DBInstanceId", rdsInstanceId); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java index 1c91c255..8aa90569 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/rds/request/DescribeBinlogFilesRequest.java @@ -2,12 +2,12 @@ package com.alibaba.otter.canal.parse.inbound.mysql.rds.request; import java.util.Date; -import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; import org.apache.http.HttpResponse; import org.apache.http.util.EntityUtils; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.TypeReference; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.DescribeBinlogFileResult; /** * @author chengjin.lyf on 2018/8/7 下午3:41 @@ -15,11 +15,9 @@ import com.alibaba.fastjson.TypeReference; */ public class DescribeBinlogFilesRequest extends AbstractRequest { - - public DescribeBinlogFilesRequest() { + public DescribeBinlogFilesRequest(){ setVersion("2014-08-15"); putQueryString("Action", "DescribeBinlogFiles"); - } public void setRdsInstanceId(String rdsInstanceId) { @@ -35,11 +33,11 @@ public class DescribeBinlogFilesRequest extends AbstractRequest() { - }); + DescribeBinlogFileResult describeBinlogFileResult = JSONObject.parseObject(result, + new TypeReference() { + }); return describeBinlogFileResult; } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java index 9a886336..904b55a9 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java @@ -68,7 +68,8 @@ public class MemoryTableMeta implements TableMetaTSDB { try { // druid暂时flush privileges语法解析有问题 - if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush")) { + if (!StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "flush") + && !StringUtils.startsWithIgnoreCase(StringUtils.trim(ddl), "grant")) { repository.console(ddl); } } catch (Throwable e) { diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java index 986d4d6a..546ee58c 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogEventParserProxyTest.java @@ -8,6 +8,7 @@ import java.util.concurrent.atomic.AtomicLong; import org.junit.Assert; import org.junit.Test; +import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.parse.helper.TimeoutChecker; import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy; import com.alibaba.otter.canal.parse.index.AbstractLogPositionManager; @@ -28,10 +29,9 @@ public class RdsBinlogEventParserProxyTest { private static final String MYSQL_ADDRESS = ""; private static final String USERNAME = ""; private static final String PASSWORD = ""; - public static final String DBNAME = ""; - public static final String TBNAME = ""; - public static final String DDL = ""; - + public static final String DBNAME = ""; + public static final String TBNAME = ""; + public static final String DDL = ""; @Test public void test_timestamp() throws InterruptedException { @@ -41,7 +41,7 @@ public class RdsBinlogEventParserProxyTest { final RdsBinlogEventParserProxy controller = new RdsBinlogEventParserProxy(); Calendar calendar = Calendar.getInstance(); - calendar.add(Calendar.DAY_OF_YEAR, -1); + calendar.add(Calendar.HOUR_OF_DAY, -24 * 4); final EntryPosition defaultPosition = buildPosition(null, null, calendar.getTimeInMillis()); controller.setSlaveId(3344L); controller.setDetectingEnable(false); @@ -51,13 +51,15 @@ public class RdsBinlogEventParserProxyTest { controller.setInstanceId(""); controller.setAccesskey(""); controller.setSecretkey(""); - controller.setBatchSize(4); -// controller.setRdsOpenApiUrl("https://rds.aliyuncs.com/"); + controller.setDirectory("/tmp/binlog"); + controller.setEventBlackFilter(new AviaterRegexFilter("mysql\\.*")); + controller.setFilterTableError(true); + controller.setBatchFileSize(4); controller.setEventSink(new AbstractCanalEventSinkTest>() { @Override public boolean sink(List entrys, InetSocketAddress remoteAddress, String destination) - throws CanalSinkException { + throws CanalSinkException { for (CanalEntry.Entry entry : entrys) { if (entry.getEntryType() != CanalEntry.EntryType.HEARTBEAT) { entryCount.incrementAndGet(); @@ -79,8 +81,8 @@ public class RdsBinlogEventParserProxyTest { controller.setLogPositionManager(new AbstractLogPositionManager() { private LogPosition logPosition; + public void persistLogPosition(String destination, LogPosition logPosition) { - System.out.println(logPosition); this.logPosition = logPosition; } @@ -105,7 +107,6 @@ public class RdsBinlogEventParserProxyTest { Assert.assertTrue(entryPosition.getTimestamp() <= defaultPosition.getTimestamp()); } - // ======================== helper method ======================= private EntryPosition buildPosition(String binlogFile, Long offest, Long timestamp) { diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java index 596847ae..25a14284 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/RdsBinlogOpenApiTest.java @@ -1,11 +1,14 @@ package com.alibaba.otter.canal.parse.inbound.mysql; -import java.io.File; import java.util.Date; +import java.util.List; import org.apache.commons.lang.time.DateUtils; +import org.junit.Test; import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.BinlogFile; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.data.RdsBackupPolicy; /** * @author agapple 2017年10月15日 下午2:14:34 @@ -13,15 +16,19 @@ import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogOpenApi; */ public class RdsBinlogOpenApiTest { + @Test public void testSimple() throws Throwable { - Date startTime = DateUtils.parseDate("2017-10-13 20:56:58", new String[] { "yyyy-MM-dd HH:mm:ss" }); - Date endTime = DateUtils.parseDate("2017-10-14 02:57:59", new String[] { "yyyy-MM-dd HH:mm:ss" }); - RdsBinlogOpenApi.downloadBinlogFiles("https://rds.aliyuncs.com/", - "", - "", - "rm-bp180v4mfjnm157es", - startTime, - endTime, - new File("/tmp/binlog/")); + Date startTime = DateUtils.parseDate("2018-08-10 12:00:00", new String[] { "yyyy-MM-dd HH:mm:ss" }); + Date endTime = DateUtils.parseDate("2018-08-11 12:00:00", new String[] { "yyyy-MM-dd HH:mm:ss" }); + String url = "https://rds.aliyuncs.com/"; + String ak = ""; + String sk = ""; + String dbInstanceId = ""; + + RdsBackupPolicy backupPolicy = RdsBinlogOpenApi.queryBinlogBackupPolicy(url, ak, sk, dbInstanceId); + System.out.println(backupPolicy); + + List binlogFiles = RdsBinlogOpenApi.listBinlogFiles(url, ak, sk, dbInstanceId, startTime, endTime); + System.out.println(binlogFiles); } } From cc31e2db7ccb07a2227eba0cae598656cae2420b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 14 Aug 2018 13:23:24 +0800 Subject: [PATCH 081/360] fixed docker build --- .../otter/canal/deployer/CanalConstants.java | 1 - .../otter/canal/deployer/CanalController.java | 8 ---- deployer/src/main/resources/canal.properties | 1 - docker/Dockerfile | 35 +--------------- docker/base/Dockerfile | 41 +++++++++++++++++++ docker/build.sh | 22 +++++----- docker/image/admin/app.sh | 4 +- docker/image/admin/health.sh | 9 +++- .../src/test/resources/canal.properties | 2 +- .../canal/prometheus/PrometheusService.java | 12 ++---- .../prometheus/impl/ParserCollector.java | 1 + .../canal/prometheus/impl/StoreCollector.java | 6 +-- .../embedded/CanalServerWithEmbedded.java | 6 --- .../otter/canal/spi/CanalMetricsService.java | 5 --- .../canal/spi/NopCanalMetricsService.java | 5 --- .../memory/MemoryEventStoreWithBuffer.java | 13 +++--- 16 files changed, 74 insertions(+), 97 deletions(-) create mode 100644 docker/base/Dockerfile diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index c7a90d30..b328feb7 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -15,7 +15,6 @@ public class CanalConstants { public static final String CANAL_ID = ROOT + "." + "id"; public static final String CANAL_IP = ROOT + "." + "ip"; public static final String CANAL_PORT = ROOT + "." + "port"; - public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port"; public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers"; public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty"; diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 53a2b9b5..6b0fc934 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -98,14 +98,6 @@ public class CanalController { port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT)); embededCanalServer = CanalServerWithEmbedded.instance(); embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator - try { - int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT)); - embededCanalServer.setMetricsPort(metricsPort); - } catch (NumberFormatException e) { - logger.info("No valid metrics server port found, use default 11112."); - embededCanalServer.setMetricsPort(11112); - } - String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { canalServer = CanalServerWithNetty.instance(); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 1cefb7d9..9083477d 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -4,7 +4,6 @@ canal.id= 1 canal.ip= canal.port=11111 -canal.metrics.pull.port=11112 canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 diff --git a/docker/Dockerfile b/docker/Dockerfile index 2867b955..835e881a 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,32 +1,7 @@ -FROM centos:centos6.7 +FROM canal/osbase:v1 MAINTAINER agapple (jianghang115@gmail.com) -# install system -RUN \ - /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ - echo 'root:Hello1234' | chpasswd && \ - groupadd -r admin && useradd -g admin admin && \ - yum install -y man && \ - yum install -y dstat && \ - yum install -y unzip && \ - yum install -y nc && \ - yum install -y openssh-server && \ - yum install -y tar && \ - yum install -y which && \ - yum install -y wget && \ - yum install -y perl && \ - yum install -y file && \ - ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \ - ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \ - sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \ - sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \ - mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \ - yum install -y cronie && \ - sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \ - yum clean all && \ - true - # install canal COPY image/ /tmp/docker/ COPY canal.deployer-*.tar.gz /home/admin/ @@ -39,14 +14,6 @@ RUN \ cp -R /tmp/docker/admin/* /home/admin/ && \ /bin/cp -f alidata/bin/lark-wait /usr/bin/lark-wait && \ - touch /var/lib/rpm/* && \ - yum -y install /tmp/jdk-8-linux-x64.rpm && \ - /bin/rm -f /tmp/jdk-8-linux-x64.rpm && \ - - echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \ - echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \ - /bin/mv /home/admin/bin/clean_log /etc/cron.d && \ - mkdir -p /home/admin/canal-server && \ tar -xzvf /home/admin/canal.deployer-*.tar.gz -C /home/admin/canal-server && \ /bin/rm -f /home/admin/canal.deployer-*.tar.gz && \ diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile new file mode 100644 index 00000000..010b97e0 --- /dev/null +++ b/docker/base/Dockerfile @@ -0,0 +1,41 @@ +FROM centos:centos6.10 + +MAINTAINER agapple (jianghang115@gmail.com) + +env DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.rpm" +# install system +RUN \ + /bin/cp -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo 'root:Hello1234' | chpasswd && \ + groupadd -r admin && useradd -g admin admin && \ + yum install -y man && \ + yum install -y dstat && \ + yum install -y unzip && \ + yum install -y nc && \ + yum install -y openssh-server && \ + yum install -y tar && \ + yum install -y which && \ + yum install -y wget && \ + yum install -y perl && \ + yum install -y file && \ + ssh-keygen -q -N "" -t dsa -f /etc/ssh/ssh_host_dsa_key && \ + ssh-keygen -q -N "" -t rsa -f /etc/ssh/ssh_host_rsa_key && \ + sed -ri 's/session required pam_loginuid.so/#session required pam_loginuid.so/g' /etc/pam.d/sshd && \ + sed -i -e 's/^#Port 22$/Port 2222/' /etc/ssh/sshd_config && \ + mkdir -p /root/.ssh && chown root.root /root && chmod 700 /root/.ssh && \ + yum install -y cronie && \ + sed -i '/session required pam_loginuid.so/d' /etc/pam.d/crond && \ + true + +RUN \ + touch /var/lib/rpm/* && \ + wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O /tmp/jdk-8-linux-x64.rpm && \ + yum -y install /tmp/jdk-8-linux-x64.rpm && \ + /bin/rm -f /tmp/jdk-8-linux-x64.rpm && \ + + echo "export JAVA_HOME=/usr/java/latest" >> /etc/profile && \ + echo "export PATH=\$JAVA_HOME/bin:\$PATH" >> /etc/profile && \ + yum clean all && \ + true + +CMD ["/bin/bash"] diff --git a/docker/build.sh b/docker/build.sh index 58424fb3..eab5b2b3 100644 --- a/docker/build.sh +++ b/docker/build.sh @@ -14,17 +14,15 @@ case "`uname`" in esac BASE=${bin_abs_path} -if [ ! -f $BASE/jdk*.rpm ] ; then - DOWNLOAD_LINK="http://download.oracle.com/otn-pub/java/jdk/8u181-b13/96a7b8442fe848ef90c96a2fad6ed6d1/jdk-8u181-linux-x64.tar.gz" - wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=xxx; oraclelicense=accept-securebackup-cookie" "$DOWNLOAD_LINK" -O $BASE/jdk-8-linux-x64.rpm -fi - -cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; - -if [ "$1" == "kafka" ] ; then - cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ - docker build --no-cache -t canal/canal-server $BASE/ +if [ "$1" == "base" ] ; then + docker build --no-cache -t canal/osbase $BASE/base else - cp $BASE/../target/canal.deployer-*.tar.gz $BASE/ - docker build --no-cache -t canal/canal-server $BASE/ + cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; + if [ "$1" == "kafka" ] ; then + cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ + else + cp $BASE/../target/canal.deployer-*.tar.gz $BASE/ + docker build --no-cache -t canal/canal-server $BASE/ + fi fi diff --git a/docker/image/admin/app.sh b/docker/image/admin/app.sh index 254f7e48..66928ac3 100755 --- a/docker/image/admin/app.sh +++ b/docker/image/admin/app.sh @@ -86,7 +86,9 @@ function start_canal() { echo "multi destination:$destination is not support" exit 1; else - mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination + if [ "$destination" != "" ] && [ "$destination" != "example" ] ; then + mv /home/admin/canal-server/conf/example /home/admin/canal-server/conf/$destination + fi fi su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1' sleep 5 diff --git a/docker/image/admin/health.sh b/docker/image/admin/health.sh index 17a89740..c48e6d9d 100755 --- a/docker/image/admin/health.sh +++ b/docker/image/admin/health.sh @@ -1,6 +1,11 @@ #!/bin/sh -CHECK_URL="http://127.0.0.1:8080/metrics" -CHECK_POINT="success" +metrics_port=`perl -le 'print $ENV{"canal.metrics.pull.port"}'` +if [ "$metrics_port" == "" ]; then + metrics_port="11112" +fi + +CHECK_URL="http://127.0.0.1:$metrics_port/metrics" +CHECK_POINT="canal" CHECK_COUNT=`curl -s --connect-timeout 7 --max-time 7 $CHECK_URL | grep -c $CHECK_POINT` if [ $CHECK_COUNT -eq 0 ]; then echo "[FAILED]" diff --git a/instance/spring/src/test/resources/canal.properties b/instance/spring/src/test/resources/canal.properties index ffb5cf0e..1887781d 100644 --- a/instance/spring/src/test/resources/canal.properties +++ b/instance/spring/src/test/resources/canal.properties @@ -51,4 +51,4 @@ canal.instance.global.mode = spring canal.instance.global.lazy = false #canal.instance.global.manager.address = 127.0.0.1:1099 canal.instance.global.spring.xml = classpath:spring/memory-instance.xml -#canal.instance.global.spring.xml = classpath:spring/default-instance.xml +#canal.instance.global.spring.xml = classpath:spring/default-instance.xml \ No newline at end of file diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 18bf63f7..485f6728 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -22,7 +22,6 @@ public class PrometheusService implements CanalMetricsService { private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); private final CanalInstanceExports instanceExports; private volatile boolean running = false; - private int port; private HTTPServer server; private final ClientInstanceProfiler clientProfiler; @@ -42,9 +41,9 @@ public class PrometheusService implements CanalMetricsService { @Override public void initialize() { try { - logger.info("Start prometheus HTTPServer on port {}.", port); - //TODO 2.Https? - server = new HTTPServer(port); + //TODO 1.Configurable port + //TODO 2.Https + server = new HTTPServer(11112); } catch (IOException e) { logger.warn("Unable to start prometheus HTTPServer.", e); return; @@ -113,9 +112,4 @@ public class PrometheusService implements CanalMetricsService { logger.info("Unregister metrics for destination {}.", instance.getDestination()); } - @Override - public void setServerPort(int port) { - this.port = port; - } - } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java index c1883658..73102795 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicLong; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; +import static java.util.concurrent.TimeUnit.NANOSECONDS; /** * @author Chuanyi Li diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index d345602c..3be87c3e 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -40,7 +40,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance"; private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); - private final List storeLabelsList = Arrays.asList(DEST, "batchMode", "size"); + private final List storeLabelsList = Arrays.asList(DEST, "batchMode"); private StoreCollector() {} @@ -100,8 +100,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { holder.putSeq = memStore.getPutSequence(); holder.ackSeq = memStore.getAckSequence(); holder.destLabelValues = Collections.singletonList(destination); - holder.size = memStore.getBufferSize(); - holder.storeLabelValues = Arrays.asList(destination, holder.batchMode.name(), Integer.toString(holder.size)); + holder.storeLabelValues = Arrays.asList(destination, memStore.getBatchMode().name()); Preconditions.checkNotNull(holder.batchMode); Preconditions.checkNotNull(holder.putSeq); Preconditions.checkNotNull(holder.ackSeq); @@ -129,7 +128,6 @@ public class StoreCollector extends Collector implements InstanceRegistry { private BatchMode batchMode; private AtomicLong putMemSize; private AtomicLong ackMemSize; - private int size; private List destLabelValues; private List storeLabelValues; } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index 9ac5a834..ccf21020 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -44,7 +44,6 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C private Map canalInstances; // private Map lastRollbackPostions; private CanalInstanceGenerator canalInstanceGenerator; - private int metricsPort; private CanalMetricsService metrics = NopCanalMetricsService.NOP; private static class SingletonHolder { @@ -65,7 +64,6 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C super.start(); // 如果存在provider,则启动metrics service loadCanalMetrics(); - metrics.setServerPort(metricsPort); metrics.initialize(); canalInstances = MigrateMap.makeComputingMap(new Function() { @@ -531,8 +529,4 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C this.canalInstanceGenerator = canalInstanceGenerator; } - public void setMetricsPort(int metricsPort) { - this.metricsPort = metricsPort; - } - } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java index a4528800..6d823be8 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java @@ -39,9 +39,4 @@ public interface CanalMetricsService { */ void unregister(CanalInstance instance); - /** - * @param port server port for pull - */ - void setServerPort(int port); - } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java index 45926348..0d6ad088 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java @@ -35,9 +35,4 @@ public class NopCanalMetricsService implements CanalMetricsService { public void unregister(CanalInstance instance) { } - - @Override - public void setServerPort(int port) { - - } } diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index ef97e206..00ba2e71 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -545,9 +545,6 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } // ================ setter / getter ================== - public int getBufferSize() { - return this.bufferSize; - } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; @@ -566,22 +563,22 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } public AtomicLong getPutSequence() { - return this.putSequence; + return putSequence; } public AtomicLong getAckSequence() { - return this.ackSequence; + return ackSequence; } public AtomicLong getPutMemSize() { - return this.putMemSize; + return putMemSize; } public AtomicLong getAckMemSize() { - return this.ackMemSize; + return ackMemSize; } public BatchMode getBatchMode() { - return this.batchMode; + return batchMode; } } From c421ff718b42521474b24fd774476aa5bddf034d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 14 Aug 2018 13:29:03 +0800 Subject: [PATCH 082/360] preapre for 1.1.0 release --- client/pom.xml | 2 +- common/pom.xml | 2 +- dbsync/pom.xml | 2 +- deployer/pom.xml | 2 +- docker/Dockerfile | 1 - driver/pom.xml | 2 +- example/pom.xml | 2 +- filter/pom.xml | 2 +- instance/core/pom.xml | 2 +- instance/manager/pom.xml | 2 +- instance/pom.xml | 2 +- instance/spring/pom.xml | 2 +- kafka-client/pom.xml | 4 ++-- kafka/pom.xml | 4 ++-- meta/pom.xml | 2 +- parse/pom.xml | 2 +- pom.xml | 2 +- prometheus/pom.xml | 4 ++-- protocol/pom.xml | 2 +- server/pom.xml | 2 +- sink/pom.xml | 2 +- store/pom.xml | 2 +- 22 files changed, 24 insertions(+), 25 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index c736b141..038f2a9f 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/common/pom.xml b/common/pom.xml index 9c57c560..afa3372c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml canal.common diff --git a/dbsync/pom.xml b/dbsync/pom.xml index 1f274e82..17ec6c30 100644 --- a/dbsync/pom.xml +++ b/dbsync/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/deployer/pom.xml b/deployer/pom.xml index 68224ca1..2de1ce1e 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/docker/Dockerfile b/docker/Dockerfile index 835e881a..311ac0b5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -5,7 +5,6 @@ MAINTAINER agapple (jianghang115@gmail.com) # install canal COPY image/ /tmp/docker/ COPY canal.deployer-*.tar.gz /home/admin/ -COPY jdk-8-linux-x64.rpm /tmp/ RUN \ cp -R /tmp/docker/alidata /alidata && \ diff --git a/driver/pom.xml b/driver/pom.xml index edd24e57..648e64cc 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/example/pom.xml b/example/pom.xml index 2897e821..4f13d017 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/filter/pom.xml b/filter/pom.xml index 30b53e28..f662f5ea 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/instance/core/pom.xml b/instance/core/pom.xml index cbfab2fd..e3812d4e 100644 --- a/instance/core/pom.xml +++ b/instance/core/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../../pom.xml canal.instance.core diff --git a/instance/manager/pom.xml b/instance/manager/pom.xml index 10f01e43..79340bd0 100644 --- a/instance/manager/pom.xml +++ b/instance/manager/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../../pom.xml com.alibaba.otter diff --git a/instance/pom.xml b/instance/pom.xml index aeec17cb..f45baee1 100644 --- a/instance/pom.xml +++ b/instance/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/instance/spring/pom.xml b/instance/spring/pom.xml index 6a8f172a..116dee77 100644 --- a/instance/spring/pom.xml +++ b/instance/spring/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../../pom.xml com.alibaba.otter diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml index d814be52..5444af5d 100644 --- a/kafka-client/pom.xml +++ b/kafka-client/pom.xml @@ -6,7 +6,7 @@ canal com.alibaba.otter - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter @@ -108,4 +108,4 @@
      - \ No newline at end of file + diff --git a/kafka/pom.xml b/kafka/pom.xml index 8d3b35d7..d46a2d03 100644 --- a/kafka/pom.xml +++ b/kafka/pom.xml @@ -6,7 +6,7 @@ canal com.alibaba.otter - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter @@ -137,4 +137,4 @@ - \ No newline at end of file + diff --git a/meta/pom.xml b/meta/pom.xml index 6192d92d..93c4ad7b 100644 --- a/meta/pom.xml +++ b/meta/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/parse/pom.xml b/parse/pom.xml index 994d06f0..fb93fc99 100644 --- a/parse/pom.xml +++ b/parse/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml canal.parse diff --git a/pom.xml b/pom.xml index 56ac821a..3101aca6 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ canal pom canal module for otter ${project.version} - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT https://github.com/alibaba/canal org.sonatype.oss diff --git a/prometheus/pom.xml b/prometheus/pom.xml index b814c7e6..7d192834 100644 --- a/prometheus/pom.xml +++ b/prometheus/pom.xml @@ -5,13 +5,13 @@ canal com.alibaba.otter - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT 4.0.0 com.alibaba.otter canal.prometheus - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT canal prometheus module for otter ${project.version} diff --git a/protocol/pom.xml b/protocol/pom.xml index ec7c7867..94715d15 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/server/pom.xml b/server/pom.xml index 1d243981..b7f83d56 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml canal.server diff --git a/sink/pom.xml b/sink/pom.xml index 4ed7e04f..9f136d35 100644 --- a/sink/pom.xml +++ b/sink/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/store/pom.xml b/store/pom.xml index 318c2f9d..9dee6737 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.0.26-SNAPSHOT + 1.1.0-SNAPSHOT ../pom.xml com.alibaba.otter From 156fc0edbe8e63bf4ed141aca58ea59cf9e8be13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 14 Aug 2018 13:38:01 +0800 Subject: [PATCH 083/360] fixed docker build --- docker/build.sh | 1 + docker/run.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/build.sh b/docker/build.sh index eab5b2b3..6b42e3a4 100644 --- a/docker/build.sh +++ b/docker/build.sh @@ -17,6 +17,7 @@ BASE=${bin_abs_path} if [ "$1" == "base" ] ; then docker build --no-cache -t canal/osbase $BASE/base else + rm -rf $BASE/canal.*.tar.gz ; cd $BASE/../ && mvn clean package -Dmaven.test.skip -Denv=release && cd $current_path ; if [ "$1" == "kafka" ] ; then cp $BASE/../target/canal-kafka-*.tar.gz $BASE/ diff --git a/docker/run.sh b/docker/run.sh index 7c56e2ba..3d5f7659 100644 --- a/docker/run.sh +++ b/docker/run.sh @@ -10,7 +10,7 @@ function usage() { echo " -e canal.instance.connectionCharset=UTF-8 \\" echo " -e canal.instance.tsdb.enable=true \\" echo " -e canal.instance.gtidon=false \\" - echo " -e canal.instance.filter.regex=.*\\..* " + echo " -e canal.instance.filter.regex=.*\\\\\\..* " exit } From 7ed6fcdf04824947d841654cc9dee57887a6964f Mon Sep 17 00:00:00 2001 From: yfpeng Date: Tue, 14 Aug 2018 15:59:08 +0800 Subject: [PATCH 084/360] debug memory leak --- .../otter/canal/store/memory/MemoryEventStoreWithBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index 00ba2e71..b276832a 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -417,7 +417,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple if (batchMode.isMemSize()) { ackMemSize.addAndGet(memsize); // 尝试清空buffer中的内存,将ack之前的内存全部释放掉 - for (long index = sequence + 1; index < next; index++) { + for (long index = sequence + 1; index <= next; index++) { entries[getIndex(index)] = null;// 设置为null } } From 2accfbf5db25547fb41fef3842f7c8b3f2142db9 Mon Sep 17 00:00:00 2001 From: winger Date: Tue, 14 Aug 2018 16:09:44 +0800 Subject: [PATCH 085/360] =?UTF-8?q?=E5=8D=87=E7=BA=A7Kafka=20=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E8=87=B3=202.11=5F1.1.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kafka-client/pom.xml | 2 +- kafka/pom.xml | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml index 5444af5d..42424ddd 100644 --- a/kafka-client/pom.xml +++ b/kafka-client/pom.xml @@ -24,7 +24,7 @@ org.apache.kafka kafka-clients - 0.9.0.1 + 1.1.1 diff --git a/kafka/pom.xml b/kafka/pom.xml index d46a2d03..b2587c02 100644 --- a/kafka/pom.xml +++ b/kafka/pom.xml @@ -25,11 +25,10 @@ snakeyaml 1.17 - org.apache.kafka kafka_2.11 - 0.9.0.1 + 1.1.1 org.slf4j @@ -37,6 +36,12 @@ + + + org.jboss.netty + netty + 3.2.2.Final + From 62f975deeb421017b985ba912459421cef02b39b Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 15 Aug 2018 14:26:55 +0800 Subject: [PATCH 086/360] =?UTF-8?q?=E5=8A=A0=E4=B8=8A=20if=20(logger.isDeb?= =?UTF-8?q?ugEnabled())=20=E9=98=B2=E6=AD=A2message.toString=E6=AF=8F?= =?UTF-8?q?=E6=AC=A1=E9=83=BD=E4=BC=9A=E8=A2=AB=E8=BF=90=E8=A1=8C=EF=BC=8C?= =?UTF-8?q?message=E8=BF=87=E5=A4=A7=E4=BC=9A=E5=8D=A1cpu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/kafka/producer/CanalKafkaProducer.java | 6 ++++-- kafka/src/main/resources/kafka.yml | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java index 4543aac1..0bb15308 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java +++ b/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java @@ -21,7 +21,7 @@ import com.alibaba.otter.canal.protocol.Message; */ public class CanalKafkaProducer { - private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); + private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); private Producer producer; @@ -83,6 +83,8 @@ public class CanalKafkaProducer { record = new ProducerRecord(topic.getTopic(), message); } producer.send(record); - logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); + if (logger.isDebugEnabled()) { + logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); + } } } diff --git a/kafka/src/main/resources/kafka.yml b/kafka/src/main/resources/kafka.yml index d6ccc51a..2729fc27 100644 --- a/kafka/src/main/resources/kafka.yml +++ b/kafka/src/main/resources/kafka.yml @@ -3,7 +3,7 @@ retries: 0 batchSize: 16384 lingerMs: 1 bufferMemory: 33554432 -# canal的批次大小,单位 k +# canal的批次大小,单位 k,量大建议改为1M canalBatchSize: 50 filterTransactionEntry: true @@ -13,7 +13,7 @@ canalDestinations: partition: # 一个destination可以对应多个topic # topics: -# - topics: example +# - topic: example # partition: From 0131c3ad130d63431766a1b0ee98cc4657219c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 15 Aug 2018 19:45:11 +0800 Subject: [PATCH 087/360] fixed merge code --- .../otter/canal/deployer/CanalController.java | 8 ++++++++ deployer/src/main/resources/canal.properties | 1 + .../canal/prometheus/PrometheusService.java | 12 ++++++++--- .../prometheus/impl/ParserCollector.java | 20 +++++++++---------- .../canal/prometheus/impl/StoreCollector.java | 6 ++++-- .../embedded/CanalServerWithEmbedded.java | 6 ++++++ .../otter/canal/spi/CanalMetricsService.java | 5 +++++ .../canal/spi/NopCanalMetricsService.java | 5 +++++ 8 files changed, 48 insertions(+), 15 deletions(-) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 6b0fc934..aacace84 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -98,6 +98,14 @@ public class CanalController { port = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_PORT)); embededCanalServer = CanalServerWithEmbedded.instance(); embededCanalServer.setCanalInstanceGenerator(instanceGenerator);// 设置自定义的instanceGenerator + try { + int metricsPort = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_METRICS_PULL_PORT)); + embededCanalServer.setMetricsPort(metricsPort); + } catch (NumberFormatException e) { + logger.info("No valid metrics server port found, use default 11112."); + embededCanalServer.setMetricsPort(11112); + } + String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { canalServer = CanalServerWithNetty.instance(); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 9083477d..1cefb7d9 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -4,6 +4,7 @@ canal.id= 1 canal.ip= canal.port=11111 +canal.metrics.pull.port=11112 canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java index 485f6728..18bf63f7 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/PrometheusService.java @@ -22,6 +22,7 @@ public class PrometheusService implements CanalMetricsService { private static final Logger logger = LoggerFactory.getLogger(PrometheusService.class); private final CanalInstanceExports instanceExports; private volatile boolean running = false; + private int port; private HTTPServer server; private final ClientInstanceProfiler clientProfiler; @@ -41,9 +42,9 @@ public class PrometheusService implements CanalMetricsService { @Override public void initialize() { try { - //TODO 1.Configurable port - //TODO 2.Https - server = new HTTPServer(11112); + logger.info("Start prometheus HTTPServer on port {}.", port); + //TODO 2.Https? + server = new HTTPServer(port); } catch (IOException e) { logger.warn("Unable to start prometheus HTTPServer.", e); return; @@ -112,4 +113,9 @@ public class PrometheusService implements CanalMetricsService { logger.info("Unregister metrics for destination {}.", instance.getDestination()); } + @Override + public void setServerPort(int port) { + this.port = port; + } + } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java index 73102795..867e1e89 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java @@ -1,15 +1,10 @@ package com.alibaba.otter.canal.prometheus.impl; -import com.alibaba.otter.canal.instance.core.CanalInstance; -import com.alibaba.otter.canal.parse.CanalEventParser; -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import com.alibaba.otter.canal.prometheus.InstanceRegistry; -import com.google.common.base.Preconditions; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; +import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; import io.prometheus.client.Collector; import io.prometheus.client.CounterMetricFamily; import io.prometheus.client.GaugeMetricFamily; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Arrays; @@ -19,9 +14,14 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicLong; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST; -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST; -import static java.util.concurrent.TimeUnit.NANOSECONDS; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.parse.CanalEventParser; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; +import com.alibaba.otter.canal.prometheus.InstanceRegistry; +import com.google.common.base.Preconditions; /** * @author Chuanyi Li diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index 3be87c3e..d345602c 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -40,7 +40,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance"; private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); - private final List storeLabelsList = Arrays.asList(DEST, "batchMode"); + private final List storeLabelsList = Arrays.asList(DEST, "batchMode", "size"); private StoreCollector() {} @@ -100,7 +100,8 @@ public class StoreCollector extends Collector implements InstanceRegistry { holder.putSeq = memStore.getPutSequence(); holder.ackSeq = memStore.getAckSequence(); holder.destLabelValues = Collections.singletonList(destination); - holder.storeLabelValues = Arrays.asList(destination, memStore.getBatchMode().name()); + holder.size = memStore.getBufferSize(); + holder.storeLabelValues = Arrays.asList(destination, holder.batchMode.name(), Integer.toString(holder.size)); Preconditions.checkNotNull(holder.batchMode); Preconditions.checkNotNull(holder.putSeq); Preconditions.checkNotNull(holder.ackSeq); @@ -128,6 +129,7 @@ public class StoreCollector extends Collector implements InstanceRegistry { private BatchMode batchMode; private AtomicLong putMemSize; private AtomicLong ackMemSize; + private int size; private List destLabelValues; private List storeLabelValues; } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java index ccf21020..9ac5a834 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java @@ -44,6 +44,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C private Map canalInstances; // private Map lastRollbackPostions; private CanalInstanceGenerator canalInstanceGenerator; + private int metricsPort; private CanalMetricsService metrics = NopCanalMetricsService.NOP; private static class SingletonHolder { @@ -64,6 +65,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C super.start(); // 如果存在provider,则启动metrics service loadCanalMetrics(); + metrics.setServerPort(metricsPort); metrics.initialize(); canalInstances = MigrateMap.makeComputingMap(new Function() { @@ -529,4 +531,8 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C this.canalInstanceGenerator = canalInstanceGenerator; } + public void setMetricsPort(int metricsPort) { + this.metricsPort = metricsPort; + } + } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java index 6d823be8..a4528800 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMetricsService.java @@ -39,4 +39,9 @@ public interface CanalMetricsService { */ void unregister(CanalInstance instance); + /** + * @param port server port for pull + */ + void setServerPort(int port); + } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java index 0d6ad088..45926348 100644 --- a/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java +++ b/server/src/main/java/com/alibaba/otter/canal/spi/NopCanalMetricsService.java @@ -35,4 +35,9 @@ public class NopCanalMetricsService implements CanalMetricsService { public void unregister(CanalInstance instance) { } + + @Override + public void setServerPort(int port) { + + } } From 10a80b2752a8878d434d6ce5df851b5633239313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 17 Aug 2018 10:40:51 +0800 Subject: [PATCH 088/360] fixed compiler --- .../java/com/alibaba/otter/canal/deployer/CanalConstants.java | 1 + .../otter/canal/store/memory/MemoryEventStoreWithBuffer.java | 3 +++ 2 files changed, 4 insertions(+) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index b328feb7..c7a90d30 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -15,6 +15,7 @@ public class CanalConstants { public static final String CANAL_ID = ROOT + "." + "id"; public static final String CANAL_IP = ROOT + "." + "ip"; public static final String CANAL_PORT = ROOT + "." + "port"; + public static final String CANAL_METRICS_PULL_PORT = ROOT + "." + "metrics.pull.port"; public static final String CANAL_ZKSERVERS = ROOT + "." + "zkServers"; public static final String CANAL_WITHOUT_NETTY = ROOT + "." + "withoutNetty"; diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index b276832a..a1ff4033 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -545,6 +545,9 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple } // ================ setter / getter ================== + public int getBufferSize() { + return this.bufferSize; + } public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; From a6abe7fa827cb1fc1d6ce3c2009112181107f923 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Fri, 17 Aug 2018 19:54:57 +0800 Subject: [PATCH 089/360] Avoid 48 years delay at startup. --- .../prometheus/impl/PrometheusCanalEventDownStreamHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java index f81033a4..c3c71102 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -13,7 +13,7 @@ import java.util.concurrent.atomic.AtomicLong; */ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDownStreamHandler> { - private final AtomicLong latestExecuteTime = new AtomicLong(0L); + private final AtomicLong latestExecuteTime = new AtomicLong(System.currentTimeMillis()); private final AtomicLong transactionCounter = new AtomicLong(0L); private final AtomicLong rowEventCounter = new AtomicLong(0L); private final AtomicLong rowsCounter = new AtomicLong(0L); From 4a10986b5fc16f98c601160073cfa21bf79f4b45 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sat, 18 Aug 2018 11:15:53 +0800 Subject: [PATCH 090/360] Introduce grafana template, update images. --- .../metrics/Canal_instances_tmpl.json | 1285 +++++++++++++++++ images/DML_model_indicator.PNG | Bin 35317 -> 0 bytes images/QPS_empty_batches.PNG | Bin 44056 -> 0 bytes images/RPS_client.PNG | Bin 36310 -> 0 bytes images/TPS.PNG | Bin 0 -> 11937 bytes images/TPS_event.PNG | Bin 37363 -> 0 bytes images/TPS_rows.PNG | Bin 42873 -> 0 bytes images/TPS_transaction.PNG | Bin 43954 -> 0 bytes images/basic.PNG | Bin 0 -> 55680 bytes images/client_band.PNG | Bin 45392 -> 0 bytes images/clients.PNG | Bin 0 -> 44562 bytes images/delay.PNG | Bin 0 -> 13534 bytes images/empty.PNG | Bin 0 -> 15727 bytes images/idle.PNG | Bin 0 -> 10031 bytes images/latency.PNG | Bin 0 -> 13795 bytes images/network.PNG | Bin 0 -> 17092 bytes images/publish_blocking.PNG | Bin 41387 -> 0 bytes images/remain_events.PNG | Bin 0 -> 10082 bytes images/remain_mem.PNG | Bin 0 -> 12243 bytes images/reqs.PNG | Bin 0 -> 13165 bytes images/rows.PNG | Bin 0 -> 21268 bytes images/sink_blocking.PNG | Bin 39320 -> 0 bytes images/store.PNG | Bin 0 -> 23994 bytes images/store_remain_events.PNG | Bin 56792 -> 0 bytes images/store_remain_memory.PNG | Bin 61006 -> 0 bytes images/throughput.PNG | Bin 0 -> 48350 bytes images/transactions.PNG | Bin 0 -> 16348 bytes 27 files changed, 1285 insertions(+) create mode 100644 deployer/src/main/resources/metrics/Canal_instances_tmpl.json delete mode 100644 images/DML_model_indicator.PNG delete mode 100644 images/QPS_empty_batches.PNG delete mode 100644 images/RPS_client.PNG create mode 100644 images/TPS.PNG delete mode 100644 images/TPS_event.PNG delete mode 100644 images/TPS_rows.PNG delete mode 100644 images/TPS_transaction.PNG create mode 100644 images/basic.PNG delete mode 100644 images/client_band.PNG create mode 100644 images/clients.PNG create mode 100644 images/delay.PNG create mode 100644 images/empty.PNG create mode 100644 images/idle.PNG create mode 100644 images/latency.PNG create mode 100644 images/network.PNG delete mode 100644 images/publish_blocking.PNG create mode 100644 images/remain_events.PNG create mode 100644 images/remain_mem.PNG create mode 100644 images/reqs.PNG create mode 100644 images/rows.PNG delete mode 100644 images/sink_blocking.PNG create mode 100644 images/store.PNG delete mode 100644 images/store_remain_events.PNG delete mode 100644 images/store_remain_memory.PNG create mode 100644 images/throughput.PNG create mode 100644 images/transactions.PNG diff --git a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json new file mode 100644 index 00000000..be9cc0e7 --- /dev/null +++ b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json @@ -0,0 +1,1285 @@ +{ + "__inputs": [], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "5.2.2" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "5.0.0" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "iteration": 1534552626378, + "links": [], + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 30, + "panels": [], + "title": "Instance status", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance 基本信息。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 24, + "legend": { + "alignAsTable": true, + "avg": false, + "current": false, + "hideEmpty": false, + "hideZero": false, + "max": false, + "min": false, + "rightSide": true, + "show": true, + "sideWidth": 500, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "canal_instance{destination=~\"$destination\"}", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "Destination: {{destination}}", + "refId": "A" + }, + { + "expr": "canal_instance_parser_mode{destination=~\"$destination\"}", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "Parallel parser: {{parallel}}", + "refId": "B" + }, + { + "expr": "canal_instance_store{destination=~\"$destination\"}", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "Batch mode: {{batchMode}}", + "refId": "C" + }, + { + "expr": "canal_instance_store{destination=~\"$destination\"}", + "format": "time_series", + "instant": true, + "intervalFactor": 1, + "legendFormat": "Buffer size: {{size}}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Basic", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": false, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "inbound": "#bf1b00" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance 网络带宽占用。\ninbound: 读取MySQL binlog.\noutbound: 对Client端传输格式化binlog.", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 1 + }, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(canal_instance_received_binlog_bytes{destination=~\"$destination\"}[2m]) / 1024", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "inbound", + "refId": "A" + }, + { + "expr": "rate(canal_instance_client_bytes{destination=~\"$destination\"}[2m]) / 1024", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "outbound", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Network bandwith", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "KBs", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance 相对于master的延时。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 1 + }, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "canal_instance_traffic_delay{destination=~\"$destination\"} / 1000", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "delay", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Delay", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "sink线程idle占比;dump线程idle占比(仅parallel mode)。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 1 + }, + "hideTimeOverride": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "clamp_max(rate(canal_instance_sink_blocking_time{destination=~\"$destination\"}[2m]), 1000) / 10", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "dump", + "refId": "A" + }, + { + "expr": "clamp_max(rate(canal_instance_publish_blocking_time{destination=~\"$destination\"}[2m]), 1000) / 10", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "sink", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Idle", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 6 + }, + "id": 32, + "panels": [], + "title": "Throughput", + "type": "row" + }, + { + "aliasColors": { + "TPS": "#64b0c8" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal client instance消费binlog的TPS, 以MySQL binlog events为单位计算。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 7 + }, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(canal_instance_store_consume_seq{destination=~\"$destination\"}[2m])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "events", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "TPS", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "iops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "transactions": "#f9ba8f" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance 处理binlog的TPS,以MySQL transaction为单位计算。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 7 + }, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(canal_instance_transactions{destination=~\"$destination\"}[2m])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "transactions", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "TPS(MySQL transaction)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "iops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "rowDatas": "#7eb26d", + "tableRows": "#c15c17" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance处理binlog的TPS。\n1. table rows:以table变更行数为单位。\n2. row data:rowData events为单位。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 7 + }, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(canal_instance_rows_counter{destination=~\"$destination\"}[30s])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "tableRows", + "refId": "A" + }, + { + "expr": "rate(canal_instance_row_events{destination=~\"$destination\"}[30s])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "rowDatas", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "TPS(table rows VS row data)", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "iops", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 12 + }, + "id": 34, + "panels": [], + "title": "Client", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance接收到的请求统计,结果按packet type分类。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 13 + }, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "canal_instance_client_packets{destination=~\"$destination\"}", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "{{packetType}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Client requests", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "none", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal client 请求latency概况。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 13 + }, + "id": 18, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "25.0", + "yaxis": 1 + }, + { + "alias": "100.0", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(canal_instance_client_request_latency_bucket{destination=~\"$destination\"}[2m])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "{{le}}ms", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Latency", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": false, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "server响应GET请求,但返回空包的占比。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 13 + }, + "id": 26, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(canal_instance_client_empty_batches{destination=~\"$destination\"}[2m])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "empty", + "refId": "A" + }, + { + "expr": "rate(canal_instance_client_packets{destination=~\"$destination\", packetType=\"GET\"}[2m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "total", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Empty packets", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "wps", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 18 + }, + "id": 36, + "panels": [], + "title": "Store", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance ringbuffer内未释放的events数量。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 19 + }, + "id": 20, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "canal_instance_store_produce_seq{destination=~\"$destination\"} - canal_instance_store_consume_seq{destination=~\"$destination\"}", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "events", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Store remain events", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "decimals": null, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance ringbuffer 内未释放events占用内存。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 19 + }, + "id": 22, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "(canal_instance_store_produce_mem{destination=~\"$destination\"} - canal_instance_store_consume_mem{destination=~\"$destination\"}) / 1024", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "memsize", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Store remain mem", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "deckbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 16, + "style": "dark", + "tags": [ + "canal" + ], + "templating": { + "list": [ + { + "current": { + "text": "prometheus", + "value": "prometheus" + }, + "hide": 0, + "label": "datasource", + "name": "datasource", + "options": [], + "query": "prometheus", + "refresh": 1, + "regex": "", + "type": "datasource" + }, + { + "allValue": null, + "current": {}, + "datasource": "$datasource", + "hide": 0, + "includeAll": false, + "label": "destination", + "multi": false, + "name": "destination", + "options": [], + "query": "label_values(canal_instance, destination)", + "refresh": 1, + "regex": "", + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "Canal instances", + "uid": "8vh8NGpiz", + "version": 82 +} \ No newline at end of file diff --git a/images/DML_model_indicator.PNG b/images/DML_model_indicator.PNG deleted file mode 100644 index bb999511eddcfec80c7fccf4421cffd8c8f9ce14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35317 zcmeFZ3sjO>`!{SyJE_eyGnFT@9IeK*GBr)1)1+2rXR}0WOwAN9@dyGnmNwG?)6|mG z8c&r9c|=g5vWY20$`dI9nkgazo&W`JcyF_wXP$Tc-*3I^`@aACt@W*Ot&KPCeee6) z*WTCRcU{-*Z-)=L8!RziqNk^4;OVjVsGgp_r=H&2;YACasI=AgySlT zhr|t0o>>yn#trvB?C@(ERzJJ#IA`wgjVAMwPOm?F@YJ0Ptp>cFy{=moVP?3z;rh-C z1~<>GT@|+6bDjR3P5ZY5-OrHn+h275^}Ff){g!vO{YAg={}7EG}My;8bNWfd-~D+8w&1z{WuZ*=csnO48qc_)4%SzCi2Q(`fIJP*Y&h> zg?{zQ*Q8>FuCzm^9}!sJKjq^zN1SF?SB;sbd2Lp=hxo&BQ%`(O>PFxAoD4|#nBKle zi1Yp1+~8Bb_c4_d`?Qy)VUd^rnzwf4^@KH%zy7tvGj{o}1#2PKAFLVsRpcKZsAws5@}bgGj_+Txg7J(ly1nD1DuDr% z-7E4@0WD_5J78l`nsM`7W=J&4OjVLO>=K@Vu7Nsc^2_|az3N4njPSQ-a^mpgMKOJ_ z4%3E_j?y#i%k#lgs;7r6{Ao91LJIkWdR$nFT=q(@>!*zOj0ZiD=hoMB@*BQAQEm%cOdTsO0315d8b z+VYxb^1QcJ-`&(JTl`IOZ%+?>hs=+!+Kc^p@K@I30D4HYx1*(*b^|Zi86&jh&rek; zFdxU*Tjh8`yn0=1btXlUDLW&?hKz4d6 zONz}N<5Xk=*Y>dV*Y{0h6T4e`$4tX8xbS(+_!=6p@SoAK_e1=6iS9X@zm)4H!~ zTxWUhm~^Qpi?B9!RhF`9PVmYfVua7S@fA@C0Xy_}xA?zvo{o>p&3jkon*_grd4Q7y z=J_Te&GI?+01eVzJ!goaVJxxOFhi@J-ztj@4=x6*-_AfTPmK}&5kA!1o(-f_LJX@5 zna=UIQz{KYsg12CV2|Bi_ohTGzuLUmbC1d0nCRQjCrT{DLXC*>FTrbHsrTFq~-D?m<`+2zdWrkgX>qI|&o4WDq z6d?Dzqjn$N-fs2Xux{~(LQF4w#kVn;s=1Ab&0R`@g~1$s6^=8MqW})#S}U+O2h_z$ z`C+89mDr|HCtnqQPvsUvPwI;cu}na;eVwI2Vzl!qM)8^B9%>Dzjj!U#1?< z^eGlGNB0fT7qsmmMXN=GR2s5eaLTQk!S2j$&8?$hwEa##ssZw`OrJ6o6fMBn_&46d zsdx-4vNkLC@p~{ShL$evunGK3UKqkLC{h(mVG_sN=;#xt2c+AtAm` zl3a+5|BKB0!0O=M$~+gx%Z?kabPC{co4aCQzl&VMf>AjkcMyajIWbsc#)KkKRT@@P ze4O0rm$2{S4waFcT}kb`B~K~EF+W@r-~W({rAfRWM?tdPazomr2_@?;j?;xroiZV} z$|h)NE?!O{l>iM+Jb( z<;QXq+OQ}XNyOIyOZ6gJ7kgeg5Np4?MOl3Vy8RN_Xgp-S#gne~DI-^;;Sc8DysM=A zhSs-QX_l%K4R+40z1IGHM?4{Sl@!7T6e=$JN_V(N7c?fMa#@~+elaNHT(g{Z>h|t) zFE>yq1Jz5HIKQsSUlSbvC}pFM4;C)Hea;*K&vpuK_?1vWN+oc3N+ey~eopg(3asg-0uQ_KJNd>5 zg9y%EnWxe2gXVtw<*^gH2noW59R8fgFOn7AyGC#vyNxP~>9Yn#mk|v`pUM^zSENJb zRqVcMeH)Bg-ztf-Wm#gP?5un2>x7W$2d{}~%6!!pfunmH11N>Z;&?$9xSXDc2dRhB zq(azp`_nKV==L;QUL2u|iBD>*DxHW&sA!)?tEs$-+jQ38>Jo~wnuyDz+EuG+`QFre zlCjiD!b|D~l(&?WPK{~++8S?P?;leoceo?m_a@LGIu0JgLs#Bz5#gUX5{0G2_teF$ z(V5v*4ZJAHLuQjEC(5vhXEG{pU8m7j$wl?l&r<&zVyUt{cq&y<$sk72k~NKSIGK4YN1HHMzYoi+ z37_;!ro1ogQdB!&1RCnpr79Xx#2v?L9$++@34ALwW~^&4=NMk2vfXh?lXFH}mgh)> zb%$6{Pqe+jp)t<;G(&gLzf>ONlmtjYZWwq_CR5URTo)~v##W-LGp&hkPm$Tmo53Gm;lFF_axi8&$a8eR^6SAF-K-2L9;u4iRdz!{vD$2ekOC=yYE@ zarF>I_$1o7Yhh>OYt5f`wJvYkM;w-?$3w-AH6JmZwvw1LvYbFBss7faK90>t7oum;wHGDHO@ zw9b&lJ74FQ$E)U5U}ZGkt&VfG{*jq%UM!mTF~T6Tte#l)9{)gT*|@cP;C@gjm)%n( zfO51FI@au#t;EfrYIGA*O>|;j2ARPBdX9(OICRi86TP_-jBZokh76mLNH72QQ$>$AC z@g;mjBrj0oO6)S1YPURL4o_uuQ|);{HRND&rp%dgOGWEdnz`cTF==l6tzNchnWJW7 zg#oXB)o_ES1JU_|QC?ScYe_{;6V9k3fX+DxuO@4!=jhdlujW550PS%hB&G=3V+geg zfv!hEZx-pV>*7VLGJ(t4&d-*&+Xx4=Y6xEA))2%dOnRfRZR4cLf0x~68aZSNfS%P)kJ!7Xx^tWN^4^sP*UP;0)!UN`_9%zPd#;}X2qDpga%(Nw_ z35u_{Ny-_9mXoI?hybq=;yhj+%~cwT$Dem*kNbJDaM~A7x38R6iMzhzA_;9mJYmkvJF(nt^{ZcE0RN<}g&4`937ZVhv zss9)!bi{?)|P7oBp z?TtoHSF@4aLUm&nkJI6RhM>%ALj)y_e%uP0l)*d5jlh|!{9-vl7k8MR%1~;nt8r7f z^UrQ3a^d7I7Ib^!D zMOJ8D(J_q+zxpIt(0NJ>wpR6Gr+3j*y^dk%=j$|&a3dD+Pg6lM0w>S8>Ru2g?y+2% zPogWRNkrK8cWD#LjPxpr{8oY?ZJuFm;yH`h&7mLHw48-g-LQtiQ4-{SI$3oxCVQZR zRhT3}wQ_A@D*(q#C&?okLavrUQ>CWlayN8i$B7sM@*&(N_RIv{p@)>&!Tf_ma#deYHZH#AVis7%dbl2?% z3Nw1&l_NUZAN3nIzl6hscrm=_{i@TeN)rVL)g}~1=OsVO^!16s`t>`wjh=Qr+@Nso zQ*`@Rw+gN9Ydn>t7Y{m>{i-`J=*^oNA9iWF1BH$zC-Jz0C{6E}6He-QQ_A%dXyC0( zd>rhULm%x&&FhHzYRyFcv?S!&GBM*ml<0>3Lky7?O>uqO&br-F*6!6Q(W>|7v-_)Y z+zOs9HM&!2#C_At0%s3F5|2IQQHz!ZkfebWgUnDg|Aiv8`K4Bx8hxjh-xi3>xKz_D za--m?;%^4uuV{po42K0i?b1l>4xlxRD99Vx3dztWlh62Wfh`}g$g{{0YN_wK4{}!n zX{&AAnvQK!mM22!`ubQ*CMwf`xr~41S9YNJ)N~>As=Tzf&&Y7X(k;&js2?(19;LE( z>DgSx05!a8Zhi=w_awTkcp6>vOAuH5Dv}&F`Jt9EFcx#4szb`K!pLd~PCALe#GUby zTLZY>dx$cPb1lA_x4xbtRydXPGH9x?C!%d}Y;V4>*QJI~q{1~}Jh?$WVuZWOA)#cn zywahDe+e|nImh-jc2V9(%8J?O!DTWh-@=J8nHJ5#j;jx3^ghNtgy$f^ZMP+moE`86 zrgfa+63!d$aV*d!(}G+6y!=mflN99{BpUgiicm84r3+aVn!U^}AaEj~RkgHJdMTYd z@hWU|rvdlHC5q@s6(>}UPZd>^UJ5`$R+mWjG;URxl8)^BU8%rWB3lpgd|N) zg4_A9X0&9}feKRqaAoGWr{i*@TPLdfKgoYbAk6KeR1SEl7mWG>@Fr{KMMps3+#ZH? zMv+vkWU}Se+VMd+18(h;P!es>(RzdzNF|TI2A4V-bDu1W?)0eE9g+N)ea)+?F}LJK z1CZPXtv*|<}3XadrXlW;gkeKuiu&ady2KOs<< zo%zL}t<4n4i!>>GvwO}%Q(m}5!ZQsl4&>pzxanO6*cOFL0<`v`x;oR_^zy}AAF8&< z@Q-kaeQkY*L-Z6Q>r@e`-<(nuMRIDSYp2Q-{X9gsEo+=6$~rmLO?(o?M?Iw?YgyhZ z{tM7({Y*8ZHm1R0V$3Qbx6RSiKZk$_*Qhz8k44+6kcD=_b}fLdEO9T%M8ahRuadB+ zfCg3<0ahD*pO+(6JC_z#b0^2NXj5C!sH9-Zxih$vXXvkLklvH~`jj`LiBB^N5k-vc2RD@(1x1A_b0MNr#~Kve%Z;(+f*?5H~UroU6VC7Oo>@YXux!Hj5Z zJQ7>N?VbL5PDJeioQ9x-@(`V5o%Dbzae`qa4QLQm7C&h$3i}0DNpv`!^c(kOAm>$o zEsHA_bMd`pjBWbLX-g&yi(@Y zCHRn{D@BT}af}r_{D|vo$uGx9L1MdcEz4z$2BEnfdisQx;V&RaZ~Gg$lR>jz<)%?` z-}hBjp)^5|Fi!t{W2wl!+fmcCYIsjyV=|8#a9`mPO%n9$={aSme0Qj7v1jYf2{EzT z7kb^*W%yvmXiQQ*JCJW-iGZdU8u~?LmLg!ULJXTLAvtLMpJO05$MvJ>^PB;OR=+)4Lgo7s4%caWx{AS)C7{~4w5*pDx{Mxx^o65b}>VU zy3udQxufV%bJR%pR^GFE9UodQ_6#sNk_UugeFvON5t&XE$QE2k0_>Pu1Gn$2FVWWy z!O?PG(Yu0V@}C+MslJ(c8mO`=`gF8}Od#apwpWC+$6^~}K4DhNkFDS#pJ}&hUgan< zk%>4BTW}G@JVTB$3El`#sy1ZC5VL<=(YSfE2L2~tI{tt~xf5Nb!d>7EAW2w(c)STZ zK)2%hhI6{BrM72yQ0_2>_BIUju`2Fi5=VNEiPmT{jx&YbbYqlmLd#O6ogw!^>EsvV*&mc|T#V)VQwWBxtRubCJWmw= zNx8l1HD?3vrcxwO4rf%=vX4j#aLo-kd#W^5!R1kkxWYJ%8Qx!0Ov4dq<)ATn5s3<` z)9!1`9S#9ADBJ{ZG zT#Ple5(y2LLwjN%41EZu)=1;lX;e-#8kCNO_ZzO7c$GZRyX*JvIgdxL+|0`nexi5! zG_d4^v)))Wr}PYi(Y@=}XE>yo{=33?`@PN3Q!PyXh#o5rcb@7SfMK%Q1kqVCCovcb zxM^5k@)O@KxgJN;xE31PouT7faGNjI=*aSQZXfQ#VbbwIray*H~y#kgaGIwqz&|047?>1JKf9m-HFkrZsfcugQzX7L6(8sk9!QuCPG@mo6f5I>ra zkpy%wy{HMG1_~A)loj!ii6ZESklJX~Km_F8DLb)?Yo+ku$=YSL+HS{#8*7(=v6S4r zPDcSGa@>l#jE%{Dkba5O7u?boHgOL@0)R4dU{0)SQMdMiAZDZvOQe3&$VbZ0=N?r3 zHboKt#{GE3n9KNrA&Zspmi;u>=HR1vae zd?mc9@jlP49B^+h+>W7$7~vDk=LgxB;43 zx4S+k_7@)24N$lxz$)So<>6xFfzj!<=<;SOq}l|7HPG1)={QS6a}_uJm)7G7?c39M)^z_b z*2x8G$xo=Q1kiPf??%M!IA)u_xgzJ>uycGZ9t;{6dUsRl*3zr zg@kyM$QZ6Fqtust-dDpInPNwK1$>)|oZD7ScHZfR@}L6|@g&K0qt5+Lc*oguik{4S zyWgw5ZI14xgUe2gSL2q)Oz@BK6|`f@~1DjSQaH4-Rw2|Y3JSG&4Nk>9>sf7ky)m4364 zuNHfIcU6r7{YYMlk;5L`%fC(LnI1yw_M+5Sn|NIUR@K`Jy7*u3q|+jwqPfl6hF!N>nk~2o2##8WUPx>qlkGRx@3MIEHMJO> zGxswVjd=G__S?hi?3@MBe2;Hvk4Ww3~w;)khRE=G*Y&5 zv(>rtuvG^W-wn`Zq8C!-w38=kCz@(X(!kbQdXY`$XI$IeG9e0%S6+Qw@HOA_>vSJs znh55p%AfQ+)2yh4uj*Woa>@5NEa9NeR4#Ko7g4*-Z&=}YE{qo|a+jU4y_zC-xbJu0_HYt|n4imVU{r4M>vn?-=bpv^@UB zQVOFC^RveieO#|wY>BKU@DIWGwQ9-Uf6M!Ot*dxOAw;o;nu)G_ZCyB?HBdkyaT3trwzs? zi4KI(-+WFQ;Z#9ivsSa2;}d?D>R`7utWn*6WKyrgs<1!pZdv339!KvL22eLydN=KV{`iw(l*;OZwV2Ku3ln0a6_39P!|ZdOlD7*WETC_a@Ja8E z{$AVVvuW*DR1e?@{GRp7R7Z}Xp)Cr`td3xp$<-}>Lf<69|@lUnC|3~8fAtXip!-gjN zC7z_^<2ffxS`oi8q>49Ao@u(^fgZ+ftyQRTs#2C`aJ=?x)QSwlOoU06EWWNgQueZ= z2|Z+<2hYK8<`HhEan|9D=RYDzl-FvUv3pup#ci4V_|wEF^>rWYt!cz@y2gaxj` zD=W{1#n969qL*I?&S^jb=YQo0aEQ75-`*)bzCIt^zvT%*Zp#AKFrvc74O&;F{AVe_ zQ|8ASf~k)9HJ`s~e*W&G_kvdauAJN0%t)zR{0*0in#tKx?JW_BoFj^P0!BU)Ye?E$ z5v!>#Z>*?36VL!t)E2`dcGStIu80LG+mhhR>Q~_@6x}z8&lY>-b@e{}S5?N$X?bFD zwB80R$WZ*87_n{1c7HYd{e%c-Ar|m53_VGbm@A)5Nh9Ses}Ie;quJBa@8e`$VV!aY$KPOOR zV-c#0xy3G*;svX(-AX*WO(*>@AZkJnQYaI6jbt61;)No(LC&E(y0r2bJ!BZ5`_CO3 zT6i*bv7V395>IYcgza3;W6S(7b?xSIgYRk?b`yK*4?eG{s)x_d0lmyUW7a6u)f9dt zYNIb%JAR7mc!BMj3?5uLl^KqB{(vU?@OpQNoc|^SQ^1uI5vb{EIKY>lyfs8&miCi#k7iJ7pqkzsHhK zU)J-`6`V0W<-5PWnKd*!<9D}&ZM4{s^0m-lY-Y0EN7uM}%v3Px>Gd!6q&&Hrzw`E& zwZC1F>VFJOrkZ{+EO)If>kGe4o$LH{LEhkkK=S|+_4)q8g7`fxnp}T3u7I0w&AiW) zR&fb_0520+s+9sXNpryNmJQxy^BM`fI2Pzw$;%~PO6Qr1ZZH+(cC79XU=DKL*GKtS zTH<7OxbzOp-x}w8iF0V(hiq@n;MDzVw}NMhWhMoxwbqtvh!bbR9(ww zYCa8Y(T$<&8%Yl%5hK>|Oq#lpT~O+mvYN1wT2Fs352YN?HK8=8MknRk#xratRb9{D z8tsq4C}di|uPo#H)an1h`h_(9K6J>K6hG!=GjkLh04ku^LhT&2GZBN{B@&_l8EUzUXD_v zrF&4>mtuAeo~5-$f1BDLo>4}?Q4b~t)x31v@sZY_D~W&@%LDI6I8fb|XkM;lyn_zZ ze5B*P?wf`1P*#&5yh1-=E6yI)!ZqOI89}dF&;>7KMTG zVs(ChL!Q^W%EfZZ^Dn>kWEY-n(aa zT=6Dv4d%Vcw?+#e`My?aTi0On>T}<39ZqAi1=1~5IUhx}f?AARt*zF)dWG&3)rc}W zRSO%_gw6c1a8xGWyuY8}g&90EtJD=Clc}Ud8@TO8`$%h|&t5OTO zsIZUP;EtoaTM`DH5G#<+m&s5=Y4N>Z%ULM&rCXW>mMYZLZQ7%@+{)CtLlmU>$A!CD=IG{dX; zq@JfsF$1bEi67V}kD+gScmLtP}Cl z!SzmlTw2iT{T=O(4;gqSH`=Gz=xmqsy9<3G^=G4EfwEv%>WgwqUKPz2v{OnXk(Z}K zv;;DKHvYQLf~w?v66^`jcs`Mo5~ahX8+s-aw5kUuyga&+ehux3OHYYf;c>g6Ub4(n zBR?%l=%y^{`H2;K_SfF81Ej80`gY;A{J*bXK8EYe;GgVIX#^ zW%jaA;$oB$cPm(HrPrbD?p%1m>-jdr7xbN@lPOUf&xT{GGez_2ffnAUJv9w4&X?cJ zOR3Z)V7eI6{?5a)cQ`7Bn!RL;bpzWIq^}B^rIDi=9hc+es8UUuG>QwSN%0>Zh&pUU z>T>;$^cU#AP@?z!+*PSblMBUV!nFop6|5Y@c}k90x%(ZPx*vNb1WQP~jQ+0hw%l6U zIscD(y~~Sd zP#*nb?-}DkX{Nvml4G#@h2JC(O5saeG@+1?5!c#A43TiqgYM`LX;7rYRkVKSg^SsT zz=Q?8(Unyttv2DyzZr0)HfcLL!&`g%N&Q2JykTVx?xRUQ3;#FnuC);3>vb7}gPhLl zE21@5hixUFwDXa%gzQTktM1_vIpMqCm9lj!YLE)bjz<|QQj`1_bsYb8y<+!Mub=fC zmQCDV?AhRWD(t71HM`=RR}V_l1Xc!4PD#Xyj-*KO*n~8LiUs36UGt+7&)S;2t)fcw zQ^VkYaZjixdC@hc#aD5fj!<>Nm@0SaI_o{`JTpx>2V5KTF3${O*?3C6L)K zk8@L^7E1JIzYJMhX21AvxkWg&r-d0OAA-vwZ$M zmCFKv^Gw;)c>C&=wChFM$rX|yplL`*h0Xg+;7+gRRjK$(3W3#fGySxH=~Ay#7%s%M z(?8P0#ucn!y``dPNiaOU-qN*R&Bv~_0bUKoqWYd@9r&^bJK4=Yq#}QPCq~^gr)!5Z zOjEVVoNaT&wYoi;@tr1jZ6{3%zMOu&i!tEwd!m0b)=XV^VPd*tUa>Xrll%>=CQL!D zH9zI{4&PV_xez0C%~qr9*$N2{zeN_vOPl`(+(W&R6vR^k9qoZ#<5%;O_q;38HmoZy z%1OXP;8g5Z1Yk$3_e{-32Yj$&9`r{i8J-0^ghlweY|@|pRH0okP-uwYJ}Vr3zM zlFnA>tmAl^!3B4C%N@|Zs$1=BV4{KT_)NO#0EWcR)f4b-4Lq$;>(?~w%=k6AS-u)@7L91!gh1nX0r_1ENi`dTX zmT(`rx2$m2&>wqRs=VIaOS%r<>9XuF36*2WDD6zTUysT&H}vFb4l!QPX>z`l-rwbh z|F(-T?;h=RTtsT3W@cv|0=lt8fG{SZx@kf0h-?P zTUP(q2={**3jd!PSb3R)lcD!N`Kis1ra8R-$pH#2QuPRVh@D<*bb_6-^oLNo1kHXGc?=ZfX2 zKOAnoa{E-r*S9ntn;b9m3fs{Wlisg5eQbYv$$_kbfmU{Lv5Z+I9e27CZ3sS(yXWiU z0&t4@s zZ`HzRTBYwrDBq@N_`e{{bX9%4t@Js5Q(ebH+f2s;Y%sZg7ob+-v{uiWL9ywxO38GN#zkwxen2eZM5vl9cf%6Xcq zk<*EEzb+exTx7<7xh%t*0@uu#v&u+qaGT~|$jlx;s+_HWG&%76l$?GPZ@ zrwbBe4orvmaOwrxh+v=W0&8}Wqa`jt*7Vp~7pIUd+E2Ci_aj>C2Kg1DzCCiLwx2O` zZqk%f9j!*%8||>EYcoOczT4-3Zs8f=BSB~9lUuK*w3>)bKlAI}7a=$V-E6G=SkG{v zuL*H}1{<3gVgz)VeX0|UOaBasO7Gt^dKxR^$b9R^Z(~meTp%FpCa<;YCW&}Vw8;Hx ze#D06tp{>zP|+ezeV77<$FI7JpNYQZ-=snTaR9u&BK~$g2xL9_;<;eR2FBDqa(sA7SNnuNM+brwfmZ5xHQ%qW zqN&B|`focgth&3;!`Qw*o2h)#-Wp=KpW<@BJImYsTE18zEfEiR%al?;Hgxey(DTvMskId8XAFUDoF!|~SZ@1-s zcA`(MnAI(5MmNbher^#y^B=}K5F%quVyqs8u1I~|&ulll0)`SM9Iu3}%!&MQX0Ia0 z_q4EdBR*Z1w>>rRBusX;yD^_(1t*g7ncYU)Ya0dBF7q)67^s(ilN#9z?o(|_<&#*l zY^p+(b)zzfhSrq+!u5cB9XglvtjdXbJN5oK z#7>u*@NG-a9Lu!p2A8z~T$Z+oNq8l1v@4N?Ds{!z?tXS3di-X^weQosyka}DV8@c5 zYxSa3)^Sc2;t}t$;CV-MxYo4n{^#1i*dTbH6WZoh%(0pidOY1Cv@6$q1hPPH_t8Zm z_X`qJwq0Ct7hkk_N8c9z%HE#2SB_5Jpt(tH0ZLGa2{BnCtH`Im^? z(0g43@2Hv5g3re3;Ym@*Co zv-u(pPnPbszPKHxY%15f4gt>HX#?Jl%Mr{s#_7#}-Ej%&nPo5NNO>)y)sy4Rq;s2k zojlR?wTID@n>b#mitu_yFK~@LhLBgpdo!@py|`-5ETB5^ZR!sZ8S~7*FbUu3AP+|Q z$+8~z*6f4uSbI`FrNkWQ$u7<{cPj$Lb2PTK5qT6O)ANAEhWxkemg$5ku}&FKY7XKM z{$ldyGcNX94FAhH%U62yH%WQ=efN5asQHg4sL~|1lMgoC;NZ1<^aFd#>?P)`N5S6X zTG~=i?4t|dQIy|gdCGs11&(|G*2E;_YP)Sgsck#par(KhVbmf|+8>enO4acy3-Rkd zCTU@TssyT;gp}fgju4Pkw}-IRsnx=pUFM%wroPq|FXgzuhg)&0t`_aXmt#wm5H`8bhRsYbzGRoGe1eZoOm3 zs+}#83l4f3Pdk~ai2<{b_=zP@dJg+69ea2>FfjNiB-o7@_ z@f{k=o;aVf&J+-ShX@J4tbO~Q8ZcuDk140@0B?fsx-b*&;j+xS2%)akm|o47I_;*a z>?fy2oILYT6JGD?9~GfyoWDXc(ZvK@aBx+{W0FH>+HNJZ`EJv)M!xshBv@_Pi~7Ts{Z+C3Ig~BI zX=YU0NsgD4N2F421WfxBvqR@Snc!uSRBQO0zLae=^2)5jIRnaSY(v|p(KObFgw{%I z?mGqyObqDh?M(kS`%ArgR-X$!BhBrUF8aExo1lCCYm2{!F@9FRT}_D!oDjZmH2VN^ zw3IGbMS2cKB6@8qDi`|*Xmk-LC(Q1{^@oLfcN)*0Vf}_i$M4vx11&@`a~(g!HA?W@^U4W+kdYDED}(^N*9ng*7M^si{SC0)D_q0p5O zRJr*V65q)36CY!tFfFP6eL$Uk+h?|!k_}M&;e|hjN*k;U? z=E$=;W51yI`i{#-Z4nuQiK)AwWtvk3M&JHR$F1h-yu8eTzd2eH1DqmuuF6gAoy^MZ z%U=D|_RW9k6gO_)jPd>di4O@bW!!JZX^bn~1qZp7a4DODPVI z8~@*M#DDQ^XP0Jy`xS<{4M_jE`?UCCWO~0>E`{#vz&(L%2>rKzuhuWTxB;2d9pjGy zp$+lJm4|b?!{WsYivE%wdzvtVb*Zzi1$g!Ml;r;@95$Qh{GT4JiW_{Cd@@#ui#?_i zVC6&iLvlhXv$$OE;*XZu%jbyop(9t?57^bWO;58?c=U)pNI&><*xDlWbr_|-ny%Ao zNDnJp;Xj<4t#;8nY-b_91BR4Gy;}S{)YIb};+N>+DXW=|GX-GBaj*U3PZz}JASXzj%6~tCkTKf zAOPsmhh5mHHTy{aae^QX3kb87s0gAR!qp0bP9uaUSX4x<*2h1-fHur_5_T(L;J**Z z(^}`m{nDoU5JT}1YxoP;$J`c@rIX<$^@hc9SA1hPY~o-p~(ab zP-$iW<`Es5e^v4qT-&Cr9PwytW6j?mTNr0QXbF*?f`Q;K&$qH_a882 zA{nMEs)Q+b07WX6=|N4Pe<85Nr&W882`4o54-%dE3f44ue{&Hqs#zl?3Qg2R2~o8! zlu(EIO&b@`qF`xAl`w(6St~?d+@g)swS#{tA0(>a;1F0V!H?jVp=61EBkNx1L&DX4 zX!kBit~Xt~ocpKf9+|1TUnn8&*W@`#iRM6%I`GWZ8r$m7iPw%2bs*235YEE^Wp)KX znGx71VDl~n%H%^36)PO1)e59_2S{rfNbArb(YIT{I_RwlRD!hbSPRl>p!SyQ2HjyU zLqyd!kQ^0AF{)L;!se4N1DaeQqbaAo4#!$~hpN*d0KhR%Y+Tgh2!Ifl*&e zDUq`cPSDwVafQ03HU+Cclra53$R}z&E(p(BN|b^;R?5_YuUi%tzdbdxNS^L-`D)k_m7dksK_h%}Jo1fuqy5KfpjzplMEuBHee z@&hw`OjWWp4?xZuMFXSI4eCDJF)l=21ojBi!JZ1Rhs7u3EyN3Ue3z0mSFw5c^iF`S{~KxH+Mbj=7PVqg5cdpgTLky4)3to zP_f9<*CoVu&fUP*bN|+k+JEl)*W`D7!7N3#eWu>qErE0X)}Gu?ndzEcMcW~dw>GzDKZT{XIK6lNk$;(}fJ@5bQ@wUYoz4)Q2{SSX@8CjpZW>wlw>DBzEwU$BR2ekQr zW9JNOXIKh$&4#TX#(d_{KkT#rhkgH?K2VVV5#E1vAM55nrsE&e0b1ez2FvTz+&;8_o2;dUTfIF+5JZ9{g)GG(=LsKtgG1OKJVQhyGtp~pB*VftYJs+6-VT6Jr&ambF{dMsjmnpEr4nNQc6Z%4 z2gbaoJwrtXX$T{U5Sv0B2y9#r=vuvDxZ^q%Lseh#8FAbY6J#_|bBhlAZ)e|02%~?l zs&?KYa^a z0>EKvaPoeTXcl}wA)E+Ku08S;rj&pelHq&WIDSqZY~Zh>>OjWK1@4J<(ha0&vVMjF z*d=gE!m+0?L1uOyOknL_72ttsFkO*4kT8~(8@@4psprhLIPG{B%hd1-KMDgtG)mG^ zKs3rewCa1E;U)gJ>OR_4ye^h8o_&N+w-a>n%c6iSH$RrgJpz6I8x~T+$KR~?0Yo?D zhsU~~0;Yb~+E2s^3qgDc>z*F_U5g@Ucsy;~x^I9jXEnej9iW};2QiA>?MmGn>s(U> zq0dCPN&%Z5X;9k({ADr~h&lW6z#Bj)LV7@k;b}h;rsvAkeJcaHAk6PtwcB@DN(rCt zxKHFh(iH~)LyvW5gzApRFu^?#6BQOnh{B-v5FJm!QiKxxa+E9%2#P4$ON0_uC9ESB z1V#PFa@B1QVb$P#Jeq_k%~i7S-vVWjD4+}mf;gcVL{9@}yCCCRr9@hmk~J+=2ik%M zLrjJZfcK+85G&LNCl$N{@naptQ`3h#Fu~HKg0M{pk^mh9Hzi;O22sGjWP(6WzF*S^ z+}HN0`^qa}Q12+f2s(MT6_UH7MLU_$qDZc>g|eamU!Dxey){1e`Jn5w}4~#H|P0Ca{6z;e2#8u;9Qhi2W8Ru@mIjO_0`;Agv$CuwYCV zqyh%g`VB}c@*M%AbBC$lgL_y6Qe5vn!NQV2`qqJ)C;$Om{c#Y$m4Ul!Hv=@QK>)`S zLsYT3p=w}s^*Rt}t_>w56pGcF+)%n!8&XJy*?MdGM3peDk4_Af2|&aS4JCwwJg$!A zLNvQ3Sorq}mKMZ`c#sv^Oz`(pLN^2r5Lk-0289-6FLr`8 z4Q{n+=LG8km!JUoI0G~@L25)GXYr3gaPRpZA}7(oqdmi6GT4(_Fe95Wilv^Yd*EAW zmumeyj3ENG;tnz4)#u^)TKK5wdiS&43e=Hj@_fq0}o+W8eIb_Rck` ziL2e?SlVK%6@9Hms}em@XkRU;s0avS(H1XU>J_a(P%0<_0b)RbWK>$IH{uiZjdlD=Ly=g?0fp2PiL)jKIj71ILT!8>^*VzfB*i^ z^Dy4JeYinWPK)V!k;lf!Pycp_T>3%0^hWAd((rwYtz$@TD(k-zFPp*}xhugnW$4eU zh8j8iB0rTB8*DRM}$Y{4^+Hn-oHf{1I^gIGrGZVp5xYm8wD1wT_-zPl>Z3i}KZ5f~*uTVSz9+xB&+;Nk+LzD3-0r zMgsdltCfQ0>V4^K2xd6kKp1QgUkt%T06iA)ntuLzDN`S9+PVPhb9Z0ACG0?xVJ;1_ z^d?o~c>@#BiG(}^<5dQ0Rd}~#JH|u1`}_!;go&E%BqJO@LPzL$6kvs^k7)H%xi<_* zoHxdDc}83=B6ykoghii%2>%&F8QvJdGRFuWRY$Z=#fYFzqU$hDsFrHc%7-{oo{jsG z`icEKg5;y&sYfxiMMDY9;&2EGxs*`*DN+7f>=lnZ63XkW;3!brUx-uSmkX<~P@&%) zW91lnhCqk**x%qK2pD0YE$o{m+)(5m2n$l91ofq_12v1&^m<7ltAV7-U9XK(wficV z`ajl7RpLTchlGCg%X;{0`8^&nV|OGEs7}BaHgv$9l7W>HOc@6wj*irjBMm%U%XV_z zIX2$w`mp9;cti+fm;uIw@xQQ67vXl!n zagHStVz`INH1wSMv4cd3DFX@n8TM*IiJVDJ1aY;|%h;Y#<@(aYo{@wOunpWz2dEG^hPPP) zvSR_NQ)yUY5_O@1c@R{dKOj0h7F(1Gf`5N2Ne#z4gNjup6(I~tKP-8OnIM-czW^?` z7Gxa-nn2p`g;ualLevlT)clHw<3KmzUPLq&y&a&ZT^VjcO1v#)3TA`QbC()c`CGAq zO;Ybl0rF1;Q(T0^7vvE_2_!sV;toW)?fx>Air7zGIie?+i17W7HoQ`mEGRgU*?;P| z*u;ysA?tp zQ+ELRzK*X!qFfEFsVW6oKU+ww?BLT^Ic5RLV1Y zHLBk1g&TJa-cD@N>Jdj8){0$T(CT2rjY{@tn7$c1ZuVqM!FFw?RL1Vp*0K`4AZop= z{)785um7W5GHqP$>GNNxod(}#l%{Rql$wWcVwlZJmUuKwu(NrY9sLe0zT>j*u=YE& z^Uf7uGFQA?t9a*08GBT|v-JF}+%uV_{x2L++_{w_b(Aq4nymyQWwWDTMPuMOeYGzm zMu-kX9C;gz9vbh23?8-&U-fjnS5IGRV0`X~^R!e63RA{b)x3X;u$@*W&R=p%-La3o zbnc#?Za7cLW;~zD+GFS)Gn&JS3Yf+AzARg*xt44l=Fq+-sj+grFWMK!=(~yBGaBS< zOPORe4TTfb|NWxxg)~FUo}aQe&E4zYN>D4Jg}GP%R@X9(ph|A%4(m7hx;1M|Mo%(# zbg0Jr5pR`0hmBn9nS}`uVsw`Q?3Wi5!~;vl?Mt|mBvHKp^F=Vt2Fw(l`Luzw6WeHD zLD2vJrbAEWKQfRLUp<2D!G{w7o@$%@0GJLG3O~M>1F)Bsz#sxuBlZ z3QE;ppygx0Fk1jm1}DIdK$Bo?eYpsGKQ z3i9@KXa&<1w%DM8?O|ivI&{<(gb2uEPf);NUtxC!O7U$@a6)?TEU-)3!wK?A@k<|k z0iYD#5fTjaa&{EZ%Z#G)_^>De=%vH=KrhvRYQ;%FbH)1ra*B=CZ?vV}x^y)_xD>pE z0or^7U}-b}Ss`@U9rV16i5Z}UF0P=-IdB=ychBRO*syVsd<$Xtnlw2DS|Ii{eYgbP zt_La3)F1OxSe@N!zig=U^pa3nS`MTJ!c-_)XinFcOCo?~zjQ!LrG%lCCm#3-M9<{b zudgAQ>-*n0b@>y(mT9%Vl~nbm{RyidT1mX=AS(X{i*6+2;J2?0x@g6jU-0R-F}#Fe zprbK9#vUur4&uc*-xLGy)hWtJsCzKH6F_3*hk7Fk9eo8X?+i#!Zyz{G=dTE3TpR#d z(2wNfd(qmuBI1mvbv=3%XEU^USvZ9*-vL-P7=T*ZMZl_jz^as1Qfm$teXxUsfGd?} z;A3~J8}V%t0C}mtblIjPyo~|deFaD{+Ceg|1*rE7px$|ac{z~h^->a?rv@Ni7{F4F zoLLGOJ*OPM1jpBofD|Rc*e9$4h53pBeWL#w$lZD%cPPjw%7Z;*P}7z3 z;Vq#i|85On(G~StWVj1dW1@16u}2Lm_IcUxtVU2tpt&{xAvv^);_zAG;E;}V| z3kg8Ojs47sayFv70hZxw@Ouw_fida=z&3m76g2AhA|xafU}>?OxqUXa=mfM?3cGP= zt0%Oz{2M@-B&^>PY|ELT-1P3y;s|IT6TGzvIdE@m3_vURAfmb3pyll#t0_5Xr-Ys@ z)$>D7f*?Erp8}p?6*@*rFPDMqK*3~zPg0L0hX?Z zzJ;zZlHoTONOdhVyqEl1LOR-kg*XQqDJmgWf#QcItKj=r4w&1#o9>h*^ES_4Z z3(j+Jli=g;?@SIEP?LY4hs#{dbOvQo)rcWYn$Y}UBHpMi-)@#di+UW%Eip9kE0`}K zb;wv`i+4upr@Zro##LcCPwMd8JIF<38Fjf}S+J|98ZxFHN}pfXwrq|lKt*4$(ueW* zac$?+#AV~8O0N6_8E=z0!6fj)s$sKLepl|e)g=b-lBb=GnLQTra>HlJ5vB8&lElFo zcta$^8R?(3;ZW72+O~VjfqJIfa84c*<}M*^NHEo6fXn<~`Ns4;ckMLc;woL=Uh1yn z`nzVH&I^M%S1OSJESGXN#`{f>jjh1n(8jkVCT`b-Kl6(EI7(WuIJhc~*ZFjt#YHpQ z0NUg1?<}cG3HNWAbq=l+ za$Sza{S1Y6g)ZS;a^l^D9IpJ*Cnsk|4O#~^91Qs_X}y^<|7ja0c`Ql4`J88*xN!fy z88aR4CHT&_d1-h(2&d{Nd{c-n4wZFHHB0u+*aP80%L5`Gf4pYg)Bi~L2aSpBofJjG zX3SLYk83z~`n(*ZuqntX)s?z;@%sw@|dcfqGbU?Xd_A z2P@*20X;l^Ixj!uCVKv(oRFSFU}co$@+{swU&GWRK+>TId(2 zQ0XK=(9rrBK?5?!Vn`@^Y5#>8!o;lHI8kwoq}QS(Xp>_sbuurF)uYc%F0km5ImW^; zy#AnniMaC4JZyQt`oD#6ale<0_B#3fOQ>o)B!(ZGSDWnCR$snXZ<*L-E(}`oUgPk{ zjnu#}h8RxsZ-TfSp4lNI)!8lp={saNBzSz-u-?zl05h7skn_K>7 z;_DIf-)kswdo2TQukdI%Svps0feb%rri?zOOq7}8BiU~<<6T_6=E@>H4gnbh`6!fGNLp#@4ap`{vkuZ4(#dXUgN8 zGuDdx&X9PhdUo3uIIwB&SYx9g-GWDqMXV{AH0_6XANAk*%B@t4_vCT#Q?6Sn#(hX8 zFVI97yZ_R8zPC6;lT3k(6y}kzTvh;A!5;1JH?T(JNU@<$H7xMsVdyZFw(CD8*65(~ zH-GY%&{C_FAEzAa^{Ds%mZEfLyx@?;3q}iBfmmawmNz7AE|e2`DY@F;iSztU zJX7t9s0TDXPxH^}>9^fW;!6TNIr?aUAN69nl>Q<~j`G!k`PR6?Us5UWZC1V-YIV3( zZ0tq?=q_8Op}5pAq|QIFYH3?aU!o!)k!q4W+`P4#*ZK;i8H}-OixqIi9nwR_C;^4o_BG5d{u^got z1TQkJ&+3xWHD$#gAH5zr*3#I+b?LXG*Deh$WqzFYbXz&UhRnP9kF&P-4l3n8Khb24 zl)J3F5`+BwV$wihF8#o>wM3@NjsR=I^J7mpSmWA)2|Fi#6)oy2)jRBK6(}VMIw50CoBzdueOwxs{#3rZ{quYY{a&dmB;!$!F4DCV zIlJ3iClJMMZ(US4(4`RaYm_x21>YDh{mVEN%JZRQLow?l#cLyl0jz@ZdaAFI&55)?a%vWDV=99C z_H&qcY`gpLnz-_AL#@5wOby(n_2i2sG35nEjoNE0{ns#LoD{0|fk2ZQ+_vL+ng3FG z9)~r%lVYJyQ|^-r(zMNz*d#?`WLkP;LWKUzflhIZPDLH0s#Ul;GWA+{MBylx)fkc3 zq~O;S2~%4y*u%=G>E1g;g|v5PU2nSvPiqkZC$R8ol^qHmfqF?`a)&`1e;d5Qa!vg?Oy9~jrMSu{xmVX(epjwSV#eqEN!VEorxOc!qU z=5Z8vSFB^*lT3%yY)}?!0xXGRukRnEnAmZBgI)4<=>z4!xS_!54gRdw*-exq7kuJ_ z+^yMGWtvr&qUW5mJn^BPSIlJy=%K5QKw3Nj_VI&<4_tNyd}8=eK=<1uh%2fz2y$}0 z)?8V^!s#bl{94k3uJf?R!kCiLwA~7gv6y=~Vv+w-k!~hri_q1D%L5`VI?B(}uMwkm z*2u-ub_bokq~Hpz!LjYBD88=K4;7tk*|eFO7U`;Q^(^y>7InW250g;;87y{Xsq^~b92wXXn;)U^1<-zhlgd71SPndF9yO^7ipzCD#=eYCp{_+?W?LcDrGDX1m zhE}4(&_`%xiaPX!#!ii`&G_`J?X_K-+Ra~Z=XBjP?~hnIZs75*%SIVHtv>#ESfpd8 zRkEO*C~2&5zDXD4w$T&@FShGuuCY#)h4p3Zu;41{(+=lK>ER`Ap3Ux@XWEIy!6Dp} zY{_z+ZshZ)slp9!557JKyFl!kdJ=CUVzoMW;CV@Oe&S4(w`@RSQ#bo6Y1R@X$_&|* zbhZ1*+`Y;}iFX$iD$5nBOyjkn?sc@;=;$WOwl zD@rOu{Lq7Z`7R=OzkK)9n5A4HZL0plPs^)KL!AXhka{xNI`)a~9_zr$Zdw)oAl~gZ zIh6BYkF91t-MTvV(k-~N%L}&Sx!SnZT9p&W{zGP3`&Q${k((Vn%Slk?+2zxil;V+E zA#0v_02ob(ah0LaC!Tz+hp=>w>@HiWc;LtuIngTJ&xG^O$brh&~z7iGP7MP(%$KT&c5ctY(3yi=JVq@52$A9bkX*Kn#sBvQ8!7-f%!%N){{ zvP4eqUpiugwuQ9^zRXB@yb(3@jAVLYwPfOs?dVC$F=ZaifnL!kR;3kONs{F!BaYF} zxS>(TUbA??oP~Q<{j9?c76Xk*`mEji%uhe9;<-)P_Mo?_^j=Vje4^7e9-ZH-%*K~?_xp`@9axTh}=M&X3PjPWUC+$%f_5O;P;WuY)NgDiZGxj9M&yqFk z%YV|+OIB_Qi=O6q+iEyqli9XS zqKi}Q7yo5$XhjyW?72si#4=qo?Sq-?nm1MRE7n=;wu>Mlx%_o%sM%~_kZ z@YAr`r{Tt}omOW3XU6`0_WH5(>+82v*_%C^XL=oerqb>9uC^IFmD&AiK7W>bIsJ!) YsENHN=)t04(`#$L^;uou5p?7~0Y{knLI3~& diff --git a/images/QPS_empty_batches.PNG b/images/QPS_empty_batches.PNG deleted file mode 100644 index fd29572086c50b950457ba0cae74898877a52b2c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44056 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 QZRMvl)pS)$?^?h3U%d++OaK4? literal 0 HcmV?d00001 diff --git a/images/TPS_event.PNG b/images/TPS_event.PNG deleted file mode 100644 index 62416ba059318c9e1429a4f5636e34f5b8acdf1b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37363 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 OMMn=CAHp1PyzpNcYVHaE diff --git a/images/TPS_rows.PNG b/images/TPS_rows.PNG deleted file mode 100644 index d84b94cdaad94921bcde9ff9a38f9fa32ad076f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42873 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�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 CRkgPO diff --git a/images/TPS_transaction.PNG b/images/TPS_transaction.PNG deleted file mode 100644 index 9d0f2344b08db7907b0ffc2ed3085e74f0c10efd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43954 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 literal 0 HcmV?d00001 diff --git a/images/client_band.PNG b/images/client_band.PNG deleted file mode 100644 index 40024bcf041a6dc0170f26654d75f8fa7de62fe8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45392 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%0DLT+9USBwLNKh+)h6 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 f!@O#6^hT?fxTrH1Gmqls(4;ABN-$ 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 CqIvTG literal 0 HcmV?d00001 diff --git a/images/empty.PNG b/images/empty.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d96009e4b8fd0a5689c20547c0cc98848952f4bd GIT binary patch literal 15727 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 literal 0 HcmV?d00001 diff --git a/images/latency.PNG b/images/latency.PNG new file mode 100644 index 0000000000000000000000000000000000000000..ca531caa9df5590d6293910dec33d669dcfee78e GIT binary patch literal 13795 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 literal 0 HcmV?d00001 diff --git a/images/network.PNG b/images/network.PNG new file mode 100644 index 0000000000000000000000000000000000000000..06a5b6e1c578e030580386d9d9ac6e1414f39c2d GIT binary patch literal 17092 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 O@Q|U&{_MS{e*ZtreH1(Z diff --git a/images/remain_events.PNG b/images/remain_events.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7aa058837f0026dac26fff7efe6043ce6072ff60 GIT binary patch literal 10082 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 literal 0 HcmV?d00001 diff --git a/images/remain_mem.PNG b/images/remain_mem.PNG new file mode 100644 index 0000000000000000000000000000000000000000..7f749be1777a40259b2518aef2e440047247c756 GIT binary patch literal 12243 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 Lnrxx8ssDciEtu!a literal 0 HcmV?d00001 diff --git a/images/reqs.PNG b/images/reqs.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d27065139b050c2f413e3ea2c4c48668994e898d GIT binary patch literal 13165 zcmb_@cT`l()24tVL83AsAcABhDk30~k(@KbkOd@3W^-tE;P?uBRtNRr%@lYqzdlxpL*YoUD}kl`9yf zz#k1B4)Dzje&I>rABMB~Q;91j{WNR9hpU$2O5#_plt&SqnqUK;@n6X5IbXR#!G!+9 zkW**;cIC=_OgSlWO%KRc+E1^WTJ=8%N~6-hr+-gnj=~Gh(jk{$#gMqpcoj1X7n43( zg7tN1u*}*W23g)gDM?(loZ!G&FT9cWqAQ8c4Pz%yryUvz#@zVyV*8YzS-Ksaf2->% zs{1+Iel}tAN&n-;T5ci)Hg~q|9zY@?tBa}9aJtu|IiFGX?r5X9HKC*QVeG~6fDF)R z8t!{>=BpjLp5c3Qyf@}9cW4;!B{iO6O*4Z8iAm|n`JtI!x#^p}^fVSu)a0#&^=s|Yc=SlAhQ;`Je#3{h@Yyz}U4)Z!wsj8SGsRUkrgitRBZ+Xq)(oI*OiUxS=_F*r!+H>ZSWtI` z1)T>2ps4QJZ1H<=t%qE?$c41q_H=O%EHQsH#R@qMsfNA#u*Oh>Zy>K0+?b$rnJ zJ#IzWbU|lrF~|(hx@kDSxMMq!wRQH=a`BYB%vi~c>Oe`#Lu)R;Jn{P*0w1J; zU4w||Vq{7mG8l&rfZWq=*jmqd!5lzg$5FOnTTVyiY zCQQXbjg=U3i>5x#P1qL%fqYL*&t^`va7w;9#F@~Vx(Fscu?su}yD6t}z@Iy&IjWSY z(E4*<=g=-vpQ#Q0VhAtPF4Z!6Fb#fNVn}8&*NElk+xL{j=q6SC7-FQs3z zZX7l@JpF%dyDvh2L=Wc0uxMH2pLLPgKzn!TFs?t2# zmY&3Gb0WhhAxlzyaXA0JyqJ*Rf`hH^a=4zKw?40(4vUM6|2yGfINf!!$Nkh(&HRD~ zTf5JB$3?G(HEpn$F7@hA4S>YHkx(M5%sNZhS57ler9s>Y#J3;cGpuJG^{{(Uvce74 z%1!m+qV zpW(>zRo6!pG~_6?lGxL3jrSvheQ#^Twg@^8_Q!Gys&KM0nQ%$(XlFR{?8&PPL>tr? zRpz*DPiFUz!3{UbPh@OB#k@$n@iMh`-8ak2lQ%%zI^{pNMWc&~K;_z5^8|#AU8^1v z>k9teVW?20A~`LVuahr0O8fdk*!56v&syJv71(CymQf%R(7d}k z9&l)nk#Ag2LVop)_Uy@yFG;Y!mFTfmorEv%3;4sg0uE|ZA2YD38=RJ&>md$l%?jT} z!w2sGVaYU6wTHk^4Q}uWKW%ZB>Yh#Vn)jM{xU10Gm>lF`vK9U60#8ax8ADUe2Sl__-r+dwn&Ibj)5n9_ zrHc$TtFDhXe@{O8K6gzYBAbJa?ObR(6lt{ADnyu4=F_44K_B8-e^4@*BBp?E_>v^@ ziH{5RI+vQj7><#ycCmgoPT^_-ufFN#Lq-T0AE38XHu+0C$)(+CR_E!fV|x{DpG;P3 zT;DcNTI5D$Wi48Gr`?<-G>s>-8iIRgDmvUvvILe6m7EdD->o1TBq1;xPNh{^pKck{ zv`a{ImzeiM+i@QGOPma8g=@U5_YJARE8I=f`PF`Y)W_e&RAR+8riOuRX^0BD4t{gF9DT z+|!;Aza;(qz4!C6JRQGGd^W+(hQmR=A5E>m@dSN!VJXPtWQ(bQ&`M3vkoxT9*5v+M zKkZxnpzn>U_D$OX!=QmBX-87>)ms6^(Orte2cE1NRLzgMMDE?bGqDj6-6=Y}yY-<Miw@e?^M^>fdHj7UhGl zOKJF42#2hF>cO2mw&Y)v95+Y3uu@AR&XialH7X>Qb{RM{7%qYFcQFSR0qdYR`SIUI z>voa@P8Hxmyd)ibe?I|mSy`)01fF9a1Pu`IY2!fj{Xj0_NfJyfSkT}*sTGCcz0M_9F0rgc=)c}LYSsHB{i`}E!<^yhsthznNN7{YRFZ|_|aU$QI*2HN4$D>z8Z(4h=& znPY7es^RVKmR#)kWKhwz*IP9+%smN7XnPu27rFL1>*Fa{n|UtdkpAA5G4< z#LR5rQSjVV0x?Q?FPmngcnMg-X+ZCZ7U%Sv96I?W4=Ynt^DJ-#^~_vRK}pj{*dWP{Qt`Jyd(isRuBA5=W1s2U zMqXhPI*Caut^IG^ewjqm6Knb8To#TZ}dJcyk?{SRFVUnJ6E+a zrqwH}B3A824iCXFO4!ttI5BVQXufKG&!8TE25GFxlC2R$p(*TZzm^-0-Z-VrF5{DFHVeOciEC(w6RZ z5Y+HS(_FRm-Y4&Q9U-o>qT|)1hWb+KI1 zv~OA3oPuzW($JV-zhBc(U*vgtpsM$xtZZ-2WbmD2;j3C!ZZh6~^e6m0_`0^kmq%OQ zt%#kgCrpa)w|!bOOzI@feR6B_ic;>_kxgoogO;QjaDE$XUTMzJ+v400gnB+zm8J%6 z{-M{Dd1>0#L@xh&c{OW5n0b_y%lH^!^vBloa_<|{a=#W%@Y!sH>$hVoPJJV|v@+Z2 ztD?$G0^+e7;QnLYZ&y$~vq@SL>nTk5-G;Bpfaak;%}f_9C)eH=rLz(c zeTqlMAv4h$?*CDt+vqduR=rDjpQvi)vdjGSU1q_NK!EdgN)aIAR+W))9QwrAPuA10>OQq3Y3n>vsHU{r5Xdkh&5>QEZ zNaop^`b)>pel1iekr-D;xW#Sd7?X1+dpn{k7tx z$d*4{Fzu+J^iKbjA_ntMVdd(?IfdT7wXE$_Eu!ox6PjK|_aLouatP$}bhpWA^cz0=|t*zo5f(gS(M(#{ut zJAEFR)1<{#QgWS}%UFo32`?FO2Zzz!=3Sswf2ttI%79T^z+p*&?a(r(yi|VvnqMsn zwX~Ocnx)v=<3{^^8x3s(hlj-ID(6F#z;C;?EeCdgz)Po zuFIeUoAH2ca-&JX4hl2R>_-?*{Mhk6wdUYJf4^FB)UX|S7fx1zjYulqDmN|2s zSFX;Da~O)J4$SP#qSHi=KF65gur zt!wIKc{6wqm*RJp{X$JnxKjKp-)Y#6g^X|_=om$g#czqU;%KDrg+=!eIKPkX^K-o& zf0NO@Z@v}GWf(ZWe~`6T)h@c?o#!KD?%Kdt64p z+J;v9A86$X~(4^^5}BgEoK*T|2iv=?Kj;Mo-n8O?9S(d)sgs?hpDdt5TX9* zbbe%Pc+u>X88oXij&M!gO^%&Qikv~?N4#q@&pbF`_!}SZ^x@Y#?N2(C`Utrp^c6lS z5i;Vo=oy^_R2ZAxnJqo=$wvB{wHId08XWt4=Cu;EJ9UZmd=f}`8fLM3?8- z4i9HlRBL+u*nHX8dZIjDjb{GU+Tj_4`5r?I@o$K8-^ww!pPKCriH@>I7(r71UlBQX zT@1{2)OJ?}*c!M0ipN@(Ux)qpF2wwDKB>efq_rCT&+WM?GIabH&`lVx$?haeO;Z0~ z!8&(t^?qK>;#MkCtd4T@K?rR)3{OXK<*$G}NZ=V&a$_t~k8k0l_1?3=w=&`Z_w$_p z&UG;^RMT1^Ha!I)Ci{)8#yn|>m#L;4k~sNulU2~5!eTkE%hu6aab@{eGT38)koLOf zD@hn6A9Ua7o_TWD)Bt>I=;#O=z-a&!)ImNsd_I@$uXxI;(IAqoR7xb*<{zC-d>ttaa}zBy(>?!WRldJL-)1uMtvpJ>^obC3A=LIFUtvU4e4?23=N6({ncqMiS#On-#E zBgPQ_FF+;%B`pmeg%7)fjo2m=OL43#J@3N-*|FwdS$Ihr!EkFs9ThCKKg8_ekHgIP z7Uo@P0w?7V12jlWLYmzi;{T3D zdcT$-0RBS)EC*|{eAvG9Y7Ktrz5!mqbYaYlaxL2ve4e|k)@h$dlMrUKLsgfuct7F8 zUW#;Rq!jM$K-jK@m1nbZId;D*8+~2a;!aO|ej$ro{dW285VLE$%h|6Z2ikpGs{r>E z%uvzco{ZR`b5I;#6i^)deCYu+8|tmiSCi|Y@V@Z}oX?CZsqf`&tX_6+@5*+r<7iQw zmgAGr8}~uZRdH>AklRdqInq00f?aIk0eKL z@fI+;r2p9a0DM~%%HG*{rVN9@{3EH}@^i){153a^{h_^qm8w6L|&u}dfb7=Pc4bjm1tadmzAmd60u~+B*=0F+7?WtqD}K3P9NsW9HXtC1!t-EPK~Qgi1+ z62Hx8zVlplgH-!hqkGxl&Vc*qJ^SK=0?AnI&->=vW6(ih3z|7G^P9m-&?r`1+bSlr z3PUbwvC8^81=G9X;U8jpj=N4!)pMYlVo@Inb?;VZD+TutZ~z?7ufdZR7wMfAmN_TN zo!#%`8PudZ&7&*?UoJ}beo;~kB1&6Yso*<0M-uyuQl7U1UF`}9?JS2HIa_#t7T6QVd5e9;v0?^Z-7|h}m zj7Clh3O{j?S!O?cs{Xt3Pze8Nfr3(sKrC#U-Y=Rnldh@5*|NAl# z@|F)e_FDCmuuTiU2f|}2YVXRB>j6PeFU5k4qP;0le)P%C+E9-jdz)`03VBhcD#Iz8 zTl=`xrTDB>2U^Ze&0xwO58xi-&3-Qd{T)WxXG(l6?z<|8-3% z!pM&OlTp<7h!+1q`NV+j4%dLIl9I!@wG_qXpL5LhURafcUlrA?6&^DeP9 zeKUmo!*{MLfkOE>JX+02+*k2N&#Ldkr2XM```NLhzzf$OH8$-aM* z77-GA7`1kOsnD=y;;dpQad06}%<0IHYsDHmyG+3^3KWXTCwq$It(WXG*g|gTjGHz2 z`T#S0iiSyf@L{3GVnxIIa?dotr2hNoN9aN$+R1b`$kH8w3KI!v*@-Fdn?M}{_6fQW z`WqB{c3m48OOZvVU$`gCv!5Rwo?(phA#1aj@HLaG-F=6_o9|e5epZmrdFX-Mt+|1u zJ93An@R~~?<}X8(5H4+&v-+2H`_r-ttE<;7m5JIE)bNru7zb*+r0UQ$zKB3)GAv@{D ziouLdJTsR67&f0nX(6Tekhp`*@p>;ULw6gF8bs~Y8sy|R?2|RLy7d!Im~me*^gtXe zsWq65cn4S)h{I?*#27;xES#wxT7E#d=Ew#P_|S8SL4ks{*ET3lhjOUan%sI;s9yl4 zfg>jo#RbHraqFSnv>b|v=p)=~OwJSI>~c|izsvcor962X(IsdcHJ8-rRGum(R3 zPD_6KUZEv@0w&~;bF0Wi$umyJ#kfZUnED&uvyT0Es<(MZGUDB~$qlY~K+)c<1Chq% z-Fd*vEYiav!~+C6;HaL|QOeJ&of-K0cY)(lJ`NN@p_}*)3pfK4^l*e{A!IB6t@*1$ zObC~x-(ipDt~STL2lm@FGoWb8`ERuzn4Bau#>&r??Vm9u{ue5%?}km#9>W*R4fTJ= zi=J&b?q1>;TO3>}?Z=GUMvcOEC;o$WDitoOJ+@o%O? z6+(aYbp`P_rI2mFbfdG5eB-zp(3MM+8n=$FBTM3M%T=?o4PZjHrhLnUfo#T^dl)c>9_~ zo-RPgZ3A2=eSj1VnwN?W_TNMEnU4P_&hnGSg1H9yj8=Z&`LuVvyVouH?yW==fb<74p4C5@_ly+JD ztmnhU5xb*un#uTv4=7#4nR!2d4wuI~tS%A_Vsu?v=NEjLb1Yz!S>v_bH^ClaHUq!z zv~!LHaDN{X1ng$tRY!QQQpysWrY6MGvfhosFn^QqP+#4nIN1E`q!fq_J3%Y#eO-Vjh|h4|GL?SQ2ONRbaL)*r-2!*Gcb8ogl!FLCzR zYZ%EH1-u4Zg^(|GPaQMErEm`UuTFcN-GAZ+t;=`NtsZ5%SD(*Pq>%HFwUiZ;^A02a ziLd>=F=p=5mjsP#C>Db<#A3rwEl`BAm>aWFD#KXGoa#Ws=UZ4-$%0Iem{Ysj6G7p4~G3cr9s+=dCr?S_JvqeO6`^0Xxm z?E*X0$7nio$vpf@4N#yR5vjuI4>3?kH#>(jTtSdK6^ z1Ksg?Meh6xxb8y~7t{5wIzhE;|I@EYR*6HUNe`nUFTRb|@c^|t{;C3=BTC6=`nyA( zE==zfNMkkkqpe;gfMEErYY8z(9syiu7Z<|ilNTP&<_nCa@KJ$FFR~D0wYqM?SDyYr ze|NRqQ^q0!V0c?w8Ac3)<#Lm1ZEwbUSJKaRWwB&Wval^AgG^O@zK`1N+kBDb5n4WU zXTj`_kh-nBhY7e#16SSUQet6F9PXqfI1e5Q{)ZC))`HJu2fbV(O>EVK&oPrWDGKY|k z&3$K9V?)V!1~jFx$nAR+=GYMb>8)sK#l?$-{b!C9p8NX1ZAZV;1-c8>$6g1DZkyw5 zw3F`YyhC{wg%x2SZmknUqxKbFZi54pstwag=H|KD$S68d{LT7z?<9arVx^J2@QnF3 znXa0-+liv*yHU^I*80{))dB}e>$&kK2WB$@{n(uW&d$g8e%%V3W9uN=t{dY2m~p>< zVYb0*uLZYV3-zOMd%BKeExSWg54ViC=xM4qE&UhyzMjI`!)$rIGrij)p7*?(j`f*h z7CM_WEQslz#IePG)J#?GlI5v)+tPN_9xX9sfkLq>EP6-9Ccb2ME|&V96{ZS73*&UlxP0`ov=U-SfRpZbEXYLh+ zsIfrl%MBKaPS!IXs9qxoTTC?aiOKNgDugBN3iPCl`f?o|y(u94i9L1t;c!8WrYnu# zApJ3vVXV|hXUb(#AjffkUCWi|xHljcq4<+{40G?q{49`tiQl`!c`oK2L$5WhVXkN!~lp1L*^i~OTfjj-Tt)tf?~-*X{R=U1MzN2QyGY!#{1r( z_bMaMsiuhm&eZq%v)6e9p z>{*nPiu845w+-uEN-4q;C>imngtn+N`%*UjV-g|mho4E!>Zqaf&ZkY4v(eS>=m=u5p;lR|m2E)-L+S^tol!O{lo8$LxE3toUx)0ar!G{dkc66X0f_ z_7dENu=u)5dlsSL$KWr!+Leon5&#D+&T|}>t6$6J2e0^^S3uHe7FD}W@G9P6u=}qNnH#K-f7nc|xkFD=uzlgoY z3H>RuuXwGP&P8M0RXO9!cxf`kO^Dc=))lc2enAA{0v8Q>%R*yx88;V4n6rxeri_f^ z)Oi?H6du~sB`v<;vw1y&uggLf-Yof(iYyApU#JLQS3`rLkVDjac8)dIxcQ;tRJA`c z+>+Px1`XMgWrm&h#%9QPXe`3#a6YJ+zwOmEJhB;6T;kgWdr&;nmeV%{76Fz|YKd2S zq{0X`ishz{1<8d*JykIJaZhTlO~^qG1~6%e;e7j+{QNV{C;UUbt}&p5FKNQpofSt!1HlEtwdEwnsx5=ZpZas=-NWP;SKG zH8>)sP8sZXHQdr})OD0nmVkHFv<>tI?n15K4qYF%HiNmt#HN;!S`#BT~g@`9KnA{F~ws?jc7PD z91#gl2yJjRq0PC%Ca%1yURjgE^OAO@_&rq;j7w~<YYl5O3gcg1EOc+yNH#|{AgQa~ zwfEtXOPdUT!FBbI%G1@#-)29aaOgE{$BNNhh26ie)4sN0yajK|v1hd0#ARspi7DF; zFm#E}V#4_hdu{bu{)_X#4@Qekf)nJy^OPK0mBZv@fVja0s-#Es+#7=RX*sr{peyrc z)VfiRPCO;v#V5ss#ME0pf*wC@Y4c!=2n~HIZ|Zp`Fd^}*rJ-Y(p-^Fu0QU|V;`TK| z-&%@CqIeistVN}Lm?K8TS|Z9uxEuSJR$7_T7F+P4Qu)^b(c!Ms=V!Q>s6k`8anM1? zBRnEQzeA@iv*+@M@R97fZ}UNjS7-aGSBzlVCAwNnC3(&*{Kz^p5!B`eeu%cI{x38E zQ~>@rw;+p-&YI4GPB$)&8YB|c&@Z>9^!4>!frtM(k&OOt(TGWJ)7gfvCTToAtpfUT zB|ZVqjakNf972FM14kK(UxM#$J%yf~>|6{TVgN@Tq{>&8r%>R>C88R}=z*?z;QPFO z5Aj@ED%kS`euILkeHZ=pb&nqQl0>6((9wk~9@H^Y>t*g%S{KXZct*Jza{}BJijT?k2%JRB5ZrrQ{ z{^)S;0B2}2_xyl=H@$R~WN*L*sn>u5EPELZnHx81k_gT%v4LYecV#258#gGJuK#YT z=raDgaYMIUMP5eV-)t-YsN-H&W7B-$wj|CjmLz-Zdn_H042eF^n^dOz+R#V1xA2`+ zQty#4+GF*o#H7kRVYJsSihffNG?Z|q+_rzuZ=k%~lt0LhA9>VYaPWCQXKnrIw{LI9 zn+%`&99`sBS?dqd`r<4BZ=MnAEfsvI5ib3_0?5+gW91 z(gK>ttz|IkI)5ILno?sSx&PbC?qk&Lfpj*#ewM=E=VmS#)L7-N!&f1)KhI3p|Jivz zGd2V02wl<#cm}iRLV@azxt_b!5S!T8JGr!yfm6{OHo07X^@5lUBV_ydsZclY#-35@ z#NYUCOHtb_4;WiKYYG%}c*m+rzQ0W6mZK1tnSB&^!24}GjMd>?IwA1OpYIH1AI$~L ztLKXyWCBfR_j@x;NPV0l>a!ky;(B$oyw!xfv&Hu|^x~D>;bzP7ic?3n3&t{Bd@yp_)Vq+t1)z^jYP<^CzJ<)-;7Glqrww55}Srul&-4(S|DV1txx%>AU-#WLkHC zw@Xd2*nQ!?zg%Tav`5APQFjSDtK#CdHxhQ(c{lswO8z0?@X)%Td9EFIyKcWC7J*Wt z9J(gWtYZ0bBf-wEAMb>q(;ZhYB#9-14u#NKdG(*_*K#>a<)S7A+MaGV`K<}4_qObf zev7y>E=i$7BkBn@d*y75T@mcRIgGGd=wi;(TE6^1+&y6!_P+E1uT{Ee z*pH$X?-Q2&Df_})MJ-8}SHyIVp~t&Wa%O6_2{M-i^Gubi-E-{yUGzIE+cDKiyY>_U z=#t{+P>fD*Vm#JH7o}N@U}lOzWrX)TFnNW%s{;ImD;^@iUDq>9*kxMXDem3I#Lnl4 zcAxD}EmZbF^PQcQJ{66nb7X}5@ek^p3rf_oFfitc2hDz~u8W7rg62_lI4~-Y47v$? zgIq;}4mbHL48v?S3nW}zKpL4$BWg^L+A29-55?$`Ag`I`A@z$uOQprksP|+~X+;D3 z$36P9wKN7E{=V00MZszIeDpop!TBMkZ5;2%S1RGqaWJI$OKIP3k@H&OyUlXFvRp*c zMAI7S!6N}&qEDme83r})?K2vdx_sbEjcq zP5^0*VM@xg_mk8nTGE*~*bshQh4IZUgk3rtz)W1s{4bf1h3}NYo+r{sTMpes$M3Vv z_D8P+6F0^*X=NZLtxUbzEX28m0Um9;Dxzy2LppnkJCjF9E74Ca?(2m#5ubOSvubpm zb=^lmpN{zCR5=-2m`}1y7tTv%)c(lCb{Z+}X_OiJkYfUbnpm$2D7FYQ^*z5KkqM)^ z<;m{Ii{o7~7vZ;)4DLe`vt#uxqjM5RGjTQhy8M&hqg$D^3ZByt6KPU&nkE(N?c~&K zeHXEz!IW9naYx@CMEej;hHo&!S@3NXt@u#Se~$r6Xy(M%*ww5#@Zfch@D72~<1^Og zr5a)p+D2~l?ksKF=HzXkUwyXHN{Q6g4<)8pX?u8@=SsYUjeXyXC;pZSIn5chAMDN- zTzptdxc*T}SyH4?UcN_uBO}W=yLtjvhxRg77wM#b>0HRScI&4J19LRXEwAN%QGSQ7 z7H%eWr45TSUWCmXSRimCTjDdA27_OsAbLjHEYCb*srt=ZzoYP(7Eq(H!-Xppd!*RnGvKk|M&HScuk3AF2y@V>Uq#;eQ*Ds`EY1UIdrGMiTti_Q*MS@^Gf4Qe<`@@(L4`(f2X#?+ED< zSoK3oo* zLmr7ElN#tk`-6{BPlC3m3_+FN)oO`9s?0Fy;cY?rTaz_?^=@Tp+`X^)$bm5Nb0*fp z13gn==!S`Q>|bbh{O#6$(m2%+l%$b`)PDHLRAQ(7%nZCEy|d25PJ-rGORm&mk)tjc zJ1DUmScEPK+Zp>-N=4uCn+L0X&O#_>QTrSr<2!4P(F*P~G4qn_IyW>kwzWZC<9*o? z>2WXRA&mGIvYmON`_5*Ax$>oBg%=+a#U87Z?lvz{Q3ugJcyi;;-TOB0M9S-k1HSa_ zTIWvo^8RS1zZD%mNhataMNA`RMI*YBZ!VwiP_=lKV2{AKr|hE-N&Jd%Mwn!+a@Ftr zxUWo`^GLS-li-Y~ui2zq;%#)oLGdR2vJmNhDhjtOGldBZdS$d8|Iiy7Kp@tZg=@Z$lGEvu=hzM~>0CkmfIHQdadnc02T6%F}} zKrSOhzw=~skLH+#bF_7@$JJh6SIv93Hgf-*Hw?D;+oI{Uw)tTf+Gy|UK7DA0$^+dj z{fV^HkXkMXyTRHIakM%b9=ys|l#7RRITsYN>7Rt03u(uRQp0+@bVhVDRq#3UJrYrE z7C);f5s7jqc9iG4C>u~_Q1E#0flp=Y0-EK)8m@r~D08#Uo8D?jy~|5FuSv&ns960JC=^7fs&pq&seetXz3&-xYZ&yt2qi+jqC_mMDaHVR0H_mfrF?Ldqc6cC7 zOUm}$?INthmwTOWN0-E$#!bX;zKT|)?aoBw!b^z86xPY1xg?z_$Pi{pl7{1hJ2B5i z4EVghaz^dS747OzJmW)oBHh#;el9ZcyM>RAL#Q1w4YTMF9FCO~$KZR0WD2}mQe0&+ z&B_}E7Hdt0&xoR&i*k>Y5g8E7`jZvYdSjHWaRu+uHKBemlCV(Y-`*1G*OI2Oe}|Qa zE&Jk8^nN$naUGWJH1EM@%cf{i*E~eL&esNI;UPW3!p0%0Dzl3WpBvW=eou@EzC5ve zFhH8l#k=0>F)Cc@f;l`ib&bz(o`m=K3a2cOQ|XG*1<0YeJ7(a1qrpBejG*bCg(frm zNjvBA`82Z%INCjxG{w$jCpZ|`t>ThHj%JTay};`~CIw?kxXo%eOW$2|c(9cl;u^}A zsPW#*;BggXVa}wskAUU`tMjvToP8BJ``8{)n#@85mX|Hf^ctKZ;>QxT$7NYMHBH#| zkg-`aSPDigrf&PJj(=9bN^$yykv!WhPW%c+9d3!0hM0P~%&a$XZY6KLT05JmPiJgb z_5BhHgf&~uMr;Cyb91g|)P6820e6J8yQYA)$0I(YkDPQ$=zLznjMjxO*+m2UdfQ-m zHmXiGzRSl@rVW#zy|R7k%(HXi01>nEPl@@+Dt|b)uA6(`J~qLOeewIOncX_V03x-- z$c-<-EE{4LO>;kg!17?SY=yieSdlKCtrszTJ?>31G;6PZigM1wZ2N@lW)TSn(X)$S zxT!o}j-CcHv zSZhh`+3%GS91Yx_Y1lWDm@}aF z3S1!b_2Jrj_Rk2LHaGHJ6f7>7VPMB+{8y=EYS?m)gjw-b;N-U$}@(x2P(aj+SG3IOR{ttZ{ahg-}$2kX{u zhq6MdQ^}3MJFo#9#gfW1V1TZvfplRe{J%hbDnaqzoC1@f@^wtQ<}$cI|2a3o%KV>m zsgEFj=Lc?oeM1vC0!PGOlM*GZI;d;TV(%6?7&v6&*8YDygz}MrOVMHa0-e-So}Iy8 z!z{jbFNHtocYizB!J)t!a=C=Afe(Q6_!6juW6Ems$vF&j&0+IA zdZn6iWyttr|6(Jg@5>8>t3w+=O{AXLn8Hc2b>2zw+7IQ|ee&9x_ThJbqVsj!wmaU) zz&jIc>MG{5?i^;sZ0alvja0Uba$pL2wHbFp@CV@2?i}L741q>?7FH5aEc_n*RyeTj zcJ$h|&!CtI;iw@Nyl~za&kf;Lu-sG)1MCOdb9!k);!F=+;f_+ zd4)&DQo;nOZrrlqGDA31fKAeq2l>`2?Wf4WMezLUJDKB1tyor>4I6p;}nILjNm!fVB~v5zQ7IDaYG!e{gNm8kD#1XI2Tn>jr2 zXxr)C$Cpuw*=CYKnA73NwM=v)h`w>6`YOtUYJIB8iP(wX{|gT?Y-7R=?(aj;fvD{ZL5jmYa%MpQx?v*M%wDWn;KA|M=74@ z^Lui)9)WF&x^=0R{!{j(981kP`qu};YTK+RLK;iW0x>c=0!)opljC@0fNX({gf24C zar)9zcj)V1jdTuWt_92cO+xcY;3GBsE&~q3Es-uu-i*7_sC7Q_!d+Y zp1*~@=eMT<1U7~qJG=sk05LABvHV>=tTZF&lLm=-$a(}t-lKP*<#d>n2$63(h+YenN^IYW4pX^g~76vS2*)~;h zGeRESO5e<>)E?8DUV+~sJY{v2o6YV`q}c#*Gr+vjaL=3MU?$9a3pQJY9DD<*mlh?M zhO8mrcuEmljg3Pep85uwA;k`fjl<8>HF$5qB!kTnq7T1ENe(Wl)JCzp>%-HX-$6^# z$=M;;iTIhRF2IReS?r3BK-8*D`0FNe5q5}bBXFV@pS<#x61G~85OBiiE%fR4e?1AG z`aiD{iEj`N{H6`n|ItG!4zBL6tEaLFE=~+>boXWcY zy3PP844xZ~pE9cHCVUG2tDl;og8s^Jxr1IKPV5j6Jyg&x_zd5#|DVfGQ}XR$MbHv` z$X`SBv>oVca*DtDID;D)fJlYEMq=rDY%Tv9FM|^XviJF9iWQf6zfZ~ zB<8~=m$*O~WH2M?H9sCB8oH2vvUSvfV@V@4**@L)5}=1YRdWNi1bC=~LajqvLO;XA zfhhU_h^FP9THhkJ@CBX5h14>+gzM}EG@k3dzaD>PMu^;k-`TggDUK;y@6F{GhL#7b zd-0OX60J0~eM$@x_!=Y=CJ)KM?h- zC#Q9Wcbx7E@`s#)N6Yo>>(+7ymZuvwYDoMct<+fvpN-txIK;-sbhT{y*Zs1HKi+KQ zmNO(2s!hLh=#?=<;mc3f9%zoufr+O&%K^B{*tJ=tn}o|T9Ap3|;hHdTm}|{A`GMXy z_{ylk(EKNqvNQdet%h)Bt~E<$SZKAZ*pNx&5zZbvn7k4!TVKC}ys$evq`{l@CS%sD zHHFGsz}j9Uo3@?SJ=pJ=jPUO4-4gN!$zba2%_XW1KPTmo_ZdzpC%7s^q(y72FT{So zyuj&CFc0IOa65nA=sfHCQ22C3*pBwS!7La8{}Kyc(xzR~ZJ+$f7s&t#JK3ix^<2VO zQwA{wp*R3tA;UTM?N-FJet@_9SBlCHe)J*sP>SO!E(5W z$sjqC+t?9ZMZ#go82ynX(->3<@Oaa#E`a>tK~+vRo8T?3T0lr8#5Ad$L_o=%LqucN z#i5OWp~ho$xmnpp4$iEV^BlXvaH!L8XLLYW z%vO!oud=_%(ue`f(8y&vS7RCh3t?Xs3SM3-T~ggJaM4&9G<~@-V%t;bCVb&Ejt}F| z+HPIctVXc`0hz=xGT2BTXz_5@;o=EFsZXvi>OcynZ2L})1oKtofo*DmDjec>(yL|n z>%UwyP-OZaq)uAfyuD2+P5_13vBU*_qy+ck4UpP^p935%UI5&3S`1^!7b$*I9!tIVZJZ86r8ZVhoW2PP zUr<0nr&i|ufPzdh^kDZ_iUGvmLZqH{l_HPVt1*O&eacJY{3>uOkmKIo^HMEzq8cl@ zixSMb){?DHR2Pnn&k8lt=jtab?M00fz)TT7T6a$LzCBIUQSrg& zwfO!dm;33-c6)IxK}wHDG6F9B_Mkj@|Cz2?lhoS-TNR*B!v7@0r(*6D#!aqu#Bs>Y zDP&XjXXVp`i}hUSq20OA!+pICX}d``plMlzX(Np)zD19KH1*Qvy};ty>zy&!P~wt4ZiUz zVsBx$hrizbdBQGL^0h@R?WN(_yr z1Ar{L@=TTCM071{$8r&yA-YTGAF-y&l~{gsY2p0DqT|W8505ZRjP9}@#9T3rlCDt} z5zZeQ2tcG9;a;qtvY%5Wvs*5Z(xKL(_-xiFd zIq-U{)Uz>OR=jVR88K~g@2@PMvnWPCae`PA0gc!^SYhymje_jeMMY?+(~OC(nCP@! z9rA-Fwx-V_)^VK;M?9c2yM27NQF~{f7GH8|=HYg6&JV39@@$K#cs1=nt0uKy6(a}D zT}f?>sHGzkONu?XM6|YrW)^8wvur!sL&cV2KYkH)A1ai0{*KSrx4o<7bII6NiTh`E zUl($GI8o7yH}aB-qo}LFSwA~XSTg!mpc}yt)EE+4XN=OA6FDEiKBtK>t5KG%`v$e7 zD5xY3&z9L>u!U2%WP#MVxKXO3O3c8-{W~YNh2lopZfb=i+amUm{M>^W=8l zU)>QW5%0YhUS`tEqEWx88+=AD_A`1;IMn_dDJqRWQ*!vl zGyW98RzaU2*S;_B=O@u6Xv2^pIc-N5M>q?PpH2y>Mm_yKqqZ~q!i2efEap$Xe<;SY zA1sK=qGF$)<$Cn1Hqmn`kpt*FxN)h|BCRxt!HG4@WkrnnXnU>-CvmHydZN2m z>h;Ig6LT!|)VZgM-^TZC%0cas6KTs&omQBs#t@|$vtNCF6D^t$#19c)hG+CTIEE23 zi|L!k9<3(y-c7mXX?{)l>|$Qk;WlSCw+F&=x5rbKyoU;l|2+}bJnZd$xXTIwQ7qza zs;G&3WVrxQUqc#9?HA2k4s}-=-EW-aE)MkK>h%?$oocO^h~PxLf5Co12WyjaO z64mvxyST>1?}c>6XU{%O!M?8z@0_QkCwNOqZ__o+g$dAJX~#Z3q6v4Ea5a<6S`5S&aUrmxV_wJ^`Ll6I8@UG~1# z&W*eE2Lf?;HIGu1rQkc8;w--O0!1w?Ebl6y{C-OHFi27wJ_?@MYVkXgg6U~S0iah= zp8h<#tBZ3Y>H^%88RC8Pxeb&O{h@gAQPFB_05025Tc|v^g-AEw@F@HGF{%H0jIO8( zWsToXH}=HvNiiw-BPuG7DiKTrLNWi0m{q?0-uz3V$0KuZ)lZUkZ(Dm3%gtHnsM|RB zZXF!D-8pxOi1`2!Vf ziHL5m=#qxgQWGb}`GAodrtbB+ihSZVZzZsX=gq{FL)SCdr8g#Djm=U*z1@?HYR@OX z_m^n)U`b*%j_D*SI8YVqg{mwui_;dDcu}*O4+>%k^5yod7t8cYsb8k9t3L3ols(}5 zXXyZx*#9oc0!7Db9uM7`a9K#pbB&ol)__+D^VtC11>YDnZ?@@9Ds=cTqbK;7Dhu%Q zF`vLZfZaj6jKgLv?0eM5w-65`Y#@q4TmY>lj6+1*_)FU656+2AW&2Y~;g7!}%hRUr#UIE%!_Zi9KY6Q95krd%yup*WNFp82 zeNv5>`z6+kr7yW5&cr|7Qh5NHeW^8Cxc|d9r%iz57$x1~(QNeJ)x{h{6Bac9Mk4=w z`)hyzP;@qZmX-0gb5@*~A=`g?^$S*olv7+r3LYP> z617=%N5+30N%*pfHV-?Sjb*F5pA)#VOyDu!_N`xzcpPl_P{`T&S-k^|Nfs9Y`^B&~ za%1~DV(*iS2{6uC$qxio*VxipTdgdwRVlu5v|weBp#+J`+B6?EkKs^ zV)^2JYVbuPSYh==sxI6F=#@z&W>=nFE5Q5HHGVes;UqfX!;jG=)wty<^)gL29Dom7 zkbWu@Lid5z#L%oc0T}8{(mi_}k{pX4-(%tx6CRRj1;N(heAU$uShl@lpjfv>EhjD8 z#YF;x#%}m)iL=&_#y(0x!-!dLIs`Fbo+>kO+}rf3QLYPdH$?;+wWJ0d&7DCvdI_35 z*kV&nWiP?_3}j%$7b03Qt5AtBR6Iaq4I(f#A!S>#X)2T-)7=^F+wJtop+e=;5)`0WQ(zPuzH9iJjfW1BI8GFa#mnZ}b^wm@I^t=|97KmhN36ND!l&cie3 zwH-w_5Dj`q>b%N#eqG_;+M6x`D*V35?!PO1Dq$DR`-OXbpc+8WkjX!+WIZ8lz&38bhIoylhfRlg{XyRYW3PKwUWl1 zBfithpIxmj?Ja2W{o|XqkJh|aRL=^D|9S3kO3do9JQJk<`;y;6=MZPz zNNTw01>1j~w|7l$2EqVxxo#3lxlfD7z{uP)m0)*7CF zEA8xZ#58U-2Dxpwp5QsY4StdBQQ4rzky1L&paRuHi06@|pfq zd;OH~w`Zz584w@HG4r$STxj&m7QfPGds|sn5ZK$g+nu}I*`r5ssa8A&3klj^JlQ|w zn^^p9#!q+}VKWAIqG^E{1fPX?p=` z@wn=ID65OiD&qky!!RLJgl~i6&cU`z{)aK6b=e#;X7zd@t+5JwY>IGM3+2bMpUclM zMm3%pv(<^!ZFWscZLD#Dh=YsEk1`x~=}P(T<4c^7wPIw1Gl{v%{sYOt_y!qg*$r$g z&E7?mSI#ds)HYSs{tEyz^6nfN3gOJ37;C5N*6eR}82f$rV_4bii6V2;*h$4xQ4VPop(wYmtQmzjVzcaZ^Ex8 zQ=|Oi5eKGCp)bi*jzlsjVKqXy%F%!6q1PSA2 zG&K4Z$>S!8MkH%NGuufL98(d`Hz!>fMb-ioS8eFWqa3S)Rb3s z{9>74e6H;b8ZfgTzS<0pgv8`?D0<$tec(a*gOM0>r^oo&zh;pBi5gQKe1OKzQ2hnp zk2i$Z(FS8-ukYXA9^cPQPPCvGnT-8)4DG5lE=>aR4p`>F?YEd}joilF zEtO8@!C{4QP(}@S;njOSJB5Hsxi7^8B)pGRNpx;^e~M;a+b%|W4;Y%WjJ<93NGi!y7^-o;bW`|8n~`o{w_AfQe%h`AF^n5hRew~ z*tBvOLn9yYj5CgYGqqW!)BKt+_JT`-fq6ovl(e$_;*<%HNsd5I$wPjpR8gk!GJarS zkn5k}(g<&LHec70%r<$nXt|`qm$KW%8sYYsOz>P{4WB|8flJXm8_Om3s^ljpJ7hRT z-P;!ssPi+$ZD*P^GAV=K?qUPJfi(oS+FXZ~hL;26iJsm)Cj-B$EF6O80X5dP)WB%=ZX2&JFclaQLe7P>mx>aqRX?fKG~3j$>N>`9)B5b z{y6N?b6G11vMZ_qoB&h1oU{bVtvcb5FQWM2%@KRy+>rDoUxHJMq}x2?a9hOrxd;1$c~TwU{a-J5ta%pvOWL_R z&TCDRj=jz&3-|nq2uPBX9Xz&W524(UCs<3du`jCRbzv4K7~84*pQtNfG%^!%vi?gO zWfrB=c1G|rbQYK!3Z&A{2EApC7wEm!0XBp~xEpb8_&}+Mn2;}}0{?V=Y%1L`l)N@3 z_P>ilO=s^m;1#Td0(#WHKmLdByJkfGuKxd8MgJ+1|5CmF_m=?7;57LawEVN2|B#&f z;|dw#Xf?-T|2c-}uz`;+L{9_|PaFXerJ?*t{&t<^qVS;)XXXD;OKKzjU-1Y~Ilp&+ zw*(&8*Lh7el?EO84Sc+*u)Nvgb$DS_W!#W}Ng0g77{H!cmulwI+W-7AY~T+!LLM&n z$&_go+FpzGLLqfiCP?4eyR>{@vqZI7#KA!L%P3Bus=3Gh5B{)$HeA=C>%WcmO-94S z&_6yaP7c|V(X=??QhZ`^ow7T zU6a#avsjajfH;~kUlp)ih6+M<>!*pO?F_)? zu5*X0?8A=aqzzY8YEr1TtK?oiB(!3@bKO$dOCIWm9C~27bCWP zBaK4}*VCFI4la8w>Rd?3Ru{A_IucND)#^|#>IT=o`2SGFQ+kqB0&2dcW}&E444 zm@-K?J-CSh5_0;xL1%h%%4<<5y)ER72s5kP^zP>r+)@upc<>#%@%4vKu42x%uPro+ zPGcDuH*z|xxLnrIllu&X@4jPni+2etMnZ4!{+_LpMO`TuX?<|hEVsxg0OV>!9}oy9 zRAW?Sg>L=dWRSju%@?kUBW%Wd&DOT6!kF`GJgHo#K{fHjdDHYn*eu+cosioYw1s zz-nRP*A@QrO&r^n6VIDR6)4xH=VHTTD<=SbP9aQ#0@k>dgtU2{uR_9AYSR87cjHgX zDNX{Cr%ipNXU-v=yg!Gol@fN9N;Wv>rf~Rx#QN=`*}F-#xJ8|Hz7A(z;yy2Ce$HH5lf76MGhD@ zJ`+c5yZ;WhyTnKaRow=1bd+$449U!asX1SB45-V79Lr0I@Z#XW(gz5KRPQ}w>)@LIuF z-+oT5Y}J{zzdu=g))Jp+$y62^7if2Uh?3D!?fRZ@d*F7`Qx1ayTngw59%V5iEG0?D zy`TQCadFm2FWhkwNxpwEx&|>mtI<6$Q{o{LG=m$RQm}>Kx?E&HWdg0X2dpU8?(IOpr+(kUX39N&ZU}v~(Td@ijQ=cc-43|0sUz1b`J)mzq;zQLE6QPMJ+bWzV zhJDd^0Mqe+lGSd4S1mawAl{8PjyT23ewo)LFD~C_?>kT5+xIxZsG_{DV3~(M`*XZV z_bfiw`!s&G!KaQMQvd`uOLN9gp4fr_f1;{sgqz7t{q%!A@BG%4l#B(I^eH{vNsJ;y z@gqq%Ifp^_zS`IlK7<*X5f4D`hQUO0l|GxheNE&kr9tUj%JWhapcP_Q1czb|Vm=r( zjr23;AzslK0nq@aCXO1fu4swk9=-*}Pb##HgsDyS*BM)$86r>bNfI*S5aEh?z;BOv z)hm|Qab-ew)nSI5!^aj<55Mx>Y(5U8Gl##aAF>2pv@Gg{iZU3i#ak}jVu$p!GJ#_YtSE?nq5-JfdCOmH%R7=mo%*tAid0W#SZle{x01Y;*tbJl z0Ps>mW!bW*4$J4z&XWr>+7y{f<+A!2`{g5aT1SWLf_f zXZrH$9(aU=mD;3GYR8yG-tTQX3Nn<}8w7SolAC(pt+-Y6$J4V0B283BV1@UP%X68Bw2k8%^ks6ooW2$`HyWO<^>O0e z1@=NklgiRB+QcX6wC|9NZjq+fu!R}JIAy;TJL<)WYj)q2*<1d6Ka)dj8;r7%U0xtC zsI5JRwc9|KO8Em`M#l-e-MCuQ^k(sK4`p(5{2xkci4xzB5%e1 z1y56bf>L~Kuh>MT@9Cr#=teiG$~yw>k=4}wecX17mLa!c7o~@vmSX8_k)JxnK*CLD zNwu=p&yo|fOaFn$>(Fb>i~92R@{aOqbYT(<1w_tSwI>!DaLz%MsE0FVW_*7kaiK%@w7cq|_MIZHK% zt9e=IVh(tq9X|A$NDhH!Q!0Up4ApipA*7uYT1C(Ls zl>6g_UTW~~jTm-HCuhJ@OgpXrzDnWR8*&F-tVYcG4SWl;~uX>FuAMIdmw3Kqc)+-x&omMuO z9eN>M|Eo}7JaVt~T$jM>aE90pAgxgck?JbYr&Z6N?ms(wWw-jvK&Ivv2yAo-591&G zxu!HQ8@|^|a60Rz+4(hEO=sj?stf;;sNGVUA5?HTt!8;0uo45vXIJ1dty^rufGsB# zt7557i!oXc*Box~ONmpxDvB02v;3zp9eQwQRr2#dn$L4a9=qV6Tq)O28>j>5WMr>_ zs#2{NgMPYofgiNN^V`Gy&q*s9z{@Q-p0gzQz^Z-$plx}fqz`Wc)9B)M2%wN_7qKr# zfk;5F_s%eXb`a~4yo zmwbDpzIOdEdX;=&g8hDPvBV1aV)+36wRTZAwL5HNguHURYWkU_XwY_E!eHRRrVAQ% zz4;gbdU?q!WssjjA=B!)?bc_S9IkctLk&XLKD_HK$6O|@Hd5>ltq^`;6S0p10Q)l4 zd?;6%Dtyji9-fVRuz~3paC(Lw3#mtx`tYW^NzwXwhtBxN%#hdwe~~p#uSx6VfPvkl=x;5htZgN*U`aqz-B##a8Z&i zFsgHnB_lDSSFR1|FzG=8+$h#ZL8jK{(AmrsoAR{6v(vUKE(1SHZ<48p35hW*CxpR84&-Wvj7oyGMj z9FX_EUGHrZ4gMKjZrl_KrLkIy8;*!cBfa*I+l(4Az99?qro4le+a>xhJ#EO0F z6vxRx=aS@0=s{@pN~K<2{qNaysGyB90kR;c?CoAge-0dO%(G4|50?!XrN8z+w~|#F zZ;{?Jju>jP4pD)2@!et?U8NEZ>~@|!PG|wljQg0KFkyhz=37K-)qh%DCK%p4K@VVN zUuSYio(Txk-D}{^oNGgS*^Sgsklhz54&W7s=pIzt_186QUJXe(WhL8hvbX^D%9DYJ zg9|Gi1Fe1iwrRZIo8z5gU3eR3xxC0I3S;i*S*3Tsr@v4f=2ZrB`?tzqaij~=$X-@R z09JBY{c{yc?-ky}`rxTvEMxk6mLXRcGF0>}zDVC4HXZonD|v{kn0AuJyt7b;Nr$Ck zBDJY=+tBy7RRg~cZhNNknji47F-WFK|504yZC+%==lDsFfR&X2ws(a+UV>wi(ehB3KA! zDqAr8*%;W>e_m>NfwtcIF>LkabfvJ0&V;+!mTl@SI4R&uSO!i23k0`o*c!dafBq9? z2};qXHJ48vAnhy#-2Zq17J%aEoB3!sO&rL&sal1iRNP9=*_Ye+&)aqN?;**2nYDkF z#_5laRm_HU;rKBI+|rh>Lh|nO64H$KdOKr2wg;9Ez7qs?vN!a{X>3eBh=9@X5Vrdc z;Jmcu00crFoU$;4_46XqFck3|;Azu; z1>bjD`i?P`5NDoWdnmB_F2Xa|-`BZ!%<(WJI=zph)OT~zw&P@JS_jx#&oxxIE6Fpj z*dr}+70et(DHjH8ovAr!mEH&Ts^Sev_lgUq>FmVZIW)~RQR|s&$Vp5ndVz01%V*sU z#xw(@g6ljC#obq2SMaLcYU%kI;)AHyO4&s|EQXB5INZ1)Fd>8__lSNkQE^V0d&hM1 z+}F?x*idauyY$LA{Txb_4{WNbadVd8X};s{73TzS4U_|b7QQ9-yVX>2jz{j{hKX?& zkc}s)D%M8rnL^0a#e`x3kP~pHQ2qzeF#y9C{ob6{f(zBx04|7wLEbV|%j_}Jj_U6e z&x@>E`&pgU zB>4py4KD_naZAjD>4^a)hLm@q4t&|v35n8pD_xga{VOJqWkb03N;HZdC98d2&3J4j z=iNDQmyX9)H0Mprgr%5}D?)b?NNW$|#iHZ6iNINm5D^M$e792jlQb>#6ii~T+4qz9 zY+!cXN+^60B5(QxYdK6!ovs!p!bn!>-7bgbo4}{zx6Ro|+TdXf)iwC%7X_TDRoPMa zK6re1FJ0woJoZl?DaYG>yxbw46n7u#k}0=%YLbdavC_(j+EX#eb8hX7>(RAh)dG@l|`zTbnUU2+BJI|3?FkMdmSBGo<{ zM7n~yFrN)*qZ*fM^8De#&63g3aR_SJUQ2ZjC${xRQN=DRu_6Hy0nnE_i!tlQtGLGkW1Os||Sqf{;!3|)}F8bnB~kPdBsYrA~sgkQK&38EM4k*D<*yE!!O z1^{q?@HOq(fsNN!U%pRjjii&j&3njzQ4O=s`iS0az=d;Jd`|*J@z*1EJg{=Ggt*dR zG9UrynAwveqqLZgu)f8Il=y?0+Ve;R8BT_W7t>6GgI2nhB~J1VCz%2Gh%lO}`_8cFT;WxnftSXU<=PwH*V+BN2<92ORK3V5 z0Q>pucWCII`wGYpvYHegxz#wIPay!a-(qmm29>{_EO>h9+5ta6PoIDTC^}-ObTu-5x@2&;{E#kW;rZz zD{He}uw}r6&{bBm6pf}f5e*~tZ2vmn=l_a8?eLhEv$xK9X@$R(*K7L*7^R@wb4F)7 zzcV{aTEgDbcBh95H)k`d{6=G+#Rw4bjBi6;Fz*ZOD z2G*Qp=M>RfzeP!#vIRR!f=Jt}L$_T0O(G`>F4m3qG@<)4%N{<77-y}th`QA{W%{L! zmI(mp%mf!p$R_`8vmf4YQProN5;*c`^R=01(q-IIDB_AA=>_lujK`ryj8PYQNneq{ zdCn>Pd~B&7Nj{XF&^*Q{H>N9_$1}{bo?l)cUrD5@XX{&%-2!&T^5CP%a zR=%^d-dRc}Q0OM{|r&i7p(@r9_rtBtd$-rPsG~gSmwfyl3x>W2Sl}mDUPWoWuAFzXBixwk{gKhY+@__nghAeb;kZEAjkWNy?kUQIAyhuXUYc4OEwaE3^|Lb zoKn_cORquSaORp{LSxs5AZzyew)U{&*`tSH8mL-uJ6(Apx(GJ(5J&nFSEM*R9f^&p z2v?iN%`(cv%7n801~t8?Vh&c_d$kQrOiwHh&(uvE)Vm(p0QxD{BK(G(wk8UvTkHG0 z=ib!hVaqifMGu5*q}>lli*2MtEp^0Bt4bY2+Nwm)j!GNbm5Bh39f$#zm9s3!<`|^* z#AEQpn=C%QSg|D}_Wq&9us=iBcKSaX6GauaKjUd?GV`)R1|IcOfGYu)m1sI12&3ey4w@wTeMnCX9#@ClhQ7du&I9`4tGO6&bdoUoW! z!^YfjlR+ZOya9is=)qJ?baa~4r7z!zO@Jihk4o_|Ev9-r*;`pd>h`Oj(htEQ>UiF~ zr!G+@;G=ZR{Ag$y1$XYl(WQQ_q8!<&BI~!Y$vYZ1v8vmgaqPs5a>wwzywY8}6AnV% zhOCywej%S{7zRw_Rh#yRtA?rq z#eMA70jDwIb*>&iM%E!U4`(kT6-#x0nNdSUQ9iuNXeRADD45W2RTl4Qm)0%3PDv+7 z>*VC7pM!R0vLT zRir*}TR75OvA_zB;S4s;^|w1Q&DMqb6yVOEwwDfQn#%Y;FpQzs`42o#81j;29aFDr_ENlKmcb00Svu)E&E-WljqMEL7{WXJS)H(UVz21n`0a0Tf=sh^~Z&3#BB&XQ?o4yzNVsK+lp<^OfNzE-v-OtP`d^P_jV zZ_?JBcIh4Tiv3d`sG*ppdFs36N0QupF$L-V0b}hKX9uw;!tl!_1z9A$KJ4uwsqI&0 zud5Tt@UePtAR#*afYab@t`6lfvyY+SKY#TbL^KG2R3kO{SnC2B4j2CC%YjPdLY2ZB zV-|fBcvyva!l$(iE}mHlFFY@TxSuXp0H_$r&`p5k+{N_|?hfF|(72TU`h?Nhixl&F z_Q@?1irX%?f2?6oM1FfbH__T$9my)K&vN_QTmU2WpYrM-tOqbTKv$`UrqiA8V)7Wj zD;0n`w7PN{=~v;99~;AdZ0vg1PWlluN|4{Wnp5f`2b7wCkK+}SyJtNbj=DbxnshlX z>^88w0Y>eyc&c&o*pbTtc4Gn_QOO^z%1v2)q-$o20JO3LU-uaDeyZtKUv8x@I(r=!=k zEps>%E-kO9{ppbg>iX%tUl<-&@_w;?M6z|lWMl^WTUhuryISZi-4is|?8!$gA_AS6 zlVds_WWg3<9D#7+*XeWGV(q|7on#0VOk*)_(8#Ml{i|NRF3hZH4bn74{|UA!-BfYO z_mR1G@yRPzA@7(quR%7X7$l@1FkRYfKm7DB`d5`dAV03rvU&F~J3HG^t9<_X&r^Ys zPwQ=m{APGqd$o0;QHS5>U)zg#4`5D%0e&?M&CfYy%z)`xDBHb8v-o-kcZaL-ke7@m zw!ZUMM$K**tzh<3##}fxN?vPF@%VyL_J@wktPc#c-&o|bA)NZv!C3=JUv-$5Kw-IZ zKQz`mr0nWRk*wUcaBbGA%3TKv!z!H>V>=(&77L-8#W$?S9k#4J)$uEIh4_-NP1~UN zUX2=mHe40ms4Xi4k*vG}B-vLwaKh`zaAeG0JEBI1`Icyz(6jDBJuZCjH|cGjBbmQ* z>K(e~13ye#6VwN36tGNWW=_2zIwaMk)$<@`%(n*b6DU-o;n>;Ji3V)NhFp=?9AfCs zz`?2cBIJ+tQ9G)=`fQBs^4Tzge2=e7@DY?$BD%M$M|JmU-1@o?^%?dD9;+B1f{w4q zp|3Agj5Ie)Eq~@SHGRfqZf5vilHksb$+%Zlg2$;&s#`TwE>+EB*r~+HMK2fYRqgotKMd-Aj%@$aW2$Z%jl)#ys=c*B@_U zB7~OIJmVYIAlUZ{k2~v66zQJ+N!T^Z?`tWpLYCs#x%Oqib*M4J?g1nRr=1V>Ts;p=gFZupK z$2aX5`vUJP%yG|ZDKK7Jal3s%Lj`Tj(;LU9TMO$+MA9V2a9CgkM;g({e-j1?LPoDHew03g<2_HH41Uv+q47 zX&1ciwv!RA^a!>tau@g^(a-aPVVnW$bx4};`^E{h4Cb2enCcPl50%bZYsb4hMBbgp zv=vsy4^;GbBpq6TH_121DN!#Si!+GAwf|W^Q#z_>5`|OL^E2^lG4s#r?%Pbqp#9n% zX--j`Qyz{+$@G%q*|-EpS~rUS_G4^lf5usYT&|m@Yo5xW-whf{r;@iF0p=ZuF z+{9#)o($|WGoEBhC(sUjL=(Dcj+|j@mgMZq8ih}^^gf;71}!^wHN(;*l!q_cz~P#C zi3DF7N@t25xL+XTludkO5Q_&kVS~TYf;+v(P~}Y+t!2OPt?qO4%P;!k?dTSdFg#0#NaFMad$-LkAG`b1r7kbDD)#UupA9#1rSr<^?KDo)`A zN3>UG|X6OZ?XltKQ@NaTv|4#=P(x^$YG8k+KeXhjvjBMUMQcq zwTj9+@b$MOyN8WvVHFfUkC@=x>&tZ;4MTJE>CB@LSK+% zL%w|CWN-kfD@m1#Wr#*(z=iO>{+7W87rKM*eFz?CfC#|%yA{I$+6u!EO z$w1Zg`IQi^Oa{HAsbVHx^3=)oW`xfrA-2f@?Y_Q~YIfv4(Le{3sRS9!gk1^@ofkA- zID{kH(U~p<0qFaaM3lf=$>zRpbP@!Z_3KvPH|>wZseN1hx|em<-)WjSBI=FE`2Y!t zO6uyYzBAo*XQ(`asR#)%L3#Sr-3?;Xc9XHa$3GQ;qMg5H**TcKD3qnY@W?6uKrWQ87qaZGvENiQh?9|3aucwlg3nf-qgp zJv3J`XOv#ad|O%zp~VF$5?UJ3>#`d@)VKt+M+er?)@hRK2S~i}at~u-aq+-DzLlqq zr#HhlaLfHlN+-Hra4?0rr+Ugkf#??LlF`aMRpsuBvt}2LU`Qo3em$&Hw3;V&aXEZ$ zvdtu9BjHFO2U|iHvTHvLG>Ya;I)x=OMR^V%XO-3yi=PhJ2%wR)wAf0vI7dHJsND7T z%d3Ay%dC{JO_9t_i6Fj=$id5OmQ`}j(Vw#IJU4Q9yhmVR>q-9z8RgBvdM4hK3+_mM zk~1Qo?`-8V{Wfo_s_@<8>Ga6)ZCZ$ZDS!60Waoh7j2Yw;1I69|nL6E&$=$ow&OdL+ z!m45C#qx7ZhQTdSoRr2Uf^Ew_r4kXp$fRtLp0t5sA%sSCXpuvetH#$FMebcNUfplJ zUG)0uyj}7BRj$?qZlQt|e8*&IcTazgUAO*0V}{>!QjD$BDPs5jsWxQu#s;V1z*cBz zs@de5udddH36;LC;f!oW%OPYI-Z6zSM>qE(O`v@*lk0sO2u|-Q`>du*9w%UKlD;*v z^^jfuBKO#L0^k1D57P`|!^pa_^bF5@E-BCBySLZ@FD^LX<#6gB5Yymld@e{67J=4EM^jGocBR>E71X7sk=`^lPL?wTF2k(^J04t zC%x9e>qiHkD!!W>$kO3wdn^|g;DJ#SVDbr#?Lgmzxw+%2ZcYC(z$9J<|^D@>r za8Ybw$J6fz30Wh$LAC_D*8XM$t+GRDTH&#Zw-db>UJ7@iz!SWX**p5!X)vx`C{n zC^=yC0b)BVnd9}=09JLU`%Xl*86-b*5}3tnD5zidnCa`vBV6tf&CD?8K}2oJ(81@S z!+{&InFbGP)L<5qLlbDMLIk0z2W?K?>`0$-0G?e_$nC*!;HrV4)&Vj1bDD~Aw|~3H zv|3wU{gRh$<^Q1A13d&k#YT82?@o1M22lwGS&`MZ8ZSC^dMk$rkYy`5v)?RLjENTc zDrOABhHvbkDnG~J>FkaGm57?sZ&Ukn?3{FRZ5}Z3AqG1L)(%so4J&Eml@yCzq;tvw znwm-tx1j~kHL`0ho_sK56-XMTWleD0p$8Ffya={cytg7>rD7#6u4$M?oP}=*CCeb# zN4z+%9Z}ibz0oDOdWNotq7*Qb`aW@_eUnD2$(9o`S81=5yscn8hEOtP;Mjte zh&d2cu^xvXVrTYc8BW0mC0J6BMhGUSJo~DdZwsL+Oc?eIb+g!f<$;$2hV8S-bDTLf zRCKeDS?be;o+a-ntGz zAD7^w@kgigPVcD(sQb~CV$Tk)z|9&Xm0Zxma5wAhW_Wy#l}E(8lW(8{ZTAyR_S;8d z9127|<2^@c=LEOj#?jb=nKHZNiI?=Z8U~0`7-vJ)2mh&|@&Y%;$yW0~khA+lk`2Te zVWy0|g;qkB!+5e5>^zM5?a5FLZ4ExBBQtyLl@eM!9d;34OU{QXaT+!; zz6SIs-1Ou;j=#x7#{{2K)KT#dG};oXo{Z9bNh{7Qi#_8!U#JpN`=lXvPT%*;Et0R~ z?2cAyeI6`z@=ci?lVQW=2O&wQojQGmie8vF@`NJeI&+`$bO;ijdbE?ZllJ5!%*muq zAdas!rU0S)hEs2^3gWk_IlHeiC=waydAq&HV@`&D)Au48lQ0>0g zC8Dp(#ynP0MP>J8D=gHL`vTeBlkX(ilC*IQJ*s0%Ko-5&qpl>EXlny9Iq``k3*;t^ z*$D^r39{&r-#U%_nenycE=6xN8`sV1nH_6kY7F%b0RP&PfNtr}*$Dg;bmq1JJ!ytv5Ta;(MU#%ZPfM=kC4KA>fFfuZ!YP^Q z|NI2!T9vn(AgXE*h|99tWgCJ&=B1-Q`1dks|ch0OF{_{IdiuC=9 zTgO3**i}fZ6R~XK%h(s%udwLI7|Hn*DyFBP@0qLD+jqKsVN(@H1~MYZpk67hom6=} zuJUf&ZZfw1&fvsrz1OyxpO0{phG>U|ZOg|e_H|nNal91+$~V>IHInKke&cP?|IOAg za$-PvfR?kTY}jzJmowvg8dA%fAnfn+Rj`SSAfRYdw{tL|_=Gy=0fr9^Cp?(NgYr_X zIn3;L_0|33aN_7d=NmPbJ;s0b1wCO1s@vl=k|*9`)Mni>8emPS%ylBg_13U$RYr zV|2-wcbNx1HP4P?%vS~uF36X$vO&PU6FhTT2L{vK0vhSr$TdE=4^_9b^ zQr~g6tbRMZuQF`qqYEJ}ggLv(5G&7SRVn%Aa4GzNX_VhF+iNxXu4C!P#ck2f9e@SU zuk5Q)eF5M19=>|Ja){kCQ|Xtt?aSww@%ZYD&>5Gh%)KTt@o+QZSAwk* z@8FaTFXHRv___)UN`0u&C<+(cU=(lGBK<34_GXSjuE)HBH4y6AA;4ak$*ehz8?Aj( zzK5u*bChyU&nk7&#&hfWl-t_+N>=sTpfMfeyue+2jfr%k;SJ~`&8t5e_RAXp0bq9Z zs#6Z4QHlF;7#euqQtG~OTJPfsY8rz{tKS7xUVDdEefY5TC)kIAH zD6}fE)4e=y10Y(96rR&RDmuVemAo2hESxXFI?b4V^SJk8f_R&|&&7=%DD1#cpu*>R z>{5`zSM2$V{&+Mhdpu&H=lnqf9342{+zon|l)pweWA^hM|Jt(Y!Adbskistv`SzFU zdq2$~CX@d2?RF`AV=c>7^E05WKEAsVe4(ZB59>TNtp)06diDe*-`dXqq~Aa`B?>2R zT|V}zFnY9;s8RaemzbrMc7$Z#{1cf5xV7k(Er<6fui+Lsb!Zm<(&2}i?$>h&r}h)= z+U4f~b~(OF;Ry5f5Ah`uZFrTt98*l^?4G)pz6v@djTHMai0-(IaH`?EvCu+~k+>8( z$3yR%^mGYcYnAe*Xg`GXmL25ra?@x(f;R(sww8@Ztu&4I3;#CZ*uT8v4&{oB;w*36 z&Ms7@BhS6cx1Uv0g#9p7t`Gtsikv4G11hym0q5UAsW;p$xpp?!5k>FhgYsSz$H(X9@d)VX9EONOUMR zL}j|^@O*o*o6Rljzy6#2MZcIe$l0D-0xsv>zS7&N zmChUdWMYKpiKsW_JSF{QH@cZo7dQc}?&^d}!ZaPKcy+wT-4p)HrbQ}EqT=ym#5OBo zppCXZt0Z3rIa{N1^i$oiR6Kf_>+$(SMa5|IkZV5C{2=^;5+Ys#+ zQEGT>EHiu6rc%)Eu~1sHKsQB%Cv-Iw^1gA@v~mz(VR&CSU{+2S4qULy33M0TP6Y(# z!g@-)y1ol)n*h1J;db?|72bhp-7xi|A3f+S;a0--%VQ(9T1V@%SK`y=e2W~yDHHt@ z(O>&VF)9qae2%%{{h1ksGP;mre)r3!gtH2dR{vPFuqQw0PYH?m)W0UO4Em7(B*ZF3#qb?IXq#VO6s(E11}VZ4yKmH9Js|xzDmnK$o+sS=gNAfl zg&ZyAVgs;0yvJTg3h%9QHk`UfUh}%Et806dR=G2{A|}9-5^VFs z5%RY|Q)Zq26>V1*uvhkc*nr&R_>uv_FFbqjLx~%wwe^R#L04p~TGZ6ONIb*;Jq1k_N*Sgb<|jN$I6{CYE?+tGra zTORqs_%F*GY_Xr4bC5Mhm4E7|IJLrx?F-ONYpN6s#3UrwVMDY>+ePh?o8cFJIAv|D z{blWvty1`?GuryWKUO<2yBLDkl|NbKfS5>b`0&|R5=AUU1ZVBHe!W_&-}P2hl(*nu zmE!;B6QKYo(}7(VJ1ngRl6n#f;xn(PnC5_d;Wt z%k#IN8Eaj4-#VqywWnxh`Eq}?G)qpVkFJNqlvl!X(pQHV>Y5|71k8A8mX-~}kXByv zr6!ZzlKXPVYTArE(2;?%OXi&Vc2uiJSrm|A+|Z|g+?_ZW`?X(XEmk)k<1w7+Q^V-s zzP5q*#gC3IZ@)^npNqZ2?8)f++8)@=>!SH%B6eshn*3$2miMa2(yN0{#|HUB+a<*_ojn|84=VB-h@$k8qd~3W-92;wdvWCjH$PB<*Yj9(U#s~ zA&+6>IWG)42%vM?RZij46Kua$O5+T)Wc*|}^<8%Fkj{*+ie=6RM*Vp-8xHZv9}Qv# zRR-K7{aHBPLG+&>Ui;|S|8I|er4Su!&Mk04O|^5RMtTNPial<9Q;z7pu3DCrGa^+0 zDD0?;!VuEgdt^N2xfH(Ts-^1PFA*6JZfRSr-jFF-T3M0FEq^eTA#d}IvN$h*k{{>^$JIrQWzD9m#-efI7U8AhfNs1u;}1Ay>Jq>o@z`=(+o8fdTp~ zxj(JfB5M-Z)xULge7AgT@1xzAap~@D(le*L^V$2P)ZUkLxz}xlYIIlkIHG%9C9>Hu+f&DrPK^W-=l+vl{Q01Gu!>OO) zXg#RJ=Bwtr$C7@z%n)=cSMluw%#7BQsY5e(;Qw>g!Xm3ky&?~$(jaXj>sObSpip0`F8sSS% z$w7<&r;zf!NepNfX3cLor)_mNE)ad_wG9kkzLSV4=Xyr7XqO>)I$=tAOLXko8>-T7 z49YbrJU$}F<5_|0F=&hQA>BcSap8q+ZWr<;Du){#vep{jm#nTI_t1nYZ!u@t`d@xc z9C=IF7{+?6;xypewoU^AwxAsK<`YlVkTs#Ux&fuuXhEcJYtu~7q3K7-59VJCzxcZ? zHUow^^ENNMp+4P&ceK8bweo0PnuPDX-kG8z#=0g-y2dwp3o6IX2b91gRr)uJ zeVo4DWveqoRw7Cyd-rmcjr;3X()jkvYWLry?WbWxEop55*1*ki4z%-OmL(|{Pb4zx znce1&>-Ch@%4%UN2h0L7X4ukmlY3coTH;XNG(U>cX+gUZ$w07;uoIot4tq3+cLpv5 z#R9_lX%92`yr*Eal$@BHzEu7bAw%P6{hgj%1DY9=Sw!z(D1g{ZvSknv z8}G8{$m5^BHDRtqv!J< zT{kdf=9Lmdu{I>yeQHCXf>Px`$yeOtN12+{^eENUkzYIbfD$6mT?q>w*r%xx$C!lK z6wzs%*gF*4&@(Rw5Cw)`8g8Q3swYUJ$kN{l-CXzXy>Gj0Q8p!s{|?tCX=Kdrlxr*S z>V+QKB~MMCegzPI!CPwM?S1NvwZC+(LdI?Xv2hAEZ|VbAAsJ7|e#9WR-b}}Yj~{D> z(tcfoykA})@Nt;8dg$@LbJqWYD*XTb6q)kT3bTsVV1sx7?3#%U5!U`C;=*Rs-CrZG zW>qg(M&cEqh!)?e8~ug--p>87sYJ!s9}NlEmj9o5A=v0gkL6OLlNOr*i5EcjeMtji z4hy_)%0-{6m|6TPtUwy6YR_5x;_x>qyxiN6#SdHE!>Q7i@Wl@gbt;FY)M1MsP9)p= zERUC6{P5zew*ImjF(nUPUw1-N!u|)Qc_km+wXhwU@T8jAUfF4SM_B+b6qy%Mzt2`N_oNhl8B_W|ibyZu|vgPnh-F7s!Ri3a>lU z2QCpmRSrvb*BDb};ph2=&sLep&&T2aLcbSV)S*TIL7CB2kKza8{I8ot6-*B6=6umC z9gQ7}?Q41=*mi?0_X=PBK$jdMC^6{Q2|D<4eSu=(3^ zFgL~9ACNty%NePy_l=DB;xSzO$B;6BjxM)yee4$W2P#7i;@VYz{}d3o^}XwTJ{@D5$skRnNXh8bMGdqe`=yb=xUn%sx~Z#QIMoXbe~p!6 zy&4IDn|<3mrME{^Jv^0es*8VO@3XaYj3)hy0dfEKIdxnAezn_~9-eeCd3%s&+JWCFlx>2PYLcA@_FTGu^r=Am23Q2-Qi?+&a=S)X@O+iJB&lzoxh!|Gj- zbn7)p6PdpPbIpqj+WPI?6Mt@nX7jlnzGjuq892~giIf|>FqnKqU` zo26Pp$gnRM)nA>(K{X9xHM$6DGMkeoT-qO~Gzg0h4?=ht} zn&}VmZ~$axi7@H+iOh}B@kg81;RhFk@0n3d1l4ZXa{Bj%b3KQk9C+hKbjTU8r%$bI^GAMM&v&NH?@6nsU*{sbN`CoeDI}d2AEp$_PN`r z0f1zR*CGaE6c$Cal)AJExu%LX>i52OVi)A`j&N$@nxj!0w%r)X44Pf_B#Ot+}T@iarHk5fp_fFmBqt`+3#eL;L4xJV&7E{RK(;$ND zkLW__r{1+6+w)iWzZQr84`8*J3;z8f{<8MB&3|3@Kbj`~-)5tD2;S6)d08mga*cCL z&*i5xPm4#KkXHB_)xWynm%l8g_5bHCSoMF83w=(7^7~CxB9br9o(h79Fe(|c@%7mkZ& zfBBi4{7s*{RvnxplyJUp?{n&+{<~iL;h8Ii5u7(ub`b?x--?=9H;ezPph^dAcm&Vfnu=Ty7x^Yu~BE!8cou5 z>mt(pA%Blx7LhFUZht~tjl61h#?d6PX&JfmW2GTVc!KbWgZerh&u>N(?$qvAw3MFa7s{Sk?Vjq+w;@Sl$^k;ak zvVP?*)*=h39mGdRMEk8o1^e^!%R^= z?__KF1?K!EL9=rllStJUHBx5aT<=N2#Bh(ue!4uMYA8SZI-bKDO1jgOptA^cvjFH6 z$F%}%tp=b-?c#0yT9g$bJEI;J*qq*f=};o#Iwyh}(H-Ky;X(DTaH`WpT@fjyKTvf` zba^47d90rPkSQ}Oi@sLydP8(x9}s?gC7$!{KnDxo#PQLE#(0h3LevcOs{WEc3E(Ap zNYVb3@h=sutPsUb#CB;*UJ**}O-R696yOeT(Te7r4W)4p5DsjH$id@sp(Xs8gT_pb!-@sMvDU*@-0rr*M2gVT_ zu1@JIRywD@n(WxV(j#B#8EaC`7BK&dXG!1uEBf5$%Q7CF#B9BueW3@WU5@rgq&fPc zURv`zsnW<3+TTOjy~`%PAQI!@ZXCVbV9>BFu?MwuRF%8ac+rv2XT zc3P{hOHdbKjC#rQ_I&4fh-F z(t{MQbU3y3?0~)SVWs3_yzF%ky1B~a0bIYmo>hpW-EJj!1E@0lb9yg1LKS*%Cx|5F zRY-$0=V95~ueLmhTr?dLWU!3olnWSyPcuJqMtZAU;=JeG%^l@4pH<~hm|Reb{u4EH z5CjjEvM+U$>h9PV%zt;9^jjI4Bl_svybj&x?bbLVhZjqD=06#@Mhc&u{H2n=twy|= zPu4A&CVtH8@2=~zxQlm(;7O8fEPCcE^7SvR;@QTsr z7?TfJ9Sb3K^Ne;$E`5|-b}iY=Evlp=>5JTv>aK=en>yZRbnK>MOzo^izc(ERg$tAm z#9Z#!SEes-sEbD*n|tlc`;oHh-z;|=Fm?1}9VtP`Ld3h7;a_h%x0g<|Nfi2sUh;-R zo)?tj&X2(sn#iqytNn+)TUH^BRh2-rf7s8y(P3AG3+>;4LfTkFU34j?9IW@Bc!ZM1S)+cGfY# zY8ycf^RI${S^?h0Ai>j9cUHP(1$99er=fgXT+s2V?g`Bp9C=^qOZ`pb!zq`~M+*sK zlfvTPw?~hqIP|w)cpzMu6#o6Q&m(d(R@?!B1D7-(0_t z*;4EeDDel0d6Xy-+tAz7uf{fNL-g`|-6!rNe|S0)A0MSo&D(Y-o!q5RRH9)!I^JYX zmeD2K1p)dF03ReHq<3Bx9xtHQVA6=)jfw~nEZ?GRc7HqR?8T429*9{0dv2+V2{v~Q zVrAMqJ3az|Ds}00{HmHx5m=w zR}NRCk;GwD1m`5HLBww$O;fR<2TI3_Iv$)}Hn^kcI5-z9SQFMfY%B2sIr&_=a#vxW z+>5=yT;R3UEc}9tH1dv)-t3yNjUy=WCCj%S43a;)3i;-*E#0lY|L3CrV}k!tFQ7_| zcasrf|H~V~VagLne&b{k%JpG1TYp=>zK_56J&G-X2vIbhTaE7T@XF{04Zk$i|-!hI{^Fc= z(z6Sd&YULP1sW_mwxn#Vr!qpae-2}qxh9Ut*hYuJo!>a#`{BMKZIWI} zwCEOGo_Nibrzvs{jKYG3)9eIsrtqiW5xmeGrkLLH8*P45+=?^s*EG2O{gVxY){2>K zBYL6;<)!PKtuVds@8vRO5&T;O_&H5yD;P#`er}}6I~x(qa`T%TrWUu7r{1q=moqh_ zW_FW;**2rF!0&(9fhP+fdEhy_`ay%h!R~s1mrL|}nkB!a7 z+8v1D&J(R+2_|$yh67v^=Y%#a5BPI73rpK(hxI_0oojlHW!|tsXZh2)may~-uu1Gm zMnAf_0Ny3aRZet5yA53nre$NtdkjY%O1r=tv>SWq3Jk5m?J#8EF(xoiD;Q6X&TVUe zTEKX_!El0C9_TUCs?ZiNBY7_i2Cn8&a&esnp_2kcUFggme@C|P8|IS-M+=c47^8`d z{dl5TLjI(Xh)JNMN60loFM$YQiz^*Kh-~~O34GpIJxZwCPHd$ZF+|wn3~pkF3%dDS zK3oX%MsYkv7Oam$h^NY(rRg*NSraoOB4ss~=Y9%IdiTv6W|*;QdV2&c6ve`l`IA94 zs_`fNIUn;zQSe;1Yg>F18s2d&2m43+4|chrv#jM>L@s9q+!95m|8W6E*%o6R!G*Op zi%v&yts_ zAc59qCi$J6X!f{3>pF_jBFY@)GHZk*g$Yiwp)*9{ldK3++XUsZ5xI6F*nHfW(T+Bo(fgmeeK0(X(Bu)G0nZRf6xY35-e;D~tXnFfhfwOnc3dY_9lamGW zA7GzC=UCkhp|?09qZt;a)1TKpOcp%g8X#K;C_4*LoQHtRKQJnK^GcYxZj_9=1WWKN z6lM-^nI*YQhKMyv)6$|*`0u!{!IJO|>$vYk*{zgW6$@C6`|D6N2PrTWO<}IH1s}SH zCfQd+G799TW=ay4~w$!M0>E?#YYT%g61Ec+D-5hw=P$84i z5!EL1nBl)%$y)*@IF=f-8tG7ux|=Ehdk8f(*YI5kkCKsBW2aHvi=hV8Ro6a>@XPMbJ01uf ziQXWR^?J(XG*gy6zl~PviAtKU+CGXunZW4ZYKlQ0;DbM|f56pqK9B+TTZwF;RIX-@ z60Jn0qB+y#3{>-wBIFhxE~5gj4-09g9GSVm^m=A6+TZ;e%ejr_E6su@CAhCI5kgx) zZ0^s1cX{rGO_Fb;%S`d`E+Pop{tL`$A(q>w0!&r9ftjm(fn3Jy7P5%CW@ML|bZZ#z z34YW8<8v83MnR%?(I`Ss4B8)7Ob%^^`+|2k_GgIy^m#_Dc;oi#_$ZsELH%EsY zovV}uWs03AN<<8uOfXeX)){rMO)P-7etz15(mx^~)W3quSemuUPgV)JdJ;P4F(Yn_ z{=?r4NBbpQquVV{6dU{n z0y#!t8E$1l1Gyo=xJ=ZCZhHamqJag3rEt$-Q!LPK7EWd`!PZd(=aI@YC|+hCKTWb- z4i<=F<=5Cf5-m@`)(8o14I&|`{V4=vepw@7NM@pjC9J~@HstgE^cPNG>SRP8obs9w zf~$UmcW}Sz_GR*9LPw3;S>1e7(vXkHQ-H~jz;QmE!lbAc^j68+eX2L&+WXC^VQ@yQ zF5(R~c*2ktjEYr|EgK{|$=`IK%^k=tYu;xh|9Pihxp1q0mRdCz=fU9=(03>Bl> zu%RPS02A1==6PN5MstJR^?v+%(f)bER^l7k@?PCMBMc{}3|@442y0lp52dzR9v8YN zc*Yx6Hz9(F0((f)Qyr)$%H&zKCjx_e)6M=9pHz7H2!()ivUY)2gim^NNs^m!z(!!_ z@6Rph{uo_W{lnOb{h_h@-x|{gz#*$UI`fL|5-(1OPIWwmgzB&Vt-6vYiF>!RbUh@` zqQaZUz8*{nY&d+lWNp7t%`QoqKX-q{bM5zK!4^^wCs^QCQ7}0eK1FGlqlIo2iJB1n zp|N&SX@23Fp{Or&St%Gz1B^4q&x4)A7afilDbMQdy*4ySD$6rXH&iK`p+z0`>DM!w z?PV{(tJiy=>o)k=p_wh5cM~Oypl!|;fXf%3st7Z?HOA6n%J?XAW}Ncn>*1Gd@~mdU z<_KLzw|cayas>}Wk1@{~eV;#vvR6pla2oHs$B%oyVW&BB{4tV`?v?WoHQT!S_kTrS z1P^!@R6Yf*gjnS}4AD+R+a^@^iID6mVHyE@`sS)*SN8?UYg=gLI6A?)F0_}%K}(C) z9Fq_0Ixe@+Xo!VbkEziNry6zS?70Ej6w94IcOW-AnAP21BB-jKWjTq=zFl2}5{p%eH(?Gna1@|9XJ!do{feDlBl19-m{vI$)&5ImN|)~H%r;qng&*i#)C zEm@>&NMF)5a8@DAamT{Y&Jp_oSpGx!BuAMLazzE~o_~{nuqCYJng6nf3Z$r=i?c{b z)QoYQ&JCnFM_)p4J@&2v7RXy-fu>>;*w_kugVA)r-ZgI(<}@WP;{B<27Ze32zMrP> z6Ffn2DU6bTuwa6ipG}Y?)6IWsxDnmcBFYBlS6*uV+SK_^yjlA>YNj1(!2r=q3@|s0 zD}&1-ao{qs0%`wH;0}$?HiR1SWpTR~)*t!v`j1>G4cI1o+~H6Dgl^Q8|1!X6u^>38 za=6_@W!DYBiC8YZ{}2<8836{s>0hZ`o+Yz%1qwgWyfbtb5m1+B5$~O^6$@io8JKTvOmK zh5lx2xpgflHgA^-;Kiasf-8r;O%3h9`l&3nzm6(7*hvd&TN;p-2r|Gw=I@_EgzS_( z4hvvn#P*L{YX4+a_)Ixi-?0>IQ%{k-{HV{FAfd@Pj#m&#|ovmCjNxBr?w| zp-bY=hTQXz!cRXDll@uX@sGdXb|A~P?BV_Ad(VZk>#uFLKREIqN@hXteSs6dQ-E6S zCNT8{xAptk?QiNa@sohT9x_GP0=0K8RePt{poSYsq6Z0^#bReIUoyYa{KtcV+T1B? z3=zbw?b5hCqY^x6J!9|FP?Y&V?Dpn&es+5WKT_}C+%{pU*`nlWt$BZQU9tC>ZT-o0 z3+FYJ0{4N?HOv>7RH;{teqEY=r)vd>i52)E*&j9lzq|2whAL`uJ_YsJm^!adpSQ9N zGfQ9U{C%Gr?2;jPadBT%;@(e6(E2)N6DC3Jpa}VO;_Tb>4*{mELsiAumrwZb1C#>$ z4pt6R3FvZQMfasvT&oEeTn6!_=R4`YP(jR>NN~-7Hx+BA%^ic2VauhKF5*g4^bq*A z#BciOLhjC3|5Np{a=T9sK&w2M2x)lR+xSi)0JN zgF$NhN92z2NeM%h!@ZdKGkd|tamAjWD~FYhtGw2Cka^;|N%@H@4lnWki|nyflfOIg z(%Bg!Frl{=6b<6LN`0yD4@>YU4)5>&3yzEaJ$|YRMR?3pu(srJe;|DL690QKk&8fI zQi-0Rvs4$-ZYJ`lwCn0+6fi&l+Qs2Y0c_a1B<&pjVrOFwj_^TM%Ln`w#jg8v;w((8 zhkfe?KVLLfMo3)o&%4iw=2GoI+HLz|A?=#|T(k=lV=?_TfxAisGYK2_OZY5_{li~m zLb)Hrpdp5Awoo%4Na0V5sgm{<3sef0kyEN^;MI4`>m)@e;8E=0-~pAt@IYaril_nx z^y;6)SnriQ{GYhg{C$-X??e4Or&`goHWkfk=MN=faVEC#STRsJ(QfR?g#~H{^>E3H zW%hVD_16u!&7>dX9b9y1c|)>id9nQ6E;O6Y8Gcam8XHysZvM~I6Xd>^UTz3Yus%xgeTc(-$Ahi0DvmI}`i`A>GG&?w`naI`**&ik#Vx&^22>(a-S zgvcDo!P11qL#eJdqP{4t`WM3Y_tso`gX#NSb!QG{@Trpm4TcCc1oYU6er`H0G^mdo z0g=_o!SJUkv+Pq%+`_`br_HFcfLt%Z)BR3)Xddfe4kBcJ>LW9kbkj=oJUPUd|X<)nAQH|eP^HZTMwB#t|; z8=C5m<{O+ucTSWs95_|C-7dli4rQrl84jc*(67>7F{jIaXPY*017Ho$;Y9|3-Wun0$@0KEWcG!|_qpcX1#F>?_L zyq92Lk>03(=yc>*I#~$IBap^WjtwbBmjE3>r*ltwU@oKiD%w%p#)5L5!71m9Fp+Fr zGX*e%gYu|^Oq#fsD!Ddb3~=AUBeuo4gMc)2bkj|4rLcm5c=bSB;YM^iuv*PY23{6W7+k(O=#F39T%Zlj;<#;u zC_yQ(817I8$ktNVjP)lnZ!yIU#Azs}Jpio~1f18smIIRfTQMnB?OLcTC_F903MdI=2tG9VQ4Re2y<=^-FP zVv$YUwjaMYiBt6D#kHC^sM9Dieob8GkE6*n6Wb>4yD}Tt z>{&+t5a4IT4&(uRq-=m|DFf`liD&ftUg`$D?+)-O*gOY2ZU<%%#xooYj7|aqHM!M9 zw3}vvbFqR6@&I4!yP4U|AJo+~Log`&8QetiQX~%mKLT#Xqg)VGP;x#aG`I}}{>ugv+kxRGdvBxN z6vZx*ZUgJs$Bd(O2Tzio0GG%dHKB)^q36bKqsP_&*Fi^vH>GDT!gxo4@UTo97|#Q| z!GV<>OU{7hU`t*xy#j$x0N$?w7@+&)Xun?#b2g*?r0AoBaS&(KmM(I+zAVz8JQTB7g-&Ug{8Ej6;_(}%e`cb=D-a_9dl}Ex3C+Rp zt$ZbKZrCvy;0vl2nI#FX7yP><`}}@PFNoPs1dvE1E|7)%lWxIan1dc-(?G0 z;CoIDoAV{+mLjB$Vv1n)ybooPe{m5c$o&ja)J<)8cL03;jz!tlN}PYS1fuQ!CYV5c zilCA(Xy5;Szbs!!0R2T;1Lk?+vS&*%_QiaFjR3$37{!$X_z&6x@QC%_8|=*#K&yZs z`P*MwB%d$wc&Yj>=LGY?K)t?8ltp~@bO}P#^W(|So}U9viWLz5_)?JL-4lla?Ypdi zsz6vIr5E*nh2)%KSpD>?sGe@*nd*lIt!f$w%kdmYrX) z-slqReMn1&r`~@K7<>z$ACkHRAU_@>4H^NY!ZY$*fS8B1zexS9H>#TYWMBu(!y^a| z@0G0BwN(2@Kgf&XfogGYDLdu0KTCkU4#Kby@KOBT5U<>yglco1&?P~M)Vh*1L# z5uj=p5}Mo39f^wt-yNL)#0vTmVEE>x?sD88AY#LfFVs`ikcrJd4Tt z2r7Lta3NVrL@Zgl*+sIAjpE(;@Av;-?Y(zgljj#cOoz3twba&uqU2kx;$qc-3M5qo zv<}3DvVx+3B1B{cNMh?iK}@X!1R)A4DnewBK!Tz~L_lN&2q6kG14Kp=vfuM0s3i7% z|9k)X{rG&aR=A(%9_POAbDirtCqpk{J0JI4a`sbMyZb+yy|+jrbs=akR3haXdwvmj zTFZ2nX9bYaO@JD#gvV7&ul;`iVJy92iY<1HbgS8v(%-b~-Tl%$x_AY|LP%b4^6S`Y z!m*=F2*8tIX@@U5LZD*npIS%N%;pr*ummDG;Z@-(EUULj1C2Laoa{Ej1-STX8_L8} zC+>=N=N~`_5b@a0)7Z_$PAckG(>5++Njp-13of;(GvTj_1n*!@04<0Q*Vs-P4<0}k9O?SPC;fbiBl z7+t+!>ZHcf}w^9uEI#svhI&Z z@1q%7r`IEI146gYh3mLy3fsiJD| zfS#Y586;9L;#`%379?uRI*ADb_>in$47X2@g&t1&5UQ+VW`~Es(Yr!d@i1=Egr^5? z-uQg>0k6}Px6NLZDnF2`w1I0UYn^KCfhJxoT8CU#?w2eHH6buJfO}PebUk};1qbQU z!H5ex=`wLQrN$40GH%D$O@P#^PWVH9M zY57K}xpaj=#Ge11b*yVXr zSl|7Kr%Fm_`}tzU`?qDyfPRM&qPcl~J!)OxVhWE+K3tchxbBrS`vHceknNB|O^!7V z@@LVr^jQ?E@ZfujcNLed1$Gzb&VE1+vIYm+kP>m?9-k zmNYH8!h>8Q^WDnPI(zdBOxS484n${C?^utr41MG;&-7rP?Sm@Rt9GMGl8?xzm=v2j z5bCe;I)+aWUIKI74Pconc`Mm>5m12u@Dk50gDgmKLGfyT6J)vpHWt|xqTB2`C7}#} zU!oHD-5vldJ%-zODb%aH6eWDyZ{LX`zOeWzrM!nPHiTo=cO6g`r{I^iSF#ui!M282947ee!PLMcLFTTR7IiQcG>A>~&)uGa zChm{3A=Gb6LHEJ4DnVHkYwv^_fwxK%bI?gOEBide+W{%E;V)M4HiU;F_>t)CZO}0! z*%FT7pwYli0L@9|!MV^uE<`TqW-$^FsNN&fEn*~W9D~pY-q-EAJo(Cr18B;Mpen6HF?yrx8Uv!DPjaI;{ zLi_+CA!bJitwM-h6hh}J56uw_&4C%9%AuF8H*5&SAut2{&dodzedtV+C;W{41fG$H zhsZ{kV}qUj^&E5iUb1-g5T8kfYGZV3{(dXm7j$HkYI4*^9H+7&S;_{ zQHK-<;C8+ovdxfe^d`OE|KoZ)SHq|J1*8Z;8diX)(Mvb1A&t2!Ru!2+P&QJ9aqEA% z0MXDjc7Vf0-36A4shtx4`H;T#bynMbyvQL$ndpcm z1mUh=rnZpqr|)w9Q3LUP)0zB&+)uvgA0P~#7_V-&P~Yv3myt@qvHnimwVfW09;=44 zyWgI>8*y(+4{5qbp8Q+p%3uf$D0{F#=&aP{Nu%38@Myy#)!n*PE8v|sezzwn-eTZ# z{`>)N`mK?>J3HLK2qB!@f7iq%*@F5IdhHZjma!YS?%o?dPKjC@JoE8e6BkNkvIR*L ztMV=4ZfN>O#|EUYDBwJ&@FRR7FuhCrZvWN*ue#g0kxIC`;qt1`a&8){x#4(lUKH&D z7~r;#mlHD0;106KaTP+DAVGJXMOw~|G1I{-Zkj(-su13Y%CvO_h1)pnZB-RkYiegs&) zeSR5s!RPN^M`RkQIf-}JGz6GJaS8-?eSns$^e1CRE+JI#SuVhzIBlleyDmwWzCza) z1;n9x2DH4h=Fpxh21ElUqy4vmdy%6B&;Cdl=MP4Gu!|AC3Q4k@7|OZJZ?E{}6BTS~ zq-8>{u4U`;5h=qU8z@oOLS32`w2fQACuVLA?~-@g6p)odf$C&B?IC_t6an; zEzYYqESNh8D=2(B4_U#_+OG+!;UKZBI(wCO{#pNnM$^nYZ}rF>i*vv`Zqf3NZ%&nB z=ti)9#7e$8HlQVRuM;#;O4fd|VC`6*Z|HjgLKt4A$Z4v`NZxi$3k(}4)sT}CELgh( z?rqhmb+4w_QsC@WAw*}b9c-0K3Ra!UW9|TtbF@x?(&BlHj%$|FIYjZ6VQK?#{U`5g z8Bq5dFZ90%AFLRL1>CO<5%KI1hSCurw9pG@j9B2!*_wN}7q7Npm?#jtxX;v@LHD)~ z7My0Tisrq#pm_!cx>({Z5v@v5_aNY_&pWU#+b0}|Em&sN7LM11`0*E06}`L?_dpO_ z{deMHI}IE3RhG5XQL=SJob;2F2zYufYgCWOchT%uz}j9scrG*F$o@&UoE%7;MZOC%Z_ z;Rq+t^3N>D@_h>ycDNx%8lt{LBQ+M#am9x-ma{Vo(J7A9j!aK@BA z)Eat$$} zTG+zk2WqiZoU!KorH@DO?6`s=FhKmr9bCI`R5nuMmCp%w&hubiJb2ebsgP^Y1#K<3}VIodQcS@S{{_Afy@l=A!{b6tWk;q-geUc(f zb+*+l)N`FbEw~Neo!AW78I$RMhSWRqu9dycKp?JrYZx#6ZwL3magkB_THt5EZu&E% z5LoK^-y?$`Q!J1&a5Ws7%RiI#J_B+`62AslUYdKg=gya#20ol z<*QrPc==EVGdrNW8c7yu|6Py>`60MM^b_8_wFJrNbIDkIhe851Ge;rA0ElU9gc%i* zJNloisP01usIE`LxscvHzjR5JFye+X+<%2f!FOFRR-6HuE@X@}+BQ-!Bs3L+AAYw3 z;lPI_DR$%sj+i>hSfL9_X-jy?>~wI(OplAb95a#0Q_`Jvr}(O(5=j-=smZZ%y{sazzjpvn&hNANph za2R2(eNQfnI$8VIMW&>W{CtA-Ldn6I!t^nQ{Q|#1ynaewZNpA$b61J+ICI#U22T zJ{0~gk_&2eUkGZ=lhKk*^>$bwT$K(~x-$TrpNIao9hLUFfw@G0qpp zl!F}Fh@^ls_H60ioI%3V!`T_+7|(|Qv!ASnqPG{)$ZkJd9gY{&F5rVc1m?{`%1g^kc8x;C8O{{%pG{)f=ul1u!Q!}@?4l#g?E4K_qMSczrMl2 zmL9Zrfojd9!6@GDNhz7bLb9`8;WNixxNKHrD_eBmVN;~95=b=l(FjQSuWwC|47~on zG^)WIj*8+gd+isuZu{wEIHB5P>G1g}w@JQLgQ8+Ha1LdBC5VO(@j31Zg*^=(UW5Bx4h(%1T}HI4FHiAt1yWZvV& zAGESQBbpARoA&*`H_4&^p^viVAPfQr2Q;UfX@yzg8$n z2!HGyk!}o)Uq)%%{j9EevieAf&Oc^h-C%(B9|x!wnwu^oE1y{afXl>$2dLbCC@wq^ zKjq?eOL}JjS*q+{gZI^?effJHmUfx|f${O~ZPw)0T5gPUz2kT9XC0U_PW6899zCr! zM<1{~LMr^ACM>nob{&?_1;!Ep;K&}eV${?^(|b(F7R8HzicWEK1u45JPJ8@Q$i*EE zFg!kv3qojcUHUbhFW+jlR6&>2TL;TMqV0Or37<53Ap{eG(p1hvv!?SA-&)8t068dR zLfi!S(5AimEr`Ye6o(_J+f~%4_5dzhoT!Y!FAh`AFfsDf@&0sqtCiMU*$N-bKZv4G zfG7%ebmG8tCOU5w1H>AAgnh`v0Q>MC?GfG)ywBkPmdg7#b=I7~%XoiZBgFe3yT)Cm z)1nRE>PA~16X1@&KJUk)@OD#_R_nF$%P0^sfZQ9xH#whN0CoK3Zto}8QMH*~CTW`Q zAtb#9mWLOEFuyt4zW?3cAx5YjU>^lNU)_7C;XF}ndl}!ejS5PQuP8DexL$c$=)H91 zGw#e7_tB<-O6im((En&H$FuF(FLomz1%EfS!Mmx^2cZHY>Vx*4ntL~`4S2`451ym9 zwoHC&<+xA*)FLYIsy%%IA@KF!_k!F6f(zc-GViUGYUK^ue_*mIcoC_3d-L9XduBx{GQ0{tXOAlQ5TR-PF(HGQkztR$WvG)v?VY#sY3fEO6%!GL z(7IilHFs_Cg8bGvwN_!@588Wfs+20_0$~P;Nh+misZ8sw`O-cN0G<+7F&yYOwe`uS zQ<2n6II~`HNe@*AzF2e1%^7%>n~N}>?HOzUG%ebDcAHhm`}1(1LYj;!vig#&X>{JH zeCc}Ne2@2U0zRLuwPSDI_rNmNeh@1lW7iaM_~e|TNOk}o4eP*xO)mdZ3odx~eXv=@ zxXofUZ8pUUbPjN2LFN%8AGlA`ve`d&4dvS`fO?$LSoNBlXs-FuSr<_tFV{;Y4a?!a zu+a|LZM%VglL|XlP4~iqq1|3BS5S*<9(}7l`t#N#WhRn3BO={VPrfGFeUK-L4*@Kv zrh7}1KY|@(UqWKkA^ZXzAyqBqyf?^mezAtUpzGc(H z+86MFq>bb%9;hHHRK1&cPp;~(#o!6B>LpzqILV?nYJuduzVVEpQ4tCzZ4!WvcMffSHMY_FYs#L-M;1rkafOfnZ7&_`b7U+9%DP%D1#fxOZNj}e zIfcfRPeyEI(??s0-F^Dzw6nI05nSxBYsQ7a=4?ZDu8$+G^?2FT=2-gZ9a(VojkTA_ zV@kloBM7=(hm=u32TD40{RnMh6L&Wa!qgZyLJsGW}km=vsh#7rljqgZHhm)OtJIeaQ6(QS?1tBeTYg;SJP!{h0zc7gBAX$6Ds_ zAn4Ccd6IpUzCMqVekxfI+z1qMwCKP&(cxmutaNmqN2V1t4fdM`1uP)VpZF8u0H9$} zvJx<(Vm2Z0nm`q10=dCc#fE&ea6vgoQ4D(Y2Y~yWVOj?y&ZJ(hr%2JqsL3O>TU$ew zfc+uTOwt_beW-guN(>_ON5U)-!(0jd{F)+kAdkkVTkb@FocRs)8wUW!XEru41I~5- zoQzKDc*yI>9iWm~^bB<4d58yu-*QIrB8nXWb+Z31k~G;q%>%^A{s+%u<^{PBj`?^b zSky>**4IXtH`w7?w#s!$V`b&}C@YJwkaKNwt-@d)u#1F8iXf5#)6~R-btNz$oML_h zI`&Hl$Q0HJuk+xS!jh1R7!X3BJ)I9UbLx2#jqz0#RMALJBUg3=(**u<=+6m|m2^5m z{RU*wlOUJ?s{Z4(eI4>SeD+WL%1}%V4V~uYz7j~D2CpOnyMkHRP3<=$$58x zxJ!!3*`CX_CNTfPWe1hXFqP9FmLgAu0qVjF_!HH@8()`mFRelJKP>n~8{o3T~$sRiN)gId?|P^x}u04=;PYCi|`?cn-Qj2(?2* z*!#MFaMhXl$vHJXKen>x-j0<|4L5;a+;H=PpXNDoRV6TwmG9yfLP!FweA)Z)Jzq}q zvwe;SDU)uDf?5+}nqz@g6mj5;{g|zIQA2?htt}Ef2+7-3A*tnB zGSlVk@e7(#jChrowB7mN+Hq0kYB1zmDXm-tMsQ@JD&M<7${N3*cVQH6Fb(rZXqs8; zrsur|mHt2^fTU8jx-&I|vT1FMi(K*5k68Xc+8biReGQ_}@8hv*rJVmnCnKj%?o)%M zgOC?R5b480%}nH$Xr{hzwv;t0A-~RE#=)xDz%>)6(Mg~P0vruo$pUjOc%P4YWP=|H zH>u%tJzh~?fk21h@DMc}sri6TMUe2)WDpz_a%cc8KJURgLx!{pQX^$6QdMr+zPS^f z%=8n=b+SA5-M4xmssPyKf0KwJ&1z+R{Zh6F)RG9WQ^jMVZ6+YAqdfNC)z_-W7PPw-P~9|)-$g4ln)WQ1$x-gj8i_?cjVm-5{{R%KIpMQb31SFGkUReNclrN#stRjzwK!~iY`Ng)74zI%+L zS$sgRI}<|+Kp_1rzM4=U(lCMtavzpH5g2=2NH>HTxUW3}rZ1v>e8ehC9%C_FRIs*v zB0%(>@zIizN*)05mTH^9h2ilWUJ69O5kl*;2dJIK4xgYeHbrY&p+l0aKNr7P@`gtQ z)eN!*zfD|&r9vuFvjbo~L4X2Se*3BR94>}v4hf7vK_))>?+1)aVhaTUh!G5F8$qXd zmmz}dkzyO_op^a7T1abTRHKVQRDh;+*8*C&qP+68R@F&Rpe2d z65RtQ1zNwFXAwMo#k|f(fzv+$(}@)?d%o8b^uGG`&orng6D~Xibk<*wlvVs%foIlgQ)cFvRI=Z%iK z*jPVZouk`wLj>oO1g8Y-`{J(xPH z6|UJP^?Wbcf$HFppgln*;i*aUjtZyTS$3wXSy2|AJ|xb~o2S$08xcH-UsdE0jFqwn z366AJ8e#AL1rZmf+wQ+(`h9EUDTX*#daIK%nQJ-O6Z39)CHun6w3LHObVi?avuu)F z2Ua|AJ)VR~YuV7p4aU8qasM4`|HWKq?o5VA;y6+8ls$C`GW>zSYDTb5=>eabjAzd}D_)OmIxziYk+HIW>1m4I)alAAmb#b6`+M|= zzxZLYx1p=$MdBY>1wWLE^DwlQ?j4e|vI6rFUv`qK&W(u816!rl@Yx2;<;NzUtgWCxD6=_t7DMb7p7%v*&!|0TbXc_c*+Ku^ zP5ty>cV#0?hn~y3=AbtvdkmLe$cqvMbQY|?O< zE@mYL>X$y6p?+uRCe=?jjDkMl<#445;|FK(1>x&tNslMKn zq<;7SwPMk9+ht+H@6Mk)cWyWG(J(rHO#MD(hD~(g{Q2{#)J7#gYaJPJ2oT`0(@XHe{g33E9pB5CSVa?v3S@Y*0rUXfK_3=F*9vK(GrnF) ziDV+T)lv!X>g30#rMXLu2U6FkZ1BDk%)J+5NcEFeGr~C$`o7AppbRECdgrySj(m%iBPrPVTu!qIU$K_VUrmHsnurZ)6|vXf1;Ob@}ahwa=> z_I&@t<$^V4FJ{&m62ADKQTwP(G^@_=U#G*5R}VOPEx(qY!$DzY?h4z_cBSAFSiJ?? zoyKtN?NQ%tp2mOUGIivNFohd}ox65Tn?IMs3#16wd3IYIR34K)iLmh%oJnk#*l*^i zA0W29q}COX8u^EkvZ(fQ-jr4M0}a?_^ze~&JmqUEH{n7CrHg62Xvv)1?j|w@XNbc2 zD_pNhVsY#x)T%Z1)|Yrf-<6Rb!@OddmAP;Mrt6g+(<6mpZJcSOA1=u~KopYP3UGB# z+(o(VZdJG=$oP(q08UW=SYiIn$Q9X2^rh` z`wL2L(3+c~h5r^-xy2GHy8PFK9I~WyNImSz{0@I{#98^;ERr2())8z=Or1-oNYKK& zLmZV3cCUF+X?_GVOfbnb`qLa+LC6c^dJbF@If;uX)X|p1EU$vqmxjW&i8h6|-KHE# zXyy&p^cF;-FEE4U_HMDPv3niegcfck?TVbH_Tk5m$nZ6+s>`}b<}3>F8cE*44S~u* z;{IqEZ++h^+e#Mg(3!u3!;9{X!B=OlWqq67vbRuvJ1nshZgYqjG5FZ0!F(#~L|V6iyV%OgJK7Eby`LA&j7qsnouFhjm8>enNrZr1O- znm=zX?6;3U*q`lVPKz9&_g}7Qj%yg9dg6`CM+^j3E4R=|tEtOGL3#YRcTT6-sX5(= zT<(Fti7tWcI1k~H%Lc!2V)rLJ&Ry4iWB=>$Ut7@j!Hmh>6ar@YehcEYt9;fLmB;?U zBu*77L~k$O!kU}cl(+X}zhxLnk_#2?;<{wt)iBgagU1~#mz)e_dvt6dI&PhPHF{`3 z{O^V>PNCOl9ccg3p;1wtL%22Y$n;grVLj>WXQvzHnmINTo0~*ySGF}r-0A53dAe=% z&~4wBUCRRhH2bZDIO20%#NhK5b8s!@1vBwrw7B4b*F1d}9zjApo7Xy}cTD#NV;~>| zyIhE)_-+5WImn=UmG`p5gl5a!-ix!T0qanQDJ{M{d4JZ*yIYPW?@x@%Z&5;?* zyL{0i{?t1sFXp_21!PpY`E06(sYxU?;NaUaeBQ;OV$~TnzQ(jTUHVZ!7_=dZ@20d1 zsFUa0FfNEpvhJHYP5+s8I=a20UFi_rWnGl)gIl`&*y0O=Q`X~aJ)Lea+z$kj246pM znQP(5A*L7i6s%T{H7nE9shU->S?cVP=ISX&y-uWMQ)pTII&{a-GnX0(#SdF-JVNZM zm{M@veYy0v0fRLpcDKj(DIAx;iv=cs=e;hxUbQP>Fiz(7l9`w!I*!R+9@Be}eOA0Y zKYig5bGlufzg@sTF64Lo(${apmot*GfAH{$kse~1#l?z+ZJA7Fg_2kptuf4aT@q`S zHAU$aFoMVaR-$;ns4ah!!2W`5^l@xJl2XU*m@=Cc8e)mapvD*+OU` zbo;)Wlwii$AbBIK`s<8vV{k;ichd8yudRPQ;BDf1+hkV17ePPDAcgHXIcoZPwpi%4 zx5sJ8qGJ`03+Hq#Nu<#?nN6R&x7
      Oxh;y#JDM}6cv%AoMIvKY#O3A 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 Uj0g1#p8%J}O&GN7hFR!;0I7}kw*UYD literal 0 HcmV?d00001 diff --git a/images/transactions.PNG b/images/transactions.PNG new file mode 100644 index 0000000000000000000000000000000000000000..a8e1943c45d27817d1beb1ba5dd9ca0d98af4b2e GIT binary patch literal 16348 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 Q$iJkntfPcfco_2k05_hYk^lez literal 0 HcmV?d00001 From d06ecdef20b9d9423602b00ff07b5196e281e8fa Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sat, 18 Aug 2018 11:15:53 +0800 Subject: [PATCH 091/360] Introduce grafana template, update images. --- images/instance.PNG | Bin 0 -> 10371 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/instance.PNG diff --git a/images/instance.PNG b/images/instance.PNG new file mode 100644 index 0000000000000000000000000000000000000000..fb7b0e790f03024646ed102e19db345889891825 GIT binary patch literal 10371 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 EZz0budjJ3c literal 0 HcmV?d00001 From 59169876eb7a229017c26b381a3cd7bdd0bbc389 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sat, 18 Aug 2018 12:21:24 +0800 Subject: [PATCH 092/360] Update images. --- images/delay.PNG | Bin 13534 -> 11875 bytes images/rows.PNG | Bin 21268 -> 18123 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/delay.PNG b/images/delay.PNG index 8da0ef1974b70a7cf6869c10b6b86248ef2c52be..119756807b4a5b55192ffbd4c1b94e12c142e784 100644 GIT binary patch literal 11875 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 CqIvTG diff --git a/images/rows.PNG b/images/rows.PNG index 07246c590593a43512e3bd13e928569537a011c6..03d00727aec0e56a18fa5da38d2237daf2a02894 100644 GIT binary patch literal 18123 zcmd_SWmwc(_%^CTcXxMpcMaVoIWT}oDBaBf!qD9c5+WedN=YakN{3QPH_{E~hkI}L z{-1N+bDdA`m-oY5T+FOlYyFE*M9+htDfygJ_=8vI-GZOpPMLc+` zil}s5j~0?E-O(yI4==ad+gPM;iu2L~4Xy+ZZUf#2UY$$RMI<3ES>9ei4l>)XWj5J- z7*9hM{MNO{oIjw)9nwc6bs^(l9p&HALy+vcpLekG0OS0)yDY43#3Q}$AB-mgD*A{@ z7qQXx_ovR~oYgBU-qvHqY}OZttMn29u#E*N!@$!`lAii>7|RSLdxA|d=4odn zmYy{NN^$J)_LNA;JJvK3U_wv>6X-j$_aypnGzd5zRp7>wtiu<|Zon^x+XPGAq5f{8 zV=RWOYiX5@9v4uRUD>njdwt$+mDq)K#6sl^@i8j^u&Ir3AWZyD34-Ct57_evfi1g zQqfJ1@MOqu-8?Ai2)_^mC z5`_He8@-j4m00D(e0YBFts*9c@K{y?u1>NC$JoT>4~B!2^XR2%%c@uoqhR9Bs6_nz z+Y^_bdd5QoKTJ;iI$Q!>3(F8G)w!ks?TV&3fSBmsXZ;S-x#WiePkgFG9o zTLsG!dRn%) zzKWCtC~&`J!wV?S33zU$)SWjUi{Kr{?9+ao@O_l!$pY z8W$HI<5Ed@6Sw&6r}xepoHb^DtMzKJA8!t7x?YMm#=tDyUi(?pYrQ%!S?IR>q{ox7 z)<6~N3l(E}{if=ZIVVzN*|7pL&WTW7eB9=I_3Vfo1Po6&r8(E~yexm(B&Y_ppBe2a zD(F3I_G(Ll1h-!wzKyHqwrr!{rW5dZ(HOj3LO3~U!vby(M(A2xxYJqaUY1vs*3Os0 zY5bvdNniQasOsWK$5tO&Q*fIZ54QU0?nmH$wh+W8^|a)?daC#KBtKYBc__bKHcc{+ zky^%&IfK)LJ;n3`zQ_8L*ee`rHQfTM?u&@0Sodb9Ns}ZzTI2e)sw?tp$hQ+2wmDjR z^_^}KI0vVOy_IdTEM^1hKZVeXK7z_(IvzChjc`RMsm~)-nl~wxD0YgtEv1FF`OUpv zcBWdTI_iICmuN6r?B-G9e`4D71+cK~blQUGhR?6d?!CQodB#v=ukn&EZIa>xhsS~3 z79SZ;_wOFfee^Rcc@91>Q4A|UEE%}`#_|+y;uW>F%7v$yn?*q_C8I*EyFO`I)gNSD z&#F9efhmOs>He@QvC6+gaC-Fl`MzyY1hTYPQ~beRb!I3i)<#_|z))orgeQASUHJK@yJ9oo`!HtWPeHoP( zXX&v{22$JG4-e&nXQqJ@1y6nR6>c|d2?q{jYyy?|;C_>v@wp9MS$xa3ZEB54Z#G4(^9>2h_7g!OPX+A0;!Rhkr^kA%&m0get@Mw( zn?ayIzYeOroJ9{L31l%3+1pMvz`-M#skI$kMbc}~as~X%YFT;519Emj^Kpy1$woks=^bRVbQeUF~72vWQ{wY=DRop1mGKmHKtm;4Mq@}n%01d?u?aAUE| zfr1rn8g=Mau-qa`Y#(>r1KJE-u&0$O1P}RQGFKO8yz9Qi%6)8d`QgXKkzS44vIu|t zWdgdCOdyINnAMW1ceJRduLff$T*5MNcxOkk(65d$q&WQ3WdT{I?Lx_(1SgG5aA9N6 zdS&}TPa~Wi;VN|h)hR+RF`hnewAb#KW}wfnB+0xtf%Ljqf;y`V+MZ;E+;k*;grA!{ z*7LHYgG!#7*C%L1HTR0Ws?ukL6=+PlzV#qTmP!yxXu9$(2&OB@()h#mzzWk=X^NA~Ve^80gtf>`sWTSY*z(OO z&P8d9eh>wdrXbwjexSKp?Rwtgo$31%3{0%WDXv+?S>fH)`O6%k?Z;;%D@jrz1?FqS zq-~26WhU;6BecZld&d+LE;}4m?`E#QJj!M%Bm{F@1|vYA_Hf=7e5@}k{rW+R9$_<0 zPaB>UCyVUujgH8RvS{U{oWPVw<7*rz4q9C+wkOKgW!CH^C)>)M+k3V1)PBCAHn@OO z_FT|&d#Bp6H(4{%0n7hVT(kT~f z`GPi%Hj=`=@F?uqZ*}Yamln;QP0v=`hTL{^QB+mGetEu?vop@3nKy{b_7w|U!+xpI zcIk(iEN6dt=CM!EbLWb^9C&+sMYG~lapo7qXL~4Ua&}&+2DKPgr|c}xo9#o`JUQAK z%JwL9q0w3^buMJ*9pDf(#3S7+?TsU2;c+ydE2mRt;I$vYb=$jEY%-sJy%Yw{9N8GP z37Qr6xnXzI!KyLE4uh$R{oo7_hr z1f2B(U6N!H7#m);EJIGF5w7c-`0NP4Z4fV7UdmY%vvWPJ_L!y?v(X%0^gMXp+=s`< z)z6bQ{P~sc*qJh9rMY7+9<$kWWHl@jXI^Cky<2$Z(QfS4fSD8dm9p_R zRwlE0HD?N)C%m3+rOsfYFVMR=8=Dwnu9VetT4zVsz{#4=(y}vZ8qNj`yV50lHia%# zv4|pUq84i6eHb>Xxa0}rev6t!D>1n!$LO@^M!l1sd)r>6K2o4o^P9OtcZ*1ZtZ7LD ze=q5#VF5iUup%_g$ag}~fDBfk3l_m4k9TW})JrU%qy5}5&rMDUwzU_W-~^|KC?ZBi zdi5qi4woWv`NWpyE-?6Uc2b-B6wrp|AJF+5#HW8JF_doO7%O-fd@Y(3a5m7xJ*S`5 ziaIp$qj9@9O1rRp5PK<9=T{INUz~`wWeuf^ZHIC3!bqVO^ZB7N;qz;Qc{+B9AA@Eg zX`c|A%SIs2do1Ih7PO2{q}zg0A~-REMyU(cD0_A4aDh(eXSPpDw=`9WubOPuxpf0q zN?)AAYM3bXDbL=SHCz>b?$kSX{2V)=Uoc%TVD`=F*xNDpDi&>C&wivgfr_U$_W0!TNYOqN?`P@tJQgCVcd|_wh#q4nRazG; z6zXWHfToei9i|AqJP`Y}>(h2UK$8#k#WQY1)d;eiT!kNJq516xN5>8D3-k_DHqA;m znO|(KyVvayZhYUKtV=Ymu}*KiUM9MDiAT0O?;{H=sk*GGh$ANIn1VkX=W17~bXL$HRtBD6fgaDHh+Y zc36zRYsJJ6Sc)c2hTas%s}hMIYhj+k8R&M4z|{3)^?rE^!6;iHd2w%BMDF>kufwi3 zP=7?4y^r9Rk51;4Oy6rkib|fs!Fi4Zq^pK}6N-f8y1FD$o1fi^EJN)DX*mRWNgI_B za#B-SickfqaS;#;$UOw{_e|i9IRd9RSiT~b3JFRU@ouj;@aN^Z%<5ByC3Y9@-aW&e zXxjpH-o6^brV=-_CobHQ@06wy^VcXYIZfX$>@-$)JJ9_m_1yL{I9w+_*+2aAU}n|o zyl}AL!#dn#oB>%AM`HT1ZT*Q+q|!m-`5GIsHjHLRIzuK)_-+(UFd~n=AGjVFB9dk%Vr&=;exDC3aN}9NPqkIcxGYH1A2L+JcHbe5%MX!ZvC~! zFUUkNf|N`{N9b)F@u|8ZBCCQFN=d@=h3X~>NUPk`8O7|6N-9V!dq`Y?EH5iT@2u1T zGpRq>jIyWMk{F*)^WJFFFtW9-L%kJ8u~Ow>QH}W;8|xnEhu0A(EL!AFfNyGf-4^(78{QQ z*dSu%3eX2G+_K>TYdd0oRzE*?B1K1%g|9|8Cwd!4pvJ;|cS@E{r zRIOE))$$MmgVlwZzT=R4m*{#<)<*BDe;I% zSBdUQr-`$BvQg=t(ArG44nAe^2WtRM{|oKR>;@X$r%$sgBSo+p?VHt^V*hA(2WwT1 z;<>u*I67OP~Ly>#K3x`sDI`SJopPpAL69Yzf_F^vom-fQS3p8+3oj{D-JQt{a5^Y&&L6pj(DNr(c zQF{8u`s(!M(nYM^!D+){_qg3~_u7})75DVDrEc}5%Xl+-v8Hz+WCP#oeA<04w%!}- z){nKD+ueSA=JpF(bA4hp?hkZ+s=o^gcO~XD=G;vpcGGSW3CP8Kfyp~N}8-&=jpn@Sd0Z2 zwlBVMk&>ouvG>oVvtYEJJkmB|Q#RRVP!kh@ZVyY7f)zm3DSo1}3sTo|K7ES`;xr4|Ldo9llI$3@6v62 z0l@wQtb$61*P{lrk{a{Fn|U|uV_P55r%FSE@tm6%zIwbVuyp#erBHp{`BrebS3BuY zSC@7mvVztq9eL?9|4P(wNgwuAz0a_8NCOL?S@q$bffNiPR5pM8WT<_LGn&>Gj|LG0 zRIF4;6pQvtG?k*BpE}6WErY_Zb@66i({RT|6R4lqXG>iJa=8Y?#A(n$MrBEP{& zUxv0@wCt5p=#r3j+_A@u$=w1RdyE!zeLUWLc=Y1iQTG=!pKBWjyRUg9FU4OYJ|l?Z z*%HFt{qC*e>wmJt#UF_&P{~m9@C4@1_l{w-JF=Pgy=6^@8jP*1k;M%PLW-EIGora5 zI~=j2Te{%qsl(O8Gj=-|4He|dVNoTE#Ltq5$1dd2_|iBEqO3%-YIPFfxBBezL-U-My|ezVRvJ-Rxts zqqQejnN6ce#ASTElKYW?Utfr=FWSAze4T-`Jl}pBf8K#KR-<6||JEfgDPja4>qiF# zE)Gs}?(x0Zkd8ZAkIg|N>x&rr5c1TH96@JpI|6GpsSo;tUUPbA!Waj@Q421!TI5?m zhFKHS%No%kmOP=WG;5I4df6*&Lu~DRDo+O6q-XwFc4w>L7v*58Y>lvmaAIw$T!^v# z`4c)!4&P^g9w*B=p*>gF`BcW^dHZ>V4^|E$a#~WD6v30281^g58DM){J5;-5i6?p* z<7Cs(ABiI?4ZP~u36C$4s^+FFWUux`1|5ATQYlA9dP4I|a2MwFQx@hI0g9z2%nGU= ztw))9j)1g=htBD*13mquG)`?yf=x(TgN;5UJ!AhJVf!--#4sfX`-VcH832Wd>Oi~1 zS$VIZMGiO>7%1p*#wh>k7H|9Cky&}Iss1y9QbjnZ4o5iWw4>SeRR`Sb|AfK#@QmHv z&D?H!xbFRLJd|pT)@gqtcxiN*g>GEXVa}Y8MvU2_&7W&JT{4jK@Cc*(YJU=))kyb^ zmQ;<+x^s>9ZsaQmm7RZ$YE93p!tna34;#zNq0!~8)+?N@PvPCS_2VT^qTdYX#kO8y z!wU-ygKO(Z)bQxA#$-Zt*&n?#?t{%$Fou?$P|klPh7@Mo0=Sd1H7lZ=TVlseCH4W&bSUx1}L#icjWt zj!xYBlkqphh@RU-gxskRIDF*z7#+TQaYSN0x-=KMnv}WlrL+jq=U|EHuGT+)nk2w` zKQ)=MnT}2m?CEIf$z}mK=zpB`3sFB)Mwa)d>qok~+V`QW*fSaDP@%2Q@-ZEgfx+`I8#55*r9fd=3rPu((wQS_8_(0yN$YHY&&PSQr@(ORw{4r(?&fzlg-&s7h3v>0H z^vweWX|Yb10sXhovi+eu+2O>XA%RlcxBBjO2(NU-Nlodtdj1ALlNtO^cbv@sbq29m zURI9n)-WQ_ASu6wHvv81Os8Rj36xTMnM#a^9|9GV31_l~+#YU@a7C~7nK>0DzHb`; zQ5VyJJBkHp?%r%$n+h=J9zg43l9UQ4j){x2>hWR}ekHBLQ{u(gJflNWa>_vrJeOy` z6ssSr|LK;E`My%-BgSUjab2H&wf5k-DNL+MikI4^}Eq)2WNRMcgiJufx#4Sw+Bq6`J^atvL~qG_a= zwt1tasofU2H=`~0e!u#IlX#!q;9QDFVr=rrzk8RDh_rc<*9~EOeNv&{nKuBZ4tMkm z+$1QA3BIA8e-Y)d1rMv%lIo-6WCuCJ=xD|{!Ev_lOzNbe6vdwe{nEnw$u1-CnFP~u zni&ldpTzJ+ll}3ZbS0o%5fQT(WE|eyDFjc3N{c!z=}e-XCid&Q`A?mClv{>+VoiPz zFE$#HkO7xYTBr~dIQTL#B{P$cR}b*oBo0BQ@a(3|XM$WXkO#c1V+oCk*XN8jT9Pme z@_7A>NQ|YVZnPc|eFz3RLPa8$V&!dNoe`Gj{3vQv?=n8rmdQC6JOndq zwNcp;^|xbLmEs-8K^nAt@6;dv+l?9&>1||R`}s39BdV1bSc zdh}yW&Ge9iZ)ug{vVNo70@SZMDE|sxQbXCZ5H&^{St9`5YHpSzFjWIsI)0mg=-!;a zAG-77j)fjQ!lfr()0;Z**8#a6&*Zh`?=+W48SM_Ge42%Sj2SS3^yt_8bfN2&icPc1 zcvBF9iSt5DnLFoD8nbS}pCM@hO_R3zvlD~jr%z}=XFW20j|0B#^b%`sk&iRc|Cu@p z*!=lbuPSV{iky)+Kl*FEH&gX<^~-^FgsTs{CF5S zI3G#20g==-8ORq?h5ldal~p*=;fpET>S28HUxO7asdX#%8KzSGn9d=UD7Ov0~vtV*{N?ygHPaeNMM~XzF%9^oKqX8z2hsxRH0I4~j<4k^Obdzi;DGL+pGJqmI zAMBc44Cd)w2tv7bxW{-giIfC9maQ4vYShxi7Z=Z$Zq6JHS>v?;u-TRHvZed`dy23U zx*7oMgfGnJq$x@>%mQ1X}&qok5ASI*NSDv?;YQ%Xb%x^1hU68R2Kp7p3yeEN=-e#P{&4SU>YJ44XETJB2CrN`j!WA|e z`6|Ha<0G2sj?H8&@q&Jz;5@j#z^JizbMA15t40s2lLv|EgpChY0`h*1-I+95e*wke zLFya+LVZYFGa(OM>SUi;?azh)J(xk_aDTh1L5$I-NxWf=461l?aYSA23zRX^Ft}( z>YNta^t}z~u^7G2U28(}jjAj-#-A}ZP;M^QI5Y)?5noNqVr*j&W(K{db-I*2O9teqn*gA%4ZxpIbENPL*m9t)ib|x8 z4ZcC{v+dL8G14Txcrs3W=glYxtojBH0S8R(i7|3h_J?Rhs@ZG0w`?zOKnQ*;CH<<5 zB~JKg=tFc*Kmy5Q?^_xJ_VXHD4+7Yt?9rY9oP~`y=nz%5fiZ)@EfS>iunq6HYRpkOjF|+jY^a%wmczyS?;gmOeLgI_U59 z!p{Tbf5ir8srYS`W7+ytAI}*3*;-g1>5TsI*7rxZiHnGx;XNfz^q-O*zANtNiQl3f z)c>(=?*4TM*D}~*x)D+s`V?Ur?f|L3XO*;10QuK=*dk~^(|H65SuBg${)I${?ma*E zR`6FX;z0r8HYhxTpC6Pj@Tu-GkD6s9VOE}!CH5~WK0HJMu>=|!6X(`kAhV7O2&ihl z&99GfH#ZgHfY}*0`@|W4aCxzjKBg5%Bh|-}L49~D$F}v{poSs5>VxPmOFvVNj-Ps_f-Yx@@!W{ZU)Sj0;Qt{FT6MMumE zr(EpAZ~P4_o^0EL^RHSCV*Ro3^FBCzzbgFM2B0>7&iPcfj6Dht@%K*5N>7gQf&#qL z?%t~O)oU_liSQW4l>0Og*Gek-o0>cpw$+PqYXLu;My|vgZ??Y&k#a+bk=*a|gWK4t zf#m(SZy2i;Z31tG2e?Iqq7k6z;`Ee5*akRkEezB=4xV$Ma5x~n@@2e#^5rtrDG7Oe zMHiddZlex2eZ)X|BgFtOi`y8VJOS7$x5Ah~n4AH++0d=Yi1c{Cr)4MwzAUgY7?ax2zbFh3Kx4X4L&DdGU$APfx?Iv@68p=Y-| z=1TKeTugm@MKsId?^gE|x8{xQ`0(rE63uP;?w`b@J-vt1IDF1#hVp2b^4-DNAJhr| z3Dd*@b1t2(7!~5Zg)1&0K>|aT8438tyQa3OZ}IW|30~cKuAo7)d^-0;4Pl^{5k+{V zykQ15z5I?*%OgwD22I3&yZek*A>MMBR7D=(5q|UOa_d%%B=;YbVZ1{oQW8$6ILsaY zT9lPH;2*$ayyGULu|qsFx0B}{)XBqe^%UbvZrY9c;PEEp#&)y#>?z$e4vqEj)p;S- zKX}1fTNI{AYDrvhqUP{yuC2DQg?rxf*~xQD*OFa=KVkQN=5H%2Ke~btj1zAJobpls z0dkzDoEBm#E7ZALnUQADPGlzaWX!$AvBn@J;CuhV-_@OB;@*G3|QD0uBU$Qk#rRQ2Mfh=l{8oZ-BKlwEY$(9 zwD$JlYGd}fm$HfT`YBeN--PGC6JclP^%ToFEW_;6rU?+xg;dqVko^1U*!Y0Ox}fB| z-^)~0sEL5yWAVQl6B_&e+lcvkRB^gB^+=p0wS%53rKe3Yc`+`V4H$ECu_aqWWX!0I zEk6hQk5ytfok&Mg<4^CbC zpH4fY=0tX3qjP>(d8bNpq5K9B71$ZLs>vFpmcpk~MIJ@&tBw6)d=O4k>5eRI&8Xjj zzqa;C%sNo)Q@73yPi9b#b52ARB$L zm~XYQeH;`ZK;~qAuT7(m@XveNF+TmUm zeb0~ilH1U~4+~79IaIu5$3nwGA~q|g^iIUaRpNyy>M_Ds0Lp1niAgZiuF@XGX8|}Y zYhqY--yr07ilOJRILjK44R~x2>Gn>>yL%@wjIK(AwYhl_AR~xZB1uX>`kFrtN%NV@Eav*(x;v4TxJaygDil~ zWQ|c+etn_EcwKTqTPc-VWZB;G1QXoNqZJ1S%heDj6o#TRu3wCOyo+ z#%*nBI6LxMgYtPoheK_l{V{UdakZv#?3Ycz#?!CXr``~csjcby=4ynFBmKV zVX*Lkc@CXBndl(U2eU0ex*=ov{ZkQe?3(Jsd&mv%pe=}t`}uH_m*P-yb1(@sKE5&d zuw=uA9+lahQYN@|&R^uc^tlR9ExeAVtv5JTsC;J8<5ldxInh=|*cq2S4{lje3Xa6y zV$y4Qk70~VymFkenJeOCKU78k#3^HJ>u9Yf@e)p?0E&esb)KBkWPrK^5O@FIl z`Y<{@7<>YZCUP%YMeIztg9U$!R=xc1sr8Te6lG=v{Tn*yg^;H{Xrf#^%Qkc0bK|>5 zTiprJDi3sFWMRK0BmN*DhR0nPBCZhZX&%SiAC9~qq*)3>h|DyPPZgPa6Y_C`u8RBj zBox)A_kyjgPN|>JS+CLb^zy3Xjsb+pDo4DKwn86=A0;*>)C}6!D~N$-WAb#W0%r!< zfmf4y73v--T>wVYTn*nZr00cH1I)vCr?j+#o(=ym7XctjR>fB0fq3e7uPEGrdfLM# z&I$o3qvv<+628VU2oAif(kvpw?F;dLAv-$piQa~}3syfrG6V)&03$KjKx+vVdO|ZE zenw@k{uw?v_U7!V)zC19<V#2dGzB%B9eDMR3W{3I#9*@Z%|HdaZEA} zWPkgDQy%TvK{lDSl~oR(OjR7sxEwkrB&CF3V}&Z0{cJkTQZw=o7%76@8S7Si7>Jof z;2ZyJSblpg-35hY7N|^!jOqU^tZ{m;ynMlY_y~ucB{m5Z8>+&}yZ;9!2~WJes!8kH zX_6Y~r%&s^ts&Z@csexBf#H4y?_PgGc7%gGz$MDNkoDhDt@=WXUlFj$qj9=?p(Sg| zK0H>tjl`@e_D$itgT9)3QMa&=Z<_iC{yod$Z`vWNo=9U}eT=6DO+I%Z0J}bK5s1G) zVbBfH`y=bsSXs&C7zg%T$M#LKRpTt&$*#Q2Vh$M@OAFy;oMj}U(95=h~|h?GBhYlmt2SIPP#AuQPa93uNhLql;jE!A5D^MV{7sR zOU$WYD$TQ2z%OZ5(a(zJHCm#7b~4@F$eu0 zh*3CfoZm_}OuM!IT>bd`ofPS@UtzjE{bs(4W~W<|Fw9 z*^1<;64Iu1JOJxulUz>S92eC(g?mCGi)vSUelv}Gtu>H|=Go&Sw#c#}jG*6POi=rnS?zOH>+ma^;XC=!v3#~@FWFI5*`M9q zyi$cBgKbC!(syas-ZXAPqol?A?+WWxyHv2w@C~q{DvHATS--NR;K-EPa(05h#ypf; z4BjU~=d3CCokbj7zB#*Pb`QkwHQv33ppm zSzOK^owHDxK&68vG+UsB45r8=g~_^b$LklB@!T|;1PooGko@Y5M@x;_D5SjUJC zARSHwlsFPrEq+YDK0Y#R8cHK(WN9Em!6A-SjCGISR()2`QP`UVT*P@mhL9Q;rsGRA z)6uqIQ88Pm{%tUvMf-9fE8htJApRJDRdW~>K`m8XK&t;fgWlDps?T^%Gg$q#UNiIS z{5@Fg2GSh=^f_e0ruSesMgA^LRTNexGL}d-Ic1zcQLJzoER{okbcBwSG1JxBLCp1) zXgI$e7f1zOWd01{>=c*cipBvP9xR$L_G@Im3Z5490sX*6jvB=7OnR=gY zc8?S8&sM*yTvo0irqi7T76A1v=2z;!A#_{Y@yo^;n`^m~y|1fi&x-*3G_^bO(w;^U zr6)ZXZ`EpKky&qK?OZ>B1leF&G_Wn`w> zoW@qlZf)=5qo+=ld(Qe#LoEx$N9Ta#4?W_wF%jQ2%WMsBo2nQQplOwdF>#~uRG%%r zWom0b4nJSrG8`?{9S`+P$~4mgRArVuei=+^C#%F`HpcG^EqO?hjCkDo= z6^>Up*d&j=PIaS-rc^-A_##UwE&7cWnMxbf8GzB{SI3nZwfq6kQ(6^tFf2fOP%1?o<-d~XX40KVj- zd-$uSZBa!&od9?S!Wg>--b|v*Eh7V~)O^zsNhdm{Z7HgTC3ZEd|_`JTH(_-*jAgWJ@`u|11VRj3Ij8rK?pe#jW!E4C0%GdzX$S&)wH3M=kiRInbFbHetg1* zI)n_*RM5#Da6*iOiWVjODkr}cwK)vTrSp^TUU!)CC$x1bF%A?B24P?1!8vhpwUz;f zv!SaTF)N&4u+b$aSdupA>MJBfnjNcTb~}s-sJQW3vVhV8q=YcxmYt1L;5AZ7X(=Fo z(fMBi)muG*Nyz#KtqJ#)UI$w7xWG_R9L&I0KDBN!UmxZdxWke3ZNBW{h z4|b^XK6Sd({%rVm9Egu683NBg{WMt4qMh?-Q&8MdKD9;qI1|NJHPt>s;;YA#;mqNz z@^zE>;ldpIvBsvrGjX#!i6h%Y}c{-V;%<$S}N`}pfus( z@wSL;O3P|!O&|wmf)I6c@2nMjP7mUk zWYDSkg7L-)Y`HR_AyY6qEOR+-dibZ&%5Yu_?XXpavj1G#Pfa<0)0g2Wv8%D2M(<21 zj=#uQjCFAkl18H@Veah!SJs&K={4OCQ+lw-B|h4V8JJ!dV@14q^h@h4#u(8p5(F$LgSSs1qirvbBz#39@0qo| zs`gvnn?uP$GjP)uRV6T{!2OtwIwMR4+}NqEAB*Lt=`AWG9gj&bOkPj&$cps!-UK+~ zlI+&hv0hhxCpB2^c@%PG&M3$SlO@I1sO|^jFGE{wkAD zF>h@A#nyrhYdOeEF%PX<*|(;VaFXPQg$@%a%@KS( zVKo^s#j4$jrrHKP)*E z?*Aee>Zc3EHpI({2JSQVDdFiN^wiTbYrGH(Y@Ctd>rl@DcWt{pYJcXow6giCm&4ei z`NhZL(ac^7z<8|5#93&Q@52BXjFFhBH2$>H5rnmqT+}-lukc7vlm5r5=&}zoUp7L! zGU=9)(8s?2Ob83MIa`OpCuu>h$>Si87tikq`kC+$+-|oYEav?Bj<*ucn@$hP_!(>p zRf%;5--t}l%`R63;G+c35b8&TfBB)tNY6$xfw0v%|7&wL0LUC@Gs;ti$Mj*);*29= z4cUJLs&ec{CHwXNvG$T8kjG)=rJ`PjYzP|+PIy>0#&+3hkapN{8aX_wvdA47-|zx5 zE`~lE*b}`^epMuIQ6Hx$aj6T+Q>_yza;%D9dsPV(FpK_dEVs>yOx!%*b2u*eYa6i| z5@iJt)9! zF9K8vs&t@?g$9Puz4Z>D06KidOqe&8Ylg#{StERn?W0#=QBQofE%NDXZwpJcQ=c7Tok?9(v>!I8DJ8xy=&J^ln@AT1=iyF zWrB^mD!kVp%E@1Yu8OWLy0Ro8R(yVNU82y_E9*W#1E<0zkfcj|J-j7=@MvQLi5 z%o}RkEI99XvmSE$rqO+sCFE=}Y)6rwe5S#rT_(|Cymp|uss1_&hk~traaiV#US_$) zvfX?BZhkbV$)lHhn?YuRY72L9d7(AHeK8%ASu4xDQhK@)!qQG~zMA>AmUtH>q z*>jQiRolItAONtWEp|><7VSJ0vX!;9!Ck}U z=eEGT8ItX(N|Nc|R%VK%{q{SHwS8jJi~YrWRTObwm$7+_uu#(Z`6>Ege0|JZ%@1Q9rxL7PdT~M)KG34X4|KUI9)4o z2b}*(ekSYlY?18@FP`NTZ+7eIF@+lRJXkwlnutaFMM^Ic#IMS-Jwq3-jbMuaQhTn2 zEAXM)=jv+lu9Jt-YbPo5azQjwG$RSYSx+9V;uW;4LfPvm^it8Ubf3(muK33?=vIJTt6- z!tw4F!Cj2}BXBSD+K!w(5LO2_Q26fUkH zHh%ud{#;P8?q@kES{@Z`#S(Ts!(QgJj${9w(bi1L@$1bu`Gb=Zhr^UokGS9NG&R}{ zTbN{|@W^_iIaHa{O|{1*<-p&A1`qpt8B1cSd3GTO2Uly?a-~Wt%0dbL%d5_U$E%G9 zJ{XwWv{Hg=OMwEGJ0AZ{e=#Qvg{=6GT7gr z(#J6BrG!^HUR=y0Ji4C(%|0i&7@im9HVNXW-g~9~6KCC6Q&u5{MpmWQo^y-(ict8N zP6K>xZWdP$VE^Xv$mG?AZ>Lo7pptT`h`xAP1H)X^`)yLBNtp=)>Oz6=iV+m=m zCFeLqM8_6%jpIPK6jnhifQmVYn_Zx*d~5%z#$px5CkQsPub6BrXwi$`HOw$ZefA^N zlkKS?iuhi0RJ|)FuLZK-PXT%Xuxb)^u3*k=5AadfJ!Pm=UoEI+^K4ugp2+?-%n_Rk&O_E~&24;BY+qzFmXJ4tEC zwJCA*$*aKf(ClqxvLoa@agYTAZ(xt5=irhOLN7&r4c3R}zua~v4ix++U{`ax8>ynS z!F`JHHgxjx)HB1bpd)wgEm})Ygtu;ktAmfy)D=`TKbyjYN^kOJUR+j+=Ie?jU|+sf zar%|V=kmO4^!%Wg;~^GC$AtZy6majrhn8F}9XTGQN2vOp1Exki;pm9a1@;M zyP{7Xz^#AP2afWvX+GXi)(7)&(q-M*w+Amscr4C~ggAT8++#;?q}bI5^Hi+|+M=ze zMk~}{PRUVhF1Qs|XZ*;KwxlfJ`bgVLKDm|WHu0C!EVWr*sea@sC4LJPy}b-B_==nS zoFFUH1Mj4;N6e7ii@ND7J}D>iJt!7=OFqiC?czyn8d~!@Ez6vfjmASfRB}8)?&c0& z?$^lUHC%ugEOAm@)AF;IKZtl~$HZfW5qHwQFd3bhSFlzEx=wy8X&hss+@|U#wbSOx_4j1p?Pv2!FkXP(}@e?}s z3BJ)HO|NTOIZw9oRW;EpA$TpH)#qbcfi2KUWRQgCMs*n3YloZqG7F}9=zz3=cCOn}2;$Y+R1%JeHX5RcB&g)GpckpW!WxSx@{aUChzD-Y9ANyfV2riY-6hZd z<%?tOl#qj)MB971hP$n*<;cX(&(9c7p5D5YaL?VWfldBDh}<9S13wY8AL--aAo!B= zK56s!EucrEuYwhd8VP@Q#_^j7PT0$%?+l0B#-3+}a0e`913%7EgVczYz|K-FbF)bk zgt3r(-EAkT2HYrub9Jb?rXCW$c#XC*-1hu#Lts{7%o!LD9wZA*_;`E#Mke+S$I6pZ W_3E`j3h)Co4^$Pkz?JgQu>T7h@(MQq literal 21268 zcmd42Wmr^g`!{NUl*E95N(s^o-7VdXFi1B@cY}-|-7Td^h`=y3%n*(OBP}5{go1Pl zNY`F?-_LX3|NZWLyzhtoVSnIY)>^Y>t*g%S{KXZct*Jza{}BJijT?k2%JRB5ZrrQ{ z{^)S;0B2}2_xyl=H@$R~WN*L*sn>u5EPELZnHx81k_gT%v4LYecV#258#gGJuK#YT z=raDgaYMIUMP5eV-)t-YsN-H&W7B-$wj|CjmLz-Zdn_H042eF^n^dOz+R#V1xA2`+ zQty#4+GF*o#H7kRVYJsSihffNG?Z|q+_rzuZ=k%~lt0LhA9>VYaPWCQXKnrIw{LI9 zn+%`&99`sBS?dqd`r<4BZ=MnAEfsvI5ib3_0?5+gW91 z(gK>ttz|IkI)5ILno?sSx&PbC?qk&Lfpj*#ewM=E=VmS#)L7-N!&f1)KhI3p|Jivz zGd2V02wl<#cm}iRLV@azxt_b!5S!T8JGr!yfm6{OHo07X^@5lUBV_ydsZclY#-35@ z#NYUCOHtb_4;WiKYYG%}c*m+rzQ0W6mZK1tnSB&^!24}GjMd>?IwA1OpYIH1AI$~L ztLKXyWCBfR_j@x;NPV0l>a!ky;(B$oyw!xfv&Hu|^x~D>;bzP7ic?3n3&t{Bd@yp_)Vq+t1)z^jYP<^CzJ<)-;7Glqrww55}Srul&-4(S|DV1txx%>AU-#WLkHC zw@Xd2*nQ!?zg%Tav`5APQFjSDtK#CdHxhQ(c{lswO8z0?@X)%Td9EFIyKcWC7J*Wt z9J(gWtYZ0bBf-wEAMb>q(;ZhYB#9-14u#NKdG(*_*K#>a<)S7A+MaGV`K<}4_qObf zev7y>E=i$7BkBn@d*y75T@mcRIgGGd=wi;(TE6^1+&y6!_P+E1uT{Ee z*pH$X?-Q2&Df_})MJ-8}SHyIVp~t&Wa%O6_2{M-i^Gubi-E-{yUGzIE+cDKiyY>_U z=#t{+P>fD*Vm#JH7o}N@U}lOzWrX)TFnNW%s{;ImD;^@iUDq>9*kxMXDem3I#Lnl4 zcAxD}EmZbF^PQcQJ{66nb7X}5@ek^p3rf_oFfitc2hDz~u8W7rg62_lI4~-Y47v$? zgIq;}4mbHL48v?S3nW}zKpL4$BWg^L+A29-55?$`Ag`I`A@z$uOQprksP|+~X+;D3 z$36P9wKN7E{=V00MZszIeDpop!TBMkZ5;2%S1RGqaWJI$OKIP3k@H&OyUlXFvRp*c zMAI7S!6N}&qEDme83r})?K2vdx_sbEjcq zP5^0*VM@xg_mk8nTGE*~*bshQh4IZUgk3rtz)W1s{4bf1h3}NYo+r{sTMpes$M3Vv z_D8P+6F0^*X=NZLtxUbzEX28m0Um9;Dxzy2LppnkJCjF9E74Ca?(2m#5ubOSvubpm zb=^lmpN{zCR5=-2m`}1y7tTv%)c(lCb{Z+}X_OiJkYfUbnpm$2D7FYQ^*z5KkqM)^ z<;m{Ii{o7~7vZ;)4DLe`vt#uxqjM5RGjTQhy8M&hqg$D^3ZByt6KPU&nkE(N?c~&K zeHXEz!IW9naYx@CMEej;hHo&!S@3NXt@u#Se~$r6Xy(M%*ww5#@Zfch@D72~<1^Og zr5a)p+D2~l?ksKF=HzXkUwyXHN{Q6g4<)8pX?u8@=SsYUjeXyXC;pZSIn5chAMDN- zTzptdxc*T}SyH4?UcN_uBO}W=yLtjvhxRg77wM#b>0HRScI&4J19LRXEwAN%QGSQ7 z7H%eWr45TSUWCmXSRimCTjDdA27_OsAbLjHEYCb*srt=ZzoYP(7Eq(H!-Xppd!*RnGvKk|M&HScuk3AF2y@V>Uq#;eQ*Ds`EY1UIdrGMiTti_Q*MS@^Gf4Qe<`@@(L4`(f2X#?+ED< zSoK3oo* zLmr7ElN#tk`-6{BPlC3m3_+FN)oO`9s?0Fy;cY?rTaz_?^=@Tp+`X^)$bm5Nb0*fp z13gn==!S`Q>|bbh{O#6$(m2%+l%$b`)PDHLRAQ(7%nZCEy|d25PJ-rGORm&mk)tjc zJ1DUmScEPK+Zp>-N=4uCn+L0X&O#_>QTrSr<2!4P(F*P~G4qn_IyW>kwzWZC<9*o? z>2WXRA&mGIvYmON`_5*Ax$>oBg%=+a#U87Z?lvz{Q3ugJcyi;;-TOB0M9S-k1HSa_ zTIWvo^8RS1zZD%mNhataMNA`RMI*YBZ!VwiP_=lKV2{AKr|hE-N&Jd%Mwn!+a@Ftr zxUWo`^GLS-li-Y~ui2zq;%#)oLGdR2vJmNhDhjtOGldBZdS$d8|Iiy7Kp@tZg=@Z$lGEvu=hzM~>0CkmfIHQdadnc02T6%F}} zKrSOhzw=~skLH+#bF_7@$JJh6SIv93Hgf-*Hw?D;+oI{Uw)tTf+Gy|UK7DA0$^+dj z{fV^HkXkMXyTRHIakM%b9=ys|l#7RRITsYN>7Rt03u(uRQp0+@bVhVDRq#3UJrYrE z7C);f5s7jqc9iG4C>u~_Q1E#0flp=Y0-EK)8m@r~D08#Uo8D?jy~|5FuSv&ns960JC=^7fs&pq&seetXz3&-xYZ&yt2qi+jqC_mMDaHVR0H_mfrF?Ldqc6cC7 zOUm}$?INthmwTOWN0-E$#!bX;zKT|)?aoBw!b^z86xPY1xg?z_$Pi{pl7{1hJ2B5i z4EVghaz^dS747OzJmW)oBHh#;el9ZcyM>RAL#Q1w4YTMF9FCO~$KZR0WD2}mQe0&+ z&B_}E7Hdt0&xoR&i*k>Y5g8E7`jZvYdSjHWaRu+uHKBemlCV(Y-`*1G*OI2Oe}|Qa zE&Jk8^nN$naUGWJH1EM@%cf{i*E~eL&esNI;UPW3!p0%0Dzl3WpBvW=eou@EzC5ve zFhH8l#k=0>F)Cc@f;l`ib&bz(o`m=K3a2cOQ|XG*1<0YeJ7(a1qrpBejG*bCg(frm zNjvBA`82Z%INCjxG{w$jCpZ|`t>ThHj%JTay};`~CIw?kxXo%eOW$2|c(9cl;u^}A zsPW#*;BggXVa}wskAUU`tMjvToP8BJ``8{)n#@85mX|Hf^ctKZ;>QxT$7NYMHBH#| zkg-`aSPDigrf&PJj(=9bN^$yykv!WhPW%c+9d3!0hM0P~%&a$XZY6KLT05JmPiJgb z_5BhHgf&~uMr;Cyb91g|)P6820e6J8yQYA)$0I(YkDPQ$=zLznjMjxO*+m2UdfQ-m zHmXiGzRSl@rVW#zy|R7k%(HXi01>nEPl@@+Dt|b)uA6(`J~qLOeewIOncX_V03x-- z$c-<-EE{4LO>;kg!17?SY=yieSdlKCtrszTJ?>31G;6PZigM1wZ2N@lW)TSn(X)$S zxT!o}j-CcHv zSZhh`+3%GS91Yx_Y1lWDm@}aF z3S1!b_2Jrj_Rk2LHaGHJ6f7>7VPMB+{8y=EYS?m)gjw-b;N-U$}@(x2P(aj+SG3IOR{ttZ{ahg-}$2kX{u zhq6MdQ^}3MJFo#9#gfW1V1TZvfplRe{J%hbDnaqzoC1@f@^wtQ<}$cI|2a3o%KV>m zsgEFj=Lc?oeM1vC0!PGOlM*GZI;d;TV(%6?7&v6&*8YDygz}MrOVMHa0-e-So}Iy8 z!z{jbFNHtocYizB!J)t!a=C=Afe(Q6_!6juW6Ems$vF&j&0+IA zdZn6iWyttr|6(Jg@5>8>t3w+=O{AXLn8Hc2b>2zw+7IQ|ee&9x_ThJbqVsj!wmaU) zz&jIc>MG{5?i^;sZ0alvja0Uba$pL2wHbFp@CV@2?i}L741q>?7FH5aEc_n*RyeTj zcJ$h|&!CtI;iw@Nyl~za&kf;Lu-sG)1MCOdb9!k);!F=+;f_+ zd4)&DQo;nOZrrlqGDA31fKAeq2l>`2?Wf4WMezLUJDKB1tyor>4I6p;}nILjNm!fVB~v5zQ7IDaYG!e{gNm8kD#1XI2Tn>jr2 zXxr)C$Cpuw*=CYKnA73NwM=v)h`w>6`YOtUYJIB8iP(wX{|gT?Y-7R=?(aj;fvD{ZL5jmYa%MpQx?v*M%wDWn;KA|M=74@ z^Lui)9)WF&x^=0R{!{j(981kP`qu};YTK+RLK;iW0x>c=0!)opljC@0fNX({gf24C zar)9zcj)V1jdTuWt_92cO+xcY;3GBsE&~q3Es-uu-i*7_sC7Q_!d+Y zp1*~@=eMT<1U7~qJG=sk05LABvHV>=tTZF&lLm=-$a(}t-lKP*<#d>n2$63(h+YenN^IYW4pX^g~76vS2*)~;h zGeRESO5e<>)E?8DUV+~sJY{v2o6YV`q}c#*Gr+vjaL=3MU?$9a3pQJY9DD<*mlh?M zhO8mrcuEmljg3Pep85uwA;k`fjl<8>HF$5qB!kTnq7T1ENe(Wl)JCzp>%-HX-$6^# z$=M;;iTIhRF2IReS?r3BK-8*D`0FNe5q5}bBXFV@pS<#x61G~85OBiiE%fR4e?1AG z`aiD{iEj`N{H6`n|ItG!4zBL6tEaLFE=~+>boXWcY zy3PP844xZ~pE9cHCVUG2tDl;og8s^Jxr1IKPV5j6Jyg&x_zd5#|DVfGQ}XR$MbHv` z$X`SBv>oVca*DtDID;D)fJlYEMq=rDY%Tv9FM|^XviJF9iWQf6zfZ~ zB<8~=m$*O~WH2M?H9sCB8oH2vvUSvfV@V@4**@L)5}=1YRdWNi1bC=~LajqvLO;XA zfhhU_h^FP9THhkJ@CBX5h14>+gzM}EG@k3dzaD>PMu^;k-`TggDUK;y@6F{GhL#7b zd-0OX60J0~eM$@x_!=Y=CJ)KM?h- zC#Q9Wcbx7E@`s#)N6Yo>>(+7ymZuvwYDoMct<+fvpN-txIK;-sbhT{y*Zs1HKi+KQ zmNO(2s!hLh=#?=<;mc3f9%zoufr+O&%K^B{*tJ=tn}o|T9Ap3|;hHdTm}|{A`GMXy z_{ylk(EKNqvNQdet%h)Bt~E<$SZKAZ*pNx&5zZbvn7k4!TVKC}ys$evq`{l@CS%sD zHHFGsz}j9Uo3@?SJ=pJ=jPUO4-4gN!$zba2%_XW1KPTmo_ZdzpC%7s^q(y72FT{So zyuj&CFc0IOa65nA=sfHCQ22C3*pBwS!7La8{}Kyc(xzR~ZJ+$f7s&t#JK3ix^<2VO zQwA{wp*R3tA;UTM?N-FJet@_9SBlCHe)J*sP>SO!E(5W z$sjqC+t?9ZMZ#go82ynX(->3<@Oaa#E`a>tK~+vRo8T?3T0lr8#5Ad$L_o=%LqucN z#i5OWp~ho$xmnpp4$iEV^BlXvaH!L8XLLYW z%vO!oud=_%(ue`f(8y&vS7RCh3t?Xs3SM3-T~ggJaM4&9G<~@-V%t;bCVb&Ejt}F| z+HPIctVXc`0hz=xGT2BTXz_5@;o=EFsZXvi>OcynZ2L})1oKtofo*DmDjec>(yL|n z>%UwyP-OZaq)uAfyuD2+P5_13vBU*_qy+ck4UpP^p935%UI5&3S`1^!7b$*I9!tIVZJZ86r8ZVhoW2PP zUr<0nr&i|ufPzdh^kDZ_iUGvmLZqH{l_HPVt1*O&eacJY{3>uOkmKIo^HMEzq8cl@ zixSMb){?DHR2Pnn&k8lt=jtab?M00fz)TT7T6a$LzCBIUQSrg& zwfO!dm;33-c6)IxK}wHDG6F9B_Mkj@|Cz2?lhoS-TNR*B!v7@0r(*6D#!aqu#Bs>Y zDP&XjXXVp`i}hUSq20OA!+pICX}d``plMlzX(Np)zD19KH1*Qvy};ty>zy&!P~wt4ZiUz zVsBx$hrizbdBQGL^0h@R?WN(_yr z1Ar{L@=TTCM071{$8r&yA-YTGAF-y&l~{gsY2p0DqT|W8505ZRjP9}@#9T3rlCDt} z5zZeQ2tcG9;a;qtvY%5Wvs*5Z(xKL(_-xiFd zIq-U{)Uz>OR=jVR88K~g@2@PMvnWPCae`PA0gc!^SYhymje_jeMMY?+(~OC(nCP@! z9rA-Fwx-V_)^VK;M?9c2yM27NQF~{f7GH8|=HYg6&JV39@@$K#cs1=nt0uKy6(a}D zT}f?>sHGzkONu?XM6|YrW)^8wvur!sL&cV2KYkH)A1ai0{*KSrx4o<7bII6NiTh`E zUl($GI8o7yH}aB-qo}LFSwA~XSTg!mpc}yt)EE+4XN=OA6FDEiKBtK>t5KG%`v$e7 zD5xY3&z9L>u!U2%WP#MVxKXO3O3c8-{W~YNh2lopZfb=i+amUm{M>^W=8l zU)>QW5%0YhUS`tEqEWx88+=AD_A`1;IMn_dDJqRWQ*!vl zGyW98RzaU2*S;_B=O@u6Xv2^pIc-N5M>q?PpH2y>Mm_yKqqZ~q!i2efEap$Xe<;SY zA1sK=qGF$)<$Cn1Hqmn`kpt*FxN)h|BCRxt!HG4@WkrnnXnU>-CvmHydZN2m z>h;Ig6LT!|)VZgM-^TZC%0cas6KTs&omQBs#t@|$vtNCF6D^t$#19c)hG+CTIEE23 zi|L!k9<3(y-c7mXX?{)l>|$Qk;WlSCw+F&=x5rbKyoU;l|2+}bJnZd$xXTIwQ7qza zs;G&3WVrxQUqc#9?HA2k4s}-=-EW-aE)MkK>h%?$oocO^h~PxLf5Co12WyjaO z64mvxyST>1?}c>6XU{%O!M?8z@0_QkCwNOqZ__o+g$dAJX~#Z3q6v4Ea5a<6S`5S&aUrmxV_wJ^`Ll6I8@UG~1# z&W*eE2Lf?;HIGu1rQkc8;w--O0!1w?Ebl6y{C-OHFi27wJ_?@MYVkXgg6U~S0iah= zp8h<#tBZ3Y>H^%88RC8Pxeb&O{h@gAQPFB_05025Tc|v^g-AEw@F@HGF{%H0jIO8( zWsToXH}=HvNiiw-BPuG7DiKTrLNWi0m{q?0-uz3V$0KuZ)lZUkZ(Dm3%gtHnsM|RB zZXF!D-8pxOi1`2!Vf ziHL5m=#qxgQWGb}`GAodrtbB+ihSZVZzZsX=gq{FL)SCdr8g#Djm=U*z1@?HYR@OX z_m^n)U`b*%j_D*SI8YVqg{mwui_;dDcu}*O4+>%k^5yod7t8cYsb8k9t3L3ols(}5 zXXyZx*#9oc0!7Db9uM7`a9K#pbB&ol)__+D^VtC11>YDnZ?@@9Ds=cTqbK;7Dhu%Q zF`vLZfZaj6jKgLv?0eM5w-65`Y#@q4TmY>lj6+1*_)FU656+2AW&2Y~;g7!}%hRUr#UIE%!_Zi9KY6Q95krd%yup*WNFp82 zeNv5>`z6+kr7yW5&cr|7Qh5NHeW^8Cxc|d9r%iz57$x1~(QNeJ)x{h{6Bac9Mk4=w z`)hyzP;@qZmX-0gb5@*~A=`g?^$S*olv7+r3LYP> z617=%N5+30N%*pfHV-?Sjb*F5pA)#VOyDu!_N`xzcpPl_P{`T&S-k^|Nfs9Y`^B&~ za%1~DV(*iS2{6uC$qxio*VxipTdgdwRVlu5v|weBp#+J`+B6?EkKs^ zV)^2JYVbuPSYh==sxI6F=#@z&W>=nFE5Q5HHGVes;UqfX!;jG=)wty<^)gL29Dom7 zkbWu@Lid5z#L%oc0T}8{(mi_}k{pX4-(%tx6CRRj1;N(heAU$uShl@lpjfv>EhjD8 z#YF;x#%}m)iL=&_#y(0x!-!dLIs`Fbo+>kO+}rf3QLYPdH$?;+wWJ0d&7DCvdI_35 z*kV&nWiP?_3}j%$7b03Qt5AtBR6Iaq4I(f#A!S>#X)2T-)7=^F+wJtop+e=;5)`0WQ(zPuzH9iJjfW1BI8GFa#mnZ}b^wm@I^t=|97KmhN36ND!l&cie3 zwH-w_5Dj`q>b%N#eqG_;+M6x`D*V35?!PO1Dq$DR`-OXbpc+8WkjX!+WIZ8lz&38bhIoylhfRlg{XyRYW3PKwUWl1 zBfithpIxmj?Ja2W{o|XqkJh|aRL=^D|9S3kO3do9JQJk<`;y;6=MZPz zNNTw01>1j~w|7l$2EqVxxo#3lxlfD7z{uP)m0)*7CF zEA8xZ#58U-2Dxpwp5QsY4StdBQQ4rzky1L&paRuHi06@|pfq zd;OH~w`Zz584w@HG4r$STxj&m7QfPGds|sn5ZK$g+nu}I*`r5ssa8A&3klj^JlQ|w zn^^p9#!q+}VKWAIqG^E{1fPX?p=` z@wn=ID65OiD&qky!!RLJgl~i6&cU`z{)aK6b=e#;X7zd@t+5JwY>IGM3+2bMpUclM zMm3%pv(<^!ZFWscZLD#Dh=YsEk1`x~=}P(T<4c^7wPIw1Gl{v%{sYOt_y!qg*$r$g z&E7?mSI#ds)HYSs{tEyz^6nfN3gOJ37;C5N*6eR}82f$rV_4bii6V2;*h$4xQ4VPop(wYmtQmzjVzcaZ^Ex8 zQ=|Oi5eKGCp)bi*jzlsjVKqXy%F%!6q1PSA2 zG&K4Z$>S!8MkH%NGuufL98(d`Hz!>fMb-ioS8eFWqa3S)Rb3s z{9>74e6H;b8ZfgTzS<0pgv8`?D0<$tec(a*gOM0>r^oo&zh;pBi5gQKe1OKzQ2hnp zk2i$Z(FS8-ukYXA9^cPQPPCvGnT-8)4DG5lE=>aR4p`>F?YEd}joilF zEtO8@!C{4QP(}@S;njOSJB5Hsxi7^8B)pGRNpx;^e~M;a+b%|W4;Y%WjJ<93NGi!y7^-o;bW`|8n~`o{w_AfQe%h`AF^n5hRew~ z*tBvOLn9yYj5CgYGqqW!)BKt+_JT`-fq6ovl(e$_;*<%HNsd5I$wPjpR8gk!GJarS zkn5k}(g<&LHec70%r<$nXt|`qm$KW%8sYYsOz>P{4WB|8flJXm8_Om3s^ljpJ7hRT z-P;!ssPi+$ZD*P^GAV=K?qUPJfi(oS+FXZ~hL;26iJsm)Cj-B$EF6O80X5dP)WB%=ZX2&JFclaQLe7P>mx>aqRX?fKG~3j$>N>`9)B5b z{y6N?b6G11vMZ_qoB&h1oU{bVtvcb5FQWM2%@KRy+>rDoUxHJMq}x2?a9hOrxd;1$c~TwU{a-J5ta%pvOWL_R z&TCDRj=jz&3-|nq2uPBX9Xz&W524(UCs<3du`jCRbzv4K7~84*pQtNfG%^!%vi?gO zWfrB=c1G|rbQYK!3Z&A{2EApC7wEm!0XBp~xEpb8_&}+Mn2;}}0{?V=Y%1L`l)N@3 z_P>ilO=s^m;1#Td0(#WHKmLdByJkfGuKxd8MgJ+1|5CmF_m=?7;57LawEVN2|B#&f z;|dw#Xf?-T|2c-}uz`;+L{9_|PaFXerJ?*t{&t<^qVS;)XXXD;OKKzjU-1Y~Ilp&+ zw*(&8*Lh7el?EO84Sc+*u)Nvgb$DS_W!#W}Ng0g77{H!cmulwI+W-7AY~T+!LLM&n z$&_go+FpzGLLqfiCP?4eyR>{@vqZI7#KA!L%P3Bus=3Gh5B{)$HeA=C>%WcmO-94S z&_6yaP7c|V(X=??QhZ`^ow7T zU6a#avsjajfH;~kUlp)ih6+M<>!*pO?F_)? zu5*X0?8A=aqzzY8YEr1TtK?oiB(!3@bKO$dOCIWm9C~27bCWP zBaK4}*VCFI4la8w>Rd?3Ru{A_IucND)#^|#>IT=o`2SGFQ+kqB0&2dcW}&E444 zm@-K?J-CSh5_0;xL1%h%%4<<5y)ER72s5kP^zP>r+)@upc<>#%@%4vKu42x%uPro+ zPGcDuH*z|xxLnrIllu&X@4jPni+2etMnZ4!{+_LpMO`TuX?<|hEVsxg0OV>!9}oy9 zRAW?Sg>L=dWRSju%@?kUBW%Wd&DOT6!kF`GJgHo#K{fHjdDHYn*eu+cosioYw1s zz-nRP*A@QrO&r^n6VIDR6)4xH=VHTTD<=SbP9aQ#0@k>dgtU2{uR_9AYSR87cjHgX zDNX{Cr%ipNXU-v=yg!Gol@fN9N;Wv>rf~Rx#QN=`*}F-#xJ8|Hz7A(z;yy2Ce$HH5lf76MGhD@ zJ`+c5yZ;WhyTnKaRow=1bd+$449U!asX1SB45-V79Lr0I@Z#XW(gz5KRPQ}w>)@LIuF z-+oT5Y}J{zzdu=g))Jp+$y62^7if2Uh?3D!?fRZ@d*F7`Qx1ayTngw59%V5iEG0?D zy`TQCadFm2FWhkwNxpwEx&|>mtI<6$Q{o{LG=m$RQm}>Kx?E&HWdg0X2dpU8?(IOpr+(kUX39N&ZU}v~(Td@ijQ=cc-43|0sUz1b`J)mzq;zQLE6QPMJ+bWzV zhJDd^0Mqe+lGSd4S1mawAl{8PjyT23ewo)LFD~C_?>kT5+xIxZsG_{DV3~(M`*XZV z_bfiw`!s&G!KaQMQvd`uOLN9gp4fr_f1;{sgqz7t{q%!A@BG%4l#B(I^eH{vNsJ;y z@gqq%Ifp^_zS`IlK7<*X5f4D`hQUO0l|GxheNE&kr9tUj%JWhapcP_Q1czb|Vm=r( zjr23;AzslK0nq@aCXO1fu4swk9=-*}Pb##HgsDyS*BM)$86r>bNfI*S5aEh?z;BOv z)hm|Qab-ew)nSI5!^aj<55Mx>Y(5U8Gl##aAF>2pv@Gg{iZU3i#ak}jVu$p!GJ#_YtSE?nq5-JfdCOmH%R7=mo%*tAid0W#SZle{x01Y;*tbJl z0Ps>mW!bW*4$J4z&XWr>+7y{f<+A!2`{g5aT1SWLf_f zXZrH$9(aU=mD;3GYR8yG-tTQX3Nn<}8w7SolAC(pt+-Y6$J4V0B283BV1@UP%X68Bw2k8%^ks6ooW2$`HyWO<^>O0e z1@=NklgiRB+QcX6wC|9NZjq+fu!R}JIAy;TJL<)WYj)q2*<1d6Ka)dj8;r7%U0xtC zsI5JRwc9|KO8Em`M#l-e-MCuQ^k(sK4`p(5{2xkci4xzB5%e1 z1y56bf>L~Kuh>MT@9Cr#=teiG$~yw>k=4}wecX17mLa!c7o~@vmSX8_k)JxnK*CLD zNwu=p&yo|fOaFn$>(Fb>i~92R@{aOqbYT(<1w_tSwI>!DaLz%MsE0FVW_*7kaiK%@w7cq|_MIZHK% zt9e=IVh(tq9X|A$NDhH!Q!0Up4ApipA*7uYT1C(Ls zl>6g_UTW~~jTm-HCuhJ@OgpXrzDnWR8*&F-tVYcG4SWl;~uX>FuAMIdmw3Kqc)+-x&omMuO z9eN>M|Eo}7JaVt~T$jM>aE90pAgxgck?JbYr&Z6N?ms(wWw-jvK&Ivv2yAo-591&G zxu!HQ8@|^|a60Rz+4(hEO=sj?stf;;sNGVUA5?HTt!8;0uo45vXIJ1dty^rufGsB# zt7557i!oXc*Box~ONmpxDvB02v;3zp9eQwQRr2#dn$L4a9=qV6Tq)O28>j>5WMr>_ zs#2{NgMPYofgiNN^V`Gy&q*s9z{@Q-p0gzQz^Z-$plx}fqz`Wc)9B)M2%wN_7qKr# zfk;5F_s%eXb`a~4yo zmwbDpzIOdEdX;=&g8hDPvBV1aV)+36wRTZAwL5HNguHURYWkU_XwY_E!eHRRrVAQ% zz4;gbdU?q!WssjjA=B!)?bc_S9IkctLk&XLKD_HK$6O|@Hd5>ltq^`;6S0p10Q)l4 zd?;6%Dtyji9-fVRuz~3paC(Lw3#mtx`tYW^NzwXwhtBxN%#hdwe~~p#uSx6VfPvkl=x;5htZgN*U`aqz-B##a8Z&i zFsgHnB_lDSSFR1|FzG=8+$h#ZL8jK{(AmrsoAR{6v(vUKE(1SHZ<48p35hW*CxpR84&-Wvj7oyGMj z9FX_EUGHrZ4gMKjZrl_KrLkIy8;*!cBfa*I+l(4Az99?qro4le+a>xhJ#EO0F z6vxRx=aS@0=s{@pN~K<2{qNaysGyB90kR;c?CoAge-0dO%(G4|50?!XrN8z+w~|#F zZ;{?Jju>jP4pD)2@!et?U8NEZ>~@|!PG|wljQg0KFkyhz=37K-)qh%DCK%p4K@VVN zUuSYio(Txk-D}{^oNGgS*^Sgsklhz54&W7s=pIzt_186QUJXe(WhL8hvbX^D%9DYJ zg9|Gi1Fe1iwrRZIo8z5gU3eR3xxC0I3S;i*S*3Tsr@v4f=2ZrB`?tzqaij~=$X-@R z09JBY{c{yc?-ky}`rxTvEMxk6mLXRcGF0>}zDVC4HXZonD|v{kn0AuJyt7b;Nr$Ck zBDJY=+tBy7RRg~cZhNNknji47F-WFK|504yZC+%==lDsFfR&X2ws(a+UV>wi(ehB3KA! zDqAr8*%;W>e_m>NfwtcIF>LkabfvJ0&V;+!mTl@SI4R&uSO!i23k0`o*c!dafBq9? z2};qXHJ48vAnhy#-2Zq17J%aEoB3!sO&rL&sal1iRNP9=*_Ye+&)aqN?;**2nYDkF z#_5laRm_HU;rKBI+|rh>Lh|nO64H$KdOKr2wg;9Ez7qs?vN!a{X>3eBh=9@X5Vrdc z;Jmcu00crFoU$;4_46XqFck3|;Azu; z1>bjD`i?P`5NDoWdnmB_F2Xa|-`BZ!%<(WJI=zph)OT~zw&P@JS_jx#&oxxIE6Fpj z*dr}+70et(DHjH8ovAr!mEH&Ts^Sev_lgUq>FmVZIW)~RQR|s&$Vp5ndVz01%V*sU z#xw(@g6ljC#obq2SMaLcYU%kI;)AHyO4&s|EQXB5INZ1)Fd>8__lSNkQE^V0d&hM1 z+}F?x*idauyY$LA{Txb_4{WNbadVd8X};s{73TzS4U_|b7QQ9-yVX>2jz{j{hKX?& zkc}s)D%M8rnL^0a#e`x3kP~pHQ2qzeF#y9C{ob6{f(zBx04|7wLEbV|%j_}Jj_U6e z&x@>E`&pgU zB>4py4KD_naZAjD>4^a)hLm@q4t&|v35n8pD_xga{VOJqWkb03N;HZdC98d2&3J4j z=iNDQmyX9)H0Mprgr%5}D?)b?NNW$|#iHZ6iNINm5D^M$e792jlQb>#6ii~T+4qz9 zY+!cXN+^60B5(QxYdK6!ovs!p!bn!>-7bgbo4}{zx6Ro|+TdXf)iwC%7X_TDRoPMa zK6re1FJ0woJoZl?DaYG>yxbw46n7u#k}0=%YLbdavC_(j+EX#eb8hX7>(RAh)dG@l|`zTbnUU2+BJI|3?FkMdmSBGo<{ zM7n~yFrN)*qZ*fM^8De#&63g3aR_SJUQ2ZjC${xRQN=DRu_6Hy0nnE_i!tlQtGLGkW1Os||Sqf{;!3|)}F8bnB~kPdBsYrA~sgkQK&38EM4k*D<*yE!!O z1^{q?@HOq(fsNN!U%pRjjii&j&3njzQ4O=s`iS0az=d;Jd`|*J@z*1EJg{=Ggt*dR zG9UrynAwveqqLZgu)f8Il=y?0+Ve;R8BT_W7t>6GgI2nhB~J1VCz%2Gh%lO}`_8cFT;WxnftSXU<=PwH*V+BN2<92ORK3V5 z0Q>pucWCII`wGYpvYHegxz#wIPay!a-(qmm29>{_EO>h9+5ta6PoIDTC^}-ObTu-5x@2&;{E#kW;rZz zD{He}uw}r6&{bBm6pf}f5e*~tZ2vmn=l_a8?eLhEv$xK9X@$R(*K7L*7^R@wb4F)7 zzcV{aTEgDbcBh95H)k`d{6=G+#Rw4bjBi6;Fz*ZOD z2G*Qp=M>RfzeP!#vIRR!f=Jt}L$_T0O(G`>F4m3qG@<)4%N{<77-y}th`QA{W%{L! zmI(mp%mf!p$R_`8vmf4YQProN5;*c`^R=01(q-IIDB_AA=>_lujK`ryj8PYQNneq{ zdCn>Pd~B&7Nj{XF&^*Q{H>N9_$1}{bo?l)cUrD5@XX{&%-2!&T^5CP%a zR=%^d-dRc}Q0OM{|r&i7p(@r9_rtBtd$-rPsG~gSmwfyl3x>W2Sl}mDUPWoWuAFzXBixwk{gKhY+@__nghAeb;kZEAjkWNy?kUQIAyhuXUYc4OEwaE3^|Lb zoKn_cORquSaORp{LSxs5AZzyew)U{&*`tSH8mL-uJ6(Apx(GJ(5J&nFSEM*R9f^&p z2v?iN%`(cv%7n801~t8?Vh&c_d$kQrOiwHh&(uvE)Vm(p0QxD{BK(G(wk8UvTkHG0 z=ib!hVaqifMGu5*q}>lli*2MtEp^0Bt4bY2+Nwm)j!GNbm5Bh39f$#zm9s3!<`|^* z#AEQpn=C%QSg|D}_Wq&9us=iBcKSaX6GauaKjUd?GV`)R1|IcOfGYu)m1sI12&3ey4w@wTeMnCX9#@ClhQ7du&I9`4tGO6&bdoUoW! z!^YfjlR+ZOya9is=)qJ?baa~4r7z!zO@Jihk4o_|Ev9-r*;`pd>h`Oj(htEQ>UiF~ zr!G+@;G=ZR{Ag$y1$XYl(WQQ_q8!<&BI~!Y$vYZ1v8vmgaqPs5a>wwzywY8}6AnV% zhOCywej%S{7zRw_Rh#yRtA?rq z#eMA70jDwIb*>&iM%E!U4`(kT6-#x0nNdSUQ9iuNXeRADD45W2RTl4Qm)0%3PDv+7 z>*VC7pM!R0vLT zRir*}TR75OvA_zB;S4s;^|w1Q&DMqb6yVOEwwDfQn#%Y;FpQzs`42o#81j;29aFDr_ENlKmcb00Svu)E&E-WljqMEL7{WXJS)H(UVz21n`0a0Tf=sh^~Z&3#BB&XQ?o4yzNVsK+lp<^OfNzE-v-OtP`d^P_jV zZ_?JBcIh4Tiv3d`sG*ppdFs36N0QupF$L-V0b}hKX9uw;!tl!_1z9A$KJ4uwsqI&0 zud5Tt@UePtAR#*afYab@t`6lfvyY+SKY#TbL^KG2R3kO{SnC2B4j2CC%YjPdLY2ZB zV-|fBcvyva!l$(iE}mHlFFY@TxSuXp0H_$r&`p5k+{N_|?hfF|(72TU`h?Nhixl&F z_Q@?1irX%?f2?6oM1FfbH__T$9my)K&vN_QTmU2WpYrM-tOqbTKv$`UrqiA8V)7Wj zD;0n`w7PN{=~v;99~;AdZ0vg1PWlluN|4{Wnp5f`2b7wCkK+}SyJtNbj=DbxnshlX z>^88w0Y> Date: Sat, 18 Aug 2018 17:24:54 +0800 Subject: [PATCH 093/360] Add overview image. --- images/overview.PNG | Bin 0 -> 133310 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 images/overview.PNG diff --git a/images/overview.PNG b/images/overview.PNG new file mode 100644 index 0000000000000000000000000000000000000000..78913a0a26815e3e07f9e0872006b3f925393124 GIT binary patch literal 133310 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 g{@>j)dOu9|ZXO$^ZZW literal 0 HcmV?d00001 From 2656d1d2af9b60e90215bee75ccbfcca61d88611 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sat, 18 Aug 2018 17:32:03 +0800 Subject: [PATCH 094/360] Improve overview image. --- images/overview.PNG | Bin 133310 -> 130305 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/overview.PNG b/images/overview.PNG index 78913a0a26815e3e07f9e0872006b3f925393124..2effd958a522794a6068337d6e9805ca4f05f723 100644 GIT binary patch literal 130305 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 g{@>j)dOu9|ZXO$^ZZW From 2784f33b5242037d0f89ace5620906f78a31fd4b Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sun, 19 Aug 2018 16:51:08 +0800 Subject: [PATCH 095/360] Improve metrics. --- .../metrics/Canal_instances_tmpl.json | 220 +++++++++++++++--- images/QPS.PNG | Bin 0 -> 13843 bytes images/TPS.PNG | Bin 11937 -> 16258 bytes images/delay.PNG | Bin 11875 -> 16064 bytes images/empty.PNG | Bin 15727 -> 17266 bytes images/overview.PNG | Bin 130305 -> 146107 bytes images/reqs.PNG | Bin 13165 -> 13627 bytes images/rows.PNG | Bin 18123 -> 17947 bytes .../canal/prometheus/impl/EntryCollector.java | 19 -- ...PrometheusCanalEventDownStreamHandler.java | 11 - .../PrometheusClientInstanceProfiler.java | 5 +- .../canal/prometheus/impl/StoreCollector.java | 59 +++++ .../server/netty/handler/SessionHandler.java | 2 + .../listener/ChannelFutureAggregator.java | 2 +- .../memory/MemoryEventStoreWithBuffer.java | 88 ++++++- 15 files changed, 335 insertions(+), 71 deletions(-) create mode 100644 images/QPS.PNG diff --git a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json index be9cc0e7..ad7af619 100644 --- a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json +++ b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json @@ -1,5 +1,14 @@ { - "__inputs": [], + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], "__requires": [ { "type": "grafana", @@ -12,6 +21,12 @@ "id": "graph", "name": "Graph", "version": "5.0.0" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "5.0.0" } ], "annotations": { @@ -31,7 +46,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1534552626378, + "iteration": 1534648729090, "links": [], "panels": [ { @@ -261,12 +276,16 @@ } }, { - "aliasColors": {}, + "aliasColors": { + "ack": "#f29191", + "get": "#cca300", + "put": "#1f78c1" + }, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$datasource", - "description": "Canal instance 相对于master的延时。", + "description": "master: Canal server相对于MySQL master的延时。通过master heartbeat机制能刷新idle状态下的延时。\nput: store put操作的时间点为基准。\nget: client get操作的时间点为基准。\nack: client ack操作的时间点为基准。", "fill": 1, "gridPos": { "h": 5, @@ -300,10 +319,35 @@ { "expr": "canal_instance_traffic_delay{destination=~\"$destination\"} / 1000", "format": "time_series", + "hide": false, "interval": "15s", "intervalFactor": 2, - "legendFormat": "delay", + "legendFormat": "master", + "refId": "D" + }, + { + "expr": "canal_instance_put_delay{destination=~\"$destination\"} / 1000", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "put", "refId": "A" + }, + { + "expr": "canal_instance_get_delay{destination=~\"$destination\"} / 1000", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "get", + "refId": "B" + }, + { + "expr": "canal_instance_ack_delay{destination=~\"$destination\"} / 1000", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "ack", + "refId": "C" } ], "thresholds": [], @@ -352,7 +396,7 @@ "dashLength": 10, "dashes": false, "datasource": "$datasource", - "description": "sink线程idle占比;dump线程idle占比(仅parallel mode)。", + "description": "sink线程blocking占比;dump线程blocking占比(仅parallel mode)。", "fill": 1, "gridPos": { "h": 5, @@ -404,7 +448,7 @@ "thresholds": [], "timeFrom": null, "timeShift": null, - "title": "Idle", + "title": "Blocking", "tooltip": { "shared": true, "sort": 0, @@ -462,7 +506,7 @@ "dashLength": 10, "dashes": false, "datasource": "$datasource", - "description": "Canal client instance消费binlog的TPS, 以MySQL binlog events为单位计算。", + "description": "Canal client instance消费binlog的TPS(ack过的events), 以binlog events为单位计算。", "fill": 1, "gridPos": { "h": 5, @@ -505,7 +549,7 @@ "thresholds": [], "timeFrom": null, "timeShift": null, - "title": "TPS", + "title": "TPS(events)", "tooltip": { "shared": true, "sort": 0, @@ -639,7 +683,7 @@ "dashLength": 10, "dashes": false, "datasource": "$datasource", - "description": "Canal instance处理binlog的TPS。\n1. table rows:以table变更行数为单位。\n2. row data:rowData events为单位。", + "description": "Instance处理binlog的TPS(以master变更行数table rows为基准计算)。\nput: put操作TPS。\nget: get操作TPS。\nack: ack操作TPS。", "fill": 1, "gridPos": { "h": 5, @@ -671,26 +715,33 @@ "steppedLine": false, "targets": [ { - "expr": "rate(canal_instance_rows_counter{destination=~\"$destination\"}[30s])", + "expr": "rate(canal_instance_put_rows{destination=~\"$destination\"}[2m])", "format": "time_series", "interval": "15s", "intervalFactor": 2, - "legendFormat": "tableRows", + "legendFormat": "put", "refId": "A" }, { - "expr": "rate(canal_instance_row_events{destination=~\"$destination\"}[30s])", + "expr": "rate(canal_instance_get_rows{destination=~\"$destination\"}[2m])", "format": "time_series", "interval": "15s", "intervalFactor": 2, - "legendFormat": "rowDatas", + "legendFormat": "get", "refId": "B" + }, + { + "expr": "rate(canal_instance_ack_rows{destination=~\"$destination\"}[2m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "ack", + "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeShift": null, - "title": "TPS(table rows VS row data)", + "title": "TPS(table rows)", "tooltip": { "shared": true, "sort": 0, @@ -831,8 +882,8 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "$datasource", - "description": "Canal client 请求latency概况。", + "datasource": "${DS_PROMETHEUS}", + "description": "client 请求的GET与ACK包的QPS。", "fill": 1, "gridPos": { "h": 5, @@ -840,14 +891,12 @@ "x": 6, "y": 13 }, - "id": 18, + "id": 38, "legend": { - "alignAsTable": false, "avg": false, "current": false, "max": false, "min": false, - "rightSide": false, "show": true, "total": false, "values": false @@ -860,39 +909,37 @@ "pointradius": 5, "points": false, "renderer": "flot", - "seriesOverrides": [ - { - "alias": "25.0", - "yaxis": 1 - }, - { - "alias": "100.0", - "yaxis": 1 - } - ], + "seriesOverrides": [], "spaceLength": 10, "stack": false, "steppedLine": false, "targets": [ { - "expr": "rate(canal_instance_client_request_latency_bucket{destination=~\"$destination\"}[2m])", + "expr": "rate(canal_instance_client_packets{destination=~\"$destination\",packetType=\"GET\"}[2m])", "format": "time_series", "interval": "15s", "intervalFactor": 2, - "legendFormat": "{{le}}ms", + "legendFormat": "GET", "refId": "A" + }, + { + "expr": "rate(canal_instance_client_packets{destination=~\"$destination\",packetType=\"CLIENTACK\"}[2m])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "ACK", + "refId": "B" } ], "thresholds": [], "timeFrom": null, "timeShift": null, - "title": "Latency", + "title": "Client QPS", "tooltip": { "shared": true, "sort": 0, "value_type": "individual" }, - "transparent": false, "type": "graph", "xaxis": { "buckets": null, @@ -973,7 +1020,7 @@ "expr": "rate(canal_instance_client_packets{destination=~\"$destination\", packetType=\"GET\"}[2m])", "format": "time_series", "intervalFactor": 1, - "legendFormat": "total", + "legendFormat": "nonempty", "refId": "B" } ], @@ -1017,6 +1064,105 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal client 请求响应时间的概况。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 13 + }, + "id": 18, + "legend": { + "alignAsTable": false, + "avg": false, + "current": false, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "25.0", + "yaxis": 1 + }, + { + "alias": "100.0", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(canal_instance_client_request_latency_bucket{destination=~\"$destination\"}[2m])", + "format": "time_series", + "hide": false, + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "{{le}}ms", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Response time", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "transparent": false, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "collapsed": false, "gridPos": { @@ -1250,7 +1396,7 @@ ] }, "time": { - "from": "now-1h", + "from": "now-15m", "to": "now" }, "timepicker": { @@ -1281,5 +1427,5 @@ "timezone": "", "title": "Canal instances", "uid": "8vh8NGpiz", - "version": 82 + "version": 96 } \ No newline at end of file diff --git a/images/QPS.PNG b/images/QPS.PNG new file mode 100644 index 0000000000000000000000000000000000000000..e646ce40f1106ccece6951d4aeaf163f916c438a GIT binary patch literal 13843 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 QZRMvl)pS)$?^?h3U%d++OaK4? diff --git a/images/delay.PNG b/images/delay.PNG index 119756807b4a5b55192ffbd4c1b94e12c142e784..9f236a7cb949f5d5ad348c12ceb00667ffcbdbf5 100644 GIT binary patch literal 16064 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 H^Y#A$@wSKa literal 11875 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

      _F}tGj3#M{ctnP3VEaDf3bIJssWVGr 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 literal 18123 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 W_3E`j3h)Co4^$Pkz?JgQu>T7h@(MQq diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java index e434bd58..9242bd11 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java @@ -7,7 +7,6 @@ import com.alibaba.otter.canal.sink.CanalEventSink; import com.alibaba.otter.canal.sink.entry.EntryEventSink; import com.google.common.base.Preconditions; import io.prometheus.client.Collector; -import io.prometheus.client.Counter; import io.prometheus.client.CounterMetricFamily; import io.prometheus.client.GaugeMetricFamily; import org.slf4j.Logger; @@ -30,12 +29,8 @@ public class EntryCollector extends Collector implements InstanceRegistry { private static final Logger logger = LoggerFactory.getLogger(SinkCollector.class); private static final String DELAY = "canal_instance_traffic_delay"; private static final String TRANSACTION = "canal_instance_transactions"; - private static final String ROW_EVENTS = "canal_instance_row_events"; - private static final String ROWS_COUNTER = "canal_instance_rows_counter"; private static final String DELAY_HELP = "Traffic delay of canal instance in milliseconds"; private static final String TRANSACTION_HELP = "Transactions counter of canal instance"; - private static final String ROW_EVENTS_HELP = "Rowdata events counter of canal instance"; - private static final String ROWS_COUNTER_HELP = "Rows counter of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); private EntryCollector() {} @@ -55,10 +50,6 @@ public class EntryCollector extends Collector implements InstanceRegistry { DELAY_HELP, DEST_LABELS_LIST); CounterMetricFamily transactions = new CounterMetricFamily(TRANSACTION, TRANSACTION_HELP, DEST_LABELS_LIST); - CounterMetricFamily rowEvents = new CounterMetricFamily(ROW_EVENTS, - ROW_EVENTS_HELP, DEST_LABELS_LIST); - CounterMetricFamily rowsCounter = new CounterMetricFamily(ROWS_COUNTER, - ROWS_COUNTER_HELP, DEST_LABELS_LIST); for (EntryMetricsHolder emh : instances.values()) { long now = System.currentTimeMillis(); long latest = emh.latestExecTime.get(); @@ -66,13 +57,9 @@ public class EntryCollector extends Collector implements InstanceRegistry { delay.addMetric(emh.destLabelValues, (now - latest)); } transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue()); - rowEvents.addMetric(emh.destLabelValues, emh.rowEventCounter.doubleValue()); - rowsCounter.addMetric(emh.destLabelValues, emh.rowsCounter.doubleValue()); } mfs.add(delay); mfs.add(transactions); - mfs.add(rowEvents); - mfs.add(rowsCounter); return mfs; } @@ -89,12 +76,8 @@ public class EntryCollector extends Collector implements InstanceRegistry { PrometheusCanalEventDownStreamHandler handler = assembleHandler(entrySink); holder.latestExecTime = handler.getLatestExecuteTime(); holder.transactionCounter = handler.getTransactionCounter(); - holder.rowEventCounter = handler.getRowEventCounter(); - holder.rowsCounter = handler.getRowsCounter(); Preconditions.checkNotNull(holder.latestExecTime); Preconditions.checkNotNull(holder.transactionCounter); - Preconditions.checkNotNull(holder.rowEventCounter); - Preconditions.checkNotNull(holder.rowsCounter); EntryMetricsHolder old = instances.put(destination, holder); if (old != null) { logger.warn("Remove stale EntryCollector for instance {}.", destination); @@ -145,8 +128,6 @@ public class EntryCollector extends Collector implements InstanceRegistry { private class EntryMetricsHolder { private AtomicLong latestExecTime; private AtomicLong transactionCounter; - private AtomicLong rowEventCounter; - private AtomicLong rowsCounter; private List destLabelValues; } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java index c3c71102..0bfacffe 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusCanalEventDownStreamHandler.java @@ -15,8 +15,6 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow private final AtomicLong latestExecuteTime = new AtomicLong(System.currentTimeMillis()); private final AtomicLong transactionCounter = new AtomicLong(0L); - private final AtomicLong rowEventCounter = new AtomicLong(0L); - private final AtomicLong rowsCounter = new AtomicLong(0L); @Override public List before(List events) { @@ -34,8 +32,6 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow case ROWDATA: { long exec = e.getExecuteTime(); if (exec > 0) localExecTime = exec; - rowEventCounter.incrementAndGet(); - rowsCounter.addAndGet(e.getRowsCount()); break; } case TRANSACTIONEND: { @@ -80,11 +76,4 @@ public class PrometheusCanalEventDownStreamHandler extends AbstractCanalEventDow return transactionCounter; } - public AtomicLong getRowsCounter() { - return rowsCounter; - } - - public AtomicLong getRowEventCounter() { - return rowEventCounter; - } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java index da16f2db..afc96405 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/PrometheusClientInstanceProfiler.java @@ -71,7 +71,6 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler String destination = result.getDestination(); PacketType type = result.getType(); outboundCounter.labels(destination).inc(result.getAmount()); - packetsCounter.labels(destination, type.name()).inc(); short errorCode = result.getErrorCode(); if (errorCode > 0) { errorsCounter.labels(destination, Short.toString(errorCode)).inc(); @@ -81,12 +80,16 @@ public class PrometheusClientInstanceProfiler implements ClientInstanceProfiler switch (type) { case GET: boolean empty = result.getEmpty(); + // 区分一下空包 if (empty) { emptyBatchesCounter.labels(destination).inc(); + } else { + packetsCounter.labels(destination, type.name()).inc(); } break; // reserve for others default: + packetsCounter.labels(destination, type.name()).inc(); break; } } diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index d345602c..40186dc0 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -34,11 +34,23 @@ public class StoreCollector extends Collector implements InstanceRegistry { private static final String STORE = "canal_instance_store"; private static final String PRODUCE_MEM = "canal_instance_store_produce_mem"; private static final String CONSUME_MEM = "canal_instance_store_consume_mem"; + private static final String PUT_DELAY = "canal_instance_put_delay"; + private static final String GET_DELAY = "canal_instance_get_delay"; + private static final String ACK_DELAY = "canal_instance_ack_delay"; + private static final String PUT_ROWS = "canal_instance_put_rows"; + private static final String GET_ROWS = "canal_instance_get_rows"; + private static final String ACK_ROWS = "canal_instance_ack_rows"; private static final String PRODUCE_HELP = "Produced events counter of canal instance"; private static final String CONSUME_HELP = "Consumed events counter of canal instance"; private static final String STORE_HELP = "Canal instance info"; private static final String PRODUCE_MEM_HELP = "Produced mem bytes of canal instance"; private static final String CONSUME_MEM_HELP = "Consumed mem bytes of canal instance"; + private static final String PUT_DELAY_HELP = "Traffic delay of canal instance put"; + private static final String GET_DELAY_HELP = "Traffic delay of canal instance get"; + private static final String ACK_DELAY_HELP = "Traffic delay of canal instance ack"; + private static final String PUT_ROWS_HELP = "Put table rows of canal instance"; + private static final String GET_ROWS_HELP = "Got table rows of canal instance"; + private static final String ACK_ROWS_HELP = "Acked table rows of canal instance"; private final ConcurrentMap instances = new ConcurrentHashMap(); private final List storeLabelsList = Arrays.asList(DEST, "batchMode", "size"); @@ -65,11 +77,40 @@ public class StoreCollector extends Collector implements InstanceRegistry { PRODUCE_MEM_HELP, DEST_LABELS_LIST); CounterMetricFamily ackMem = new CounterMetricFamily(CONSUME_MEM, CONSUME_MEM_HELP, DEST_LABELS_LIST); + GaugeMetricFamily putDelay = new GaugeMetricFamily(PUT_DELAY, + PUT_DELAY_HELP, DEST_LABELS_LIST); + GaugeMetricFamily getDelay = new GaugeMetricFamily(GET_DELAY, + GET_DELAY_HELP, DEST_LABELS_LIST); + GaugeMetricFamily ackDelay = new GaugeMetricFamily(ACK_DELAY, + ACK_DELAY_HELP, DEST_LABELS_LIST); + CounterMetricFamily putRows = new CounterMetricFamily(PUT_ROWS, + PUT_ROWS_HELP, DEST_LABELS_LIST); + CounterMetricFamily getRows = new CounterMetricFamily(GET_ROWS, + GET_ROWS_HELP, DEST_LABELS_LIST); + CounterMetricFamily ackRows = new CounterMetricFamily(ACK_ROWS, + ACK_ROWS_HELP, DEST_LABELS_LIST); boolean hasMem = false; for (StoreMetricsHolder smh : instances.values()) { final boolean isMem = smh.batchMode.isMemSize(); put.addMetric(smh.destLabelValues, smh.putSeq.doubleValue()); ack.addMetric(smh.destLabelValues, smh.ackSeq.doubleValue()); + long pet = smh.putExecTime.get(); + // 防止出现启动时,未消费造成的get, ack延时小于前阶段的情况 + long get = Math.min(smh.getExecTime.get(), pet); + long aet = Math.min(smh.ackExecTime.get(), get); + long now = System.currentTimeMillis(); + if (now >= pet) { + putDelay.addMetric(smh.destLabelValues, (now - pet)); + } + if (now >= get) { + getDelay.addMetric(smh.destLabelValues, (now - get)); + } + if (now >= aet) { + ackDelay.addMetric(smh.destLabelValues, (now - aet)); + } + putRows.addMetric(smh.destLabelValues, smh.putTableRows.doubleValue()); + getRows.addMetric(smh.destLabelValues, smh.getTableRows.doubleValue()); + ackRows.addMetric(smh.destLabelValues, smh.ackTableRows.doubleValue()); store.addMetric(smh.storeLabelValues, 1); if (isMem) { hasMem = true; @@ -80,6 +121,12 @@ public class StoreCollector extends Collector implements InstanceRegistry { mfs.add(put); mfs.add(ack); mfs.add(store); + mfs.add(putDelay); + mfs.add(getDelay); + mfs.add(ackDelay); + mfs.add(putRows); + mfs.add(getRows); + mfs.add(ackRows); if (hasMem) { mfs.add(putMem); mfs.add(ackMem); @@ -102,6 +149,12 @@ public class StoreCollector extends Collector implements InstanceRegistry { holder.destLabelValues = Collections.singletonList(destination); holder.size = memStore.getBufferSize(); holder.storeLabelValues = Arrays.asList(destination, holder.batchMode.name(), Integer.toString(holder.size)); + holder.putExecTime = memStore.getPutExecTime(); + holder.getExecTime = memStore.getGetExecTime(); + holder.ackExecTime = memStore.getAckExecTime(); + holder.putTableRows = memStore.getPutTableRows(); + holder.getTableRows = memStore.getGetTableRows(); + holder.ackTableRows = memStore.getAckTableRows(); Preconditions.checkNotNull(holder.batchMode); Preconditions.checkNotNull(holder.putSeq); Preconditions.checkNotNull(holder.ackSeq); @@ -129,6 +182,12 @@ public class StoreCollector extends Collector implements InstanceRegistry { private BatchMode batchMode; private AtomicLong putMemSize; private AtomicLong ackMemSize; + private AtomicLong putExecTime; + private AtomicLong getExecTime; + private AtomicLong ackExecTime; + private AtomicLong putTableRows; + private AtomicLong getTableRows; + private AtomicLong ackTableRows; private int size; private List destLabelValues; private List storeLabelValues; diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java index a98e7b8b..fe8c5538 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java @@ -220,6 +220,7 @@ public class SessionHandler extends SimpleChannelHandler { } else { clientIdentity = new ClientIdentity(ack.getDestination(), Short.valueOf(ack.getClientId())); embeddedServer.ack(clientIdentity, ack.getBatchId()); + new ChannelFutureAggregator(ack.getDestination(), ack, packet.getType(), 0, System.nanoTime() - start).operationComplete(null); } } else { byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", ack.toString()).getMessage()); @@ -239,6 +240,7 @@ public class SessionHandler extends SimpleChannelHandler { } else { embeddedServer.rollback(clientIdentity, rollback.getBatchId()); // 只回滚单个批次 } + new ChannelFutureAggregator(rollback.getDestination(), rollback, packet.getType(), 0, System.nanoTime() - start).operationComplete(null); } else { byte[] errorBytes = NettyUtils.errorPacket(401, MessageFormatter.format("destination or clientId is null", rollback.toString()).getMessage()); NettyUtils.write(ctx.getChannel(), errorBytes, new ChannelFutureAggregator(rollback.getDestination(), diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index 71051db0..923001d1 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -43,7 +43,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { @Override public void operationComplete(ChannelFuture future) { // profiling after I/O operation - if (future.getCause() != null) { + if (future != null && future.getCause() != null) { result.channelError = future.getCause(); } profiler().profiling(result); diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index a1ff4033..fb74d5f8 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -51,6 +51,16 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple private AtomicLong getMemSize = new AtomicLong(0); private AtomicLong ackMemSize = new AtomicLong(0); + // 记录下put/get/ack操作的三个execTime + private AtomicLong putExecTime = new AtomicLong(System.currentTimeMillis()); + private AtomicLong getExecTime = new AtomicLong(System.currentTimeMillis()); + private AtomicLong ackExecTime = new AtomicLong(System.currentTimeMillis()); + + // 记录下put/get/ack操作的三个table rows + private AtomicLong putTableRows = new AtomicLong(0); + private AtomicLong getTableRows = new AtomicLong(0); + private AtomicLong ackTableRows = new AtomicLong(0); + // 阻塞put/get操作控制信号 private ReentrantLock lock = new ReentrantLock(); private Condition notFull = lock.newCondition(); @@ -192,7 +202,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple putMemSize.getAndAdd(size); } - + profiling(data, OP.PUT); // tell other threads that store is not empty notEmpty.signal(); } @@ -336,6 +346,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple if (getSequence.compareAndSet(current, end)) { getMemSize.addAndGet(memsize); notFull.signal(); + profiling(result.getEvents(), OP.GET); return result; } else { return new Events(); @@ -407,8 +418,15 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple boolean hasMatch = false; long memsize = 0; + // ack没有list,但有已存在的foreach,还是节省一下list的开销 + long localExecTime = 0L; + int deltaRows = 0; for (long next = sequence + 1; next <= maxSequence; next++) { Event event = entries[getIndex(next)]; + if (localExecTime == 0 && event.getExecuteTime() > 0) { + localExecTime = event.getExecuteTime(); + } + deltaRows += event.getRowsCount(); memsize += calculateSize(event); boolean match = CanalEventUtils.checkPosition(event, (LogPosition) position); if (match) {// 找到对应的position,更新ack seq @@ -424,11 +442,14 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple if (ackSequence.compareAndSet(sequence, next)) {// 避免并发ack notFull.signal(); + ackTableRows.addAndGet(deltaRows); + if (localExecTime > 0) { + ackExecTime.lazySet(localExecTime); + } return; } } } - if (!hasMatch) {// 找不到对应需要ack的position throw new CanalStoreException("no match ack position" + position.toString()); } @@ -544,6 +565,45 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple || type == EventType.DINDEX; } + private void profiling(List events, OP op) { + long localExecTime = 0L; + int deltaRows = 0; + if (events != null && !events.isEmpty()) { + for (Event e : events) { + if (localExecTime == 0 && e.getExecuteTime() > 0) { + localExecTime = e.getExecuteTime(); + } + deltaRows += e.getRowsCount(); + } + } + switch (op) { + case PUT: + putTableRows.addAndGet(deltaRows); + if (localExecTime > 0) { + putExecTime.lazySet(localExecTime); + } + break; + case GET: + getTableRows.addAndGet(deltaRows); + if (localExecTime > 0) { + getExecTime.lazySet(localExecTime); + } + break; + case ACK: + ackTableRows.addAndGet(deltaRows); + if (localExecTime > 0) { + ackExecTime.lazySet(localExecTime); + } + break; + default: + break; + } + } + + private enum OP { + PUT, GET, ACK + } + // ================ setter / getter ================== public int getBufferSize() { return this.bufferSize; @@ -584,4 +644,28 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple public BatchMode getBatchMode() { return batchMode; } + + public AtomicLong getPutExecTime() { + return putExecTime; + } + + public AtomicLong getGetExecTime() { + return getExecTime; + } + + public AtomicLong getAckExecTime() { + return ackExecTime; + } + + public AtomicLong getPutTableRows() { + return putTableRows; + } + + public AtomicLong getGetTableRows() { + return getTableRows; + } + + public AtomicLong getAckTableRows() { + return ackTableRows; + } } From fbb3b814c09124e5f300668aca77468e1ca074d9 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sun, 19 Aug 2018 17:40:53 +0800 Subject: [PATCH 096/360] Improve image. --- images/idle.PNG | Bin 10031 -> 13884 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/idle.PNG b/images/idle.PNG index 7a0570083f6d6069200967e08dbdb79922d14b03..a2d3a9ffc9b6e8d9344d029090d75fc6b6f3f42f 100644 GIT binary patch literal 13884 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 Nw79%jnF!eLe*gsts5k%s literal 10031 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 From 5588da59b900687e786975a38ae1d9d01b28137b Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Sun, 19 Aug 2018 18:19:18 +0800 Subject: [PATCH 097/360] Update response time image. --- images/latency.PNG | Bin 13795 -> 15130 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/images/latency.PNG b/images/latency.PNG index ca531caa9df5590d6293910dec33d669dcfee78e..be701be9298bd7f82e0e8126f13aa10a504e8c5e 100644 GIT binary patch literal 15130 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 literal 13795 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 From 02757693b42e8553ce634ee5d9fe49c43cb169d1 Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Mon, 20 Aug 2018 08:02:20 +0800 Subject: [PATCH 098/360] Remove TPS(events) in tmpl. --- .../metrics/Canal_instances_tmpl.json | 138 ++++-------------- 1 file changed, 25 insertions(+), 113 deletions(-) diff --git a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json index ad7af619..8f5ef355 100644 --- a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json +++ b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json @@ -46,7 +46,7 @@ "gnetId": null, "graphTooltip": 0, "id": null, - "iteration": 1534648729090, + "iteration": 1534723196501, "links": [], "panels": [ { @@ -500,13 +500,14 @@ }, { "aliasColors": { - "TPS": "#64b0c8" + "rowDatas": "#7eb26d", + "tableRows": "#c15c17" }, "bars": false, "dashLength": 10, "dashes": false, "datasource": "$datasource", - "description": "Canal client instance消费binlog的TPS(ack过的events), 以binlog events为单位计算。", + "description": "Instance处理binlog的TPS(以master变更行数table rows为基准计算)。\nput: put操作TPS。\nget: get操作TPS。\nack: ack操作TPS。", "fill": 1, "gridPos": { "h": 5, @@ -514,7 +515,7 @@ "x": 0, "y": 7 }, - "id": 8, + "id": 14, "legend": { "avg": false, "current": false, @@ -538,18 +539,33 @@ "steppedLine": false, "targets": [ { - "expr": "rate(canal_instance_store_consume_seq{destination=~\"$destination\"}[2m])", + "expr": "rate(canal_instance_put_rows{destination=~\"$destination\"}[2m])", "format": "time_series", "interval": "15s", "intervalFactor": 2, - "legendFormat": "events", + "legendFormat": "put", "refId": "A" + }, + { + "expr": "rate(canal_instance_get_rows{destination=~\"$destination\"}[2m])", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "get", + "refId": "B" + }, + { + "expr": "rate(canal_instance_ack_rows{destination=~\"$destination\"}[2m])", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "ack", + "refId": "C" } ], "thresholds": [], "timeFrom": null, "timeShift": null, - "title": "TPS(events)", + "title": "TPS(table rows)", "tooltip": { "shared": true, "sort": 0, @@ -674,110 +690,6 @@ "alignLevel": null } }, - { - "aliasColors": { - "rowDatas": "#7eb26d", - "tableRows": "#c15c17" - }, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "description": "Instance处理binlog的TPS(以master变更行数table rows为基准计算)。\nput: put操作TPS。\nget: get操作TPS。\nack: ack操作TPS。", - "fill": 1, - "gridPos": { - "h": 5, - "w": 6, - "x": 12, - "y": 7 - }, - "id": 14, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "rate(canal_instance_put_rows{destination=~\"$destination\"}[2m])", - "format": "time_series", - "interval": "15s", - "intervalFactor": 2, - "legendFormat": "put", - "refId": "A" - }, - { - "expr": "rate(canal_instance_get_rows{destination=~\"$destination\"}[2m])", - "format": "time_series", - "interval": "15s", - "intervalFactor": 2, - "legendFormat": "get", - "refId": "B" - }, - { - "expr": "rate(canal_instance_ack_rows{destination=~\"$destination\"}[2m])", - "format": "time_series", - "intervalFactor": 1, - "legendFormat": "ack", - "refId": "C" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "TPS(table rows)", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "iops", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, { "collapsed": false, "gridPos": { @@ -1396,7 +1308,7 @@ ] }, "time": { - "from": "now-15m", + "from": "now-6h", "to": "now" }, "timepicker": { @@ -1427,5 +1339,5 @@ "timezone": "", "title": "Canal instances", "uid": "8vh8NGpiz", - "version": 96 + "version": 97 } \ No newline at end of file From cb884094d58319e9331d17db7be603022e2a45d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 20 Aug 2018 13:01:22 +0800 Subject: [PATCH 099/360] fixed metrics --- deployer/src/main/resources/logback.xml | 2 +- .../metrics/Canal_instances_tmpl.json | 413 ++++++++---------- 2 files changed, 188 insertions(+), 227 deletions(-) diff --git a/deployer/src/main/resources/logback.xml b/deployer/src/main/resources/logback.xml index f1c0e93b..d27a1d78 100644 --- a/deployer/src/main/resources/logback.xml +++ b/deployer/src/main/resources/logback.xml @@ -79,7 +79,7 @@ - + \ No newline at end of file diff --git a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json index 8f5ef355..f9fde344 100644 --- a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json +++ b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json @@ -1,34 +1,4 @@ { - "__inputs": [ - { - "name": "DS_PROMETHEUS", - "label": "prometheus", - "description": "", - "type": "datasource", - "pluginId": "prometheus", - "pluginName": "Prometheus" - } - ], - "__requires": [ - { - "type": "grafana", - "id": "grafana", - "name": "Grafana", - "version": "5.2.2" - }, - { - "type": "panel", - "id": "graph", - "name": "Graph", - "version": "5.0.0" - }, - { - "type": "datasource", - "id": "prometheus", - "name": "Prometheus", - "version": "5.0.0" - } - ], "annotations": { "list": [ { @@ -45,8 +15,8 @@ "editable": true, "gnetId": null, "graphTooltip": 0, - "id": null, - "iteration": 1534723196501, + "id": 8, + "iteration": 1534740332233, "links": [], "panels": [ { @@ -495,7 +465,7 @@ }, "id": 32, "panels": [], - "title": "Throughput", + "title": "Server", "type": "row" }, { @@ -690,6 +660,180 @@ "alignLevel": null } }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance ringbuffer内未释放的events数量。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 7 + }, + "id": 20, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "canal_instance_store_produce_seq{destination=~\"$destination\"} - canal_instance_store_consume_seq{destination=~\"$destination\"}", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "events", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Store remain events", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "none", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "decimals": null, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "$datasource", + "description": "Canal instance ringbuffer 内未释放events占用内存。", + "fill": 1, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 7 + }, + "id": 22, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "(canal_instance_store_produce_mem{destination=~\"$destination\"} - canal_instance_store_consume_mem{destination=~\"$destination\"}) / 1024", + "format": "time_series", + "interval": "15s", + "intervalFactor": 2, + "legendFormat": "memsize", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeShift": null, + "title": "Store remain mem", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "deckbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, { "collapsed": false, "gridPos": { @@ -794,7 +938,7 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "${DS_PROMETHEUS}", + "datasource": "Prometheus", "description": "client 请求的GET与ACK包的QPS。", "fill": 1, "gridPos": { @@ -1074,193 +1218,6 @@ "align": false, "alignLevel": null } - }, - { - "collapsed": false, - "gridPos": { - "h": 1, - "w": 24, - "x": 0, - "y": 18 - }, - "id": 36, - "panels": [], - "title": "Store", - "type": "row" - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "description": "Canal instance ringbuffer内未释放的events数量。", - "fill": 1, - "gridPos": { - "h": 5, - "w": 6, - "x": 0, - "y": 19 - }, - "id": 20, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "canal_instance_store_produce_seq{destination=~\"$destination\"} - canal_instance_store_consume_seq{destination=~\"$destination\"}", - "format": "time_series", - "interval": "15s", - "intervalFactor": 2, - "legendFormat": "events", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Store remain events", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "decimals": null, - "format": "none", - "label": "", - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "decimals": null, - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } - }, - { - "aliasColors": {}, - "bars": false, - "dashLength": 10, - "dashes": false, - "datasource": "$datasource", - "description": "Canal instance ringbuffer 内未释放events占用内存。", - "fill": 1, - "gridPos": { - "h": 5, - "w": 6, - "x": 6, - "y": 19 - }, - "id": 22, - "legend": { - "avg": false, - "current": false, - "max": false, - "min": false, - "show": true, - "total": false, - "values": false - }, - "lines": true, - "linewidth": 1, - "links": [], - "nullPointMode": "null", - "percentage": false, - "pointradius": 5, - "points": false, - "renderer": "flot", - "seriesOverrides": [], - "spaceLength": 10, - "stack": false, - "steppedLine": false, - "targets": [ - { - "expr": "(canal_instance_store_produce_mem{destination=~\"$destination\"} - canal_instance_store_consume_mem{destination=~\"$destination\"}) / 1024", - "format": "time_series", - "interval": "15s", - "intervalFactor": 2, - "legendFormat": "memsize", - "refId": "A" - } - ], - "thresholds": [], - "timeFrom": null, - "timeShift": null, - "title": "Store remain mem", - "tooltip": { - "shared": true, - "sort": 0, - "value_type": "individual" - }, - "type": "graph", - "xaxis": { - "buckets": null, - "mode": "time", - "name": null, - "show": true, - "values": [] - }, - "yaxes": [ - { - "format": "deckbytes", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - }, - { - "format": "short", - "label": null, - "logBase": 1, - "max": null, - "min": null, - "show": true - } - ], - "yaxis": { - "align": false, - "alignLevel": null - } } ], "refresh": false, @@ -1273,8 +1230,9 @@ "list": [ { "current": { - "text": "prometheus", - "value": "prometheus" + "tags": [], + "text": "Prometheus", + "value": "Prometheus" }, "hide": 0, "label": "datasource", @@ -1287,7 +1245,10 @@ }, { "allValue": null, - "current": {}, + "current": { + "text": "example", + "value": "example" + }, "datasource": "$datasource", "hide": 0, "includeAll": false, @@ -1308,7 +1269,7 @@ ] }, "time": { - "from": "now-6h", + "from": "now-1h", "to": "now" }, "timepicker": { @@ -1339,5 +1300,5 @@ "timezone": "", "title": "Canal instances", "uid": "8vh8NGpiz", - "version": 97 + "version": 1 } \ No newline at end of file From dd8b1ce9551b59b719516615e43193c467214ade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 20 Aug 2018 13:28:57 +0800 Subject: [PATCH 100/360] [maven-release-plugin] prepare release canal-1.1.0 --- client/pom.xml | 2 +- common/pom.xml | 2 +- dbsync/pom.xml | 2 +- deployer/pom.xml | 2 +- driver/pom.xml | 2 +- example/pom.xml | 2 +- filter/pom.xml | 2 +- instance/core/pom.xml | 2 +- instance/manager/pom.xml | 2 +- instance/pom.xml | 2 +- instance/spring/pom.xml | 2 +- kafka-client/pom.xml | 6 ++---- kafka/pom.xml | 6 ++---- meta/pom.xml | 2 +- parse/pom.xml | 2 +- pom.xml | 2 +- prometheus/pom.xml | 8 +++----- protocol/pom.xml | 2 +- server/pom.xml | 2 +- sink/pom.xml | 2 +- store/pom.xml | 2 +- 21 files changed, 25 insertions(+), 31 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index 038f2a9f..11c42b8b 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/common/pom.xml b/common/pom.xml index afa3372c..01a57648 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml canal.common diff --git a/dbsync/pom.xml b/dbsync/pom.xml index 17ec6c30..d0c4b03f 100644 --- a/dbsync/pom.xml +++ b/dbsync/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/deployer/pom.xml b/deployer/pom.xml index 2de1ce1e..7c1a23e5 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/driver/pom.xml b/driver/pom.xml index 648e64cc..c1053f7c 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/example/pom.xml b/example/pom.xml index 4f13d017..c9818061 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/filter/pom.xml b/filter/pom.xml index f662f5ea..740b7c28 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/instance/core/pom.xml b/instance/core/pom.xml index e3812d4e..f148acf2 100644 --- a/instance/core/pom.xml +++ b/instance/core/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../../pom.xml canal.instance.core diff --git a/instance/manager/pom.xml b/instance/manager/pom.xml index 79340bd0..34ca4d2c 100644 --- a/instance/manager/pom.xml +++ b/instance/manager/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../../pom.xml com.alibaba.otter diff --git a/instance/pom.xml b/instance/pom.xml index f45baee1..4b7a5422 100644 --- a/instance/pom.xml +++ b/instance/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/instance/spring/pom.xml b/instance/spring/pom.xml index 116dee77..097aac3c 100644 --- a/instance/spring/pom.xml +++ b/instance/spring/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../../pom.xml com.alibaba.otter diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml index 42424ddd..63569cb7 100644 --- a/kafka-client/pom.xml +++ b/kafka-client/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 canal com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/kafka/pom.xml b/kafka/pom.xml index b2587c02..d0208979 100644 --- a/kafka/pom.xml +++ b/kafka/pom.xml @@ -1,12 +1,10 @@ - + 4.0.0 canal com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/meta/pom.xml b/meta/pom.xml index 93c4ad7b..888a1e85 100644 --- a/meta/pom.xml +++ b/meta/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/parse/pom.xml b/parse/pom.xml index fb93fc99..f01b8de6 100644 --- a/parse/pom.xml +++ b/parse/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml canal.parse diff --git a/pom.xml b/pom.xml index 3101aca6..a837419d 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ canal pom canal module for otter ${project.version} - 1.1.0-SNAPSHOT + 1.1.0 https://github.com/alibaba/canal org.sonatype.oss diff --git a/prometheus/pom.xml b/prometheus/pom.xml index 7d192834..bf0e3511 100644 --- a/prometheus/pom.xml +++ b/prometheus/pom.xml @@ -1,17 +1,15 @@ - + canal com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.0 4.0.0 com.alibaba.otter canal.prometheus - 1.1.0-SNAPSHOT + 1.1.0 canal prometheus module for otter ${project.version} diff --git a/protocol/pom.xml b/protocol/pom.xml index 94715d15..d8c9b779 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/server/pom.xml b/server/pom.xml index b7f83d56..3c6c7ffa 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml canal.server diff --git a/sink/pom.xml b/sink/pom.xml index 9f136d35..607b3fd3 100644 --- a/sink/pom.xml +++ b/sink/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter diff --git a/store/pom.xml b/store/pom.xml index 9dee6737..64345dfe 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0-SNAPSHOT + 1.1.0 ../pom.xml com.alibaba.otter From 9bde69b6d6f9b3997d14446f1581924365139c0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 20 Aug 2018 13:29:12 +0800 Subject: [PATCH 101/360] [maven-release-plugin] prepare for next development iteration --- client/pom.xml | 2 +- common/pom.xml | 2 +- dbsync/pom.xml | 2 +- deployer/pom.xml | 2 +- driver/pom.xml | 2 +- example/pom.xml | 2 +- filter/pom.xml | 2 +- instance/core/pom.xml | 2 +- instance/manager/pom.xml | 2 +- instance/pom.xml | 2 +- instance/spring/pom.xml | 2 +- kafka-client/pom.xml | 2 +- kafka/pom.xml | 2 +- meta/pom.xml | 2 +- parse/pom.xml | 2 +- pom.xml | 2 +- prometheus/pom.xml | 4 ++-- protocol/pom.xml | 2 +- server/pom.xml | 2 +- sink/pom.xml | 2 +- store/pom.xml | 2 +- 21 files changed, 22 insertions(+), 22 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index 11c42b8b..55955544 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/common/pom.xml b/common/pom.xml index 01a57648..1f2ece8b 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml canal.common diff --git a/dbsync/pom.xml b/dbsync/pom.xml index d0c4b03f..f4d20a10 100644 --- a/dbsync/pom.xml +++ b/dbsync/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/deployer/pom.xml b/deployer/pom.xml index 7c1a23e5..a5229bc3 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/driver/pom.xml b/driver/pom.xml index c1053f7c..692475db 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/example/pom.xml b/example/pom.xml index c9818061..679bfb1d 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/filter/pom.xml b/filter/pom.xml index 740b7c28..95c77f8f 100644 --- a/filter/pom.xml +++ b/filter/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/instance/core/pom.xml b/instance/core/pom.xml index f148acf2..1a73fc8a 100644 --- a/instance/core/pom.xml +++ b/instance/core/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../../pom.xml canal.instance.core diff --git a/instance/manager/pom.xml b/instance/manager/pom.xml index 34ca4d2c..ec85fa2c 100644 --- a/instance/manager/pom.xml +++ b/instance/manager/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../../pom.xml com.alibaba.otter diff --git a/instance/pom.xml b/instance/pom.xml index 4b7a5422..a73b43fe 100644 --- a/instance/pom.xml +++ b/instance/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/instance/spring/pom.xml b/instance/spring/pom.xml index 097aac3c..929fc273 100644 --- a/instance/spring/pom.xml +++ b/instance/spring/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../../pom.xml com.alibaba.otter diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml index 63569cb7..9767b16e 100644 --- a/kafka-client/pom.xml +++ b/kafka-client/pom.xml @@ -4,7 +4,7 @@ canal com.alibaba.otter - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/kafka/pom.xml b/kafka/pom.xml index d0208979..73a89999 100644 --- a/kafka/pom.xml +++ b/kafka/pom.xml @@ -4,7 +4,7 @@ canal com.alibaba.otter - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/meta/pom.xml b/meta/pom.xml index 888a1e85..98788116 100644 --- a/meta/pom.xml +++ b/meta/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/parse/pom.xml b/parse/pom.xml index f01b8de6..8e47215b 100644 --- a/parse/pom.xml +++ b/parse/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml canal.parse diff --git a/pom.xml b/pom.xml index a837419d..66b69ca2 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ canal pom canal module for otter ${project.version} - 1.1.0 + 1.1.1-SNAPSHOT https://github.com/alibaba/canal org.sonatype.oss diff --git a/prometheus/pom.xml b/prometheus/pom.xml index bf0e3511..29bf8c1f 100644 --- a/prometheus/pom.xml +++ b/prometheus/pom.xml @@ -3,13 +3,13 @@ canal com.alibaba.otter - 1.1.0 + 1.1.1-SNAPSHOT 4.0.0 com.alibaba.otter canal.prometheus - 1.1.0 + 1.1.1-SNAPSHOT canal prometheus module for otter ${project.version} diff --git a/protocol/pom.xml b/protocol/pom.xml index d8c9b779..f8211e56 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/server/pom.xml b/server/pom.xml index 3c6c7ffa..a11cc4f3 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml canal.server diff --git a/sink/pom.xml b/sink/pom.xml index 607b3fd3..1a1fef1e 100644 --- a/sink/pom.xml +++ b/sink/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter diff --git a/store/pom.xml b/store/pom.xml index 64345dfe..2fb122d0 100644 --- a/store/pom.xml +++ b/store/pom.xml @@ -3,7 +3,7 @@ com.alibaba.otter canal - 1.1.0 + 1.1.1-SNAPSHOT ../pom.xml com.alibaba.otter From 0f3c427e2820acd51b63f10533c76edc53530b3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 20 Aug 2018 14:28:12 +0800 Subject: [PATCH 102/360] fixed docker run --- docker/Dockerfile | 4 ++-- docker/run.sh | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 311ac0b5..44655560 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -24,8 +24,8 @@ RUN \ yum clean all && \ true -# 2222 sys , 8080 web , 8000 debug , 11111 canal -EXPOSE 2222 11111 8000 8080 +# 2222 sys , 8000 debug , 11111 canal , 11112 metrics +EXPOSE 2222 11111 8000 11112 WORKDIR /home/admin diff --git a/docker/run.sh b/docker/run.sh index 3d5f7659..28f32411 100644 --- a/docker/run.sh +++ b/docker/run.sh @@ -72,7 +72,7 @@ DATA="$BASE/data" mkdir -p $DATA CONFIG=${@:1} #VOLUMNS="-v $DATA:/home/admin/canal-server/logs" -PORTLIST="8000 8080 2222 11111" +PORTLIST="8000 2222 11111 11112" PORTS="" for PORT in $PORTLIST ; do #exist=`check_port $PORT` @@ -89,4 +89,4 @@ MEMORY="-m 4096m" LOCALHOST=`getMyIp` cmd="docker run -it -h $LOCALHOST $CONFIG --name=canal-server $VOLUMNS $NET_MODE $PORTS $MEMORY canal/canal-server" echo $cmd -eval $cmd \ No newline at end of file +eval $cmd From 47348e2e9612b3099b81b1d449a095de722c9fa0 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 20 Aug 2018 18:22:11 +0800 Subject: [PATCH 103/360] =?UTF-8?q?=E6=96=B0=E5=A2=9Eclient-launcher?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=EF=BC=8C=E5=A4=96=E9=83=A8=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E8=90=BD=E5=9C=B0=E9=80=82=E9=85=8D=E5=99=A8=E7=9A=84=E6=80=BB?= =?UTF-8?q?=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-launcher/pom.xml | 135 +++++ client-launcher/src/main/assembly/dev.xml | 57 ++ client-launcher/src/main/assembly/release.xml | 57 ++ client-launcher/src/main/bin/startup.bat | 25 + client-launcher/src/main/bin/startup.sh | 100 ++++ client-launcher/src/main/bin/stop.sh | 65 +++ .../canal/client/CanalAdapterLoader.java | 160 ++++++ .../otter/canal/client/ClientLauncher.java | 63 +++ .../adapter/AbstractCanalAdapterWorker.java | 156 ++++++ .../adapter/CanalAdapterKafkaWorker.java | 154 +++++ .../client/adapter/CanalAdapterWorker.java | 174 ++++++ .../client/adapter/CanalOuterAdapter.java | 33 ++ .../CanalOuterAdapterConfiguration.java | 51 ++ .../client/example/LoggerAdapterExample.java | 36 ++ .../client/support/CanalClientConfig.java | 177 ++++++ .../otter/canal/client/support/Dml.java | 115 ++++ .../canal/client/support/ExtensionLoader.java | 527 ++++++++++++++++++ .../canal/client/support/JdbcTypeUtil.java | 137 +++++ .../otter/canal/client/support/SPI.java | 11 + ...ter.canal.client.adapter.CanalOuterAdapter | 1 + .../src/main/resources/canal-client.yml | 22 + .../src/main/resources/logback.xml | 47 ++ pom.xml | 1 + 23 files changed, 2304 insertions(+) create mode 100644 client-launcher/pom.xml create mode 100644 client-launcher/src/main/assembly/dev.xml create mode 100644 client-launcher/src/main/assembly/release.xml create mode 100644 client-launcher/src/main/bin/startup.bat create mode 100644 client-launcher/src/main/bin/startup.sh create mode 100644 client-launcher/src/main/bin/stop.sh create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/CanalAdapterLoader.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterKafkaWorker.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterWorker.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapterConfiguration.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/support/CanalClientConfig.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/support/Dml.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/support/ExtensionLoader.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/support/JdbcTypeUtil.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/support/SPI.java create mode 100644 client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter create mode 100644 client-launcher/src/main/resources/canal-client.yml create mode 100644 client-launcher/src/main/resources/logback.xml diff --git a/client-launcher/pom.xml b/client-launcher/pom.xml new file mode 100644 index 00000000..42d605a7 --- /dev/null +++ b/client-launcher/pom.xml @@ -0,0 +1,135 @@ + + + + canal + com.alibaba.otter + 1.1.0-SNAPSHOT + + 4.0.0 + com.alibaba.otter + canal.client-launcher + jar + canal client launcher module for otter ${project.version} + + + com.alibaba.otter + canal.client + ${project.version} + + + + com.alibaba.otter + canal.kafka.client + ${project.version} + + + joda-time + joda-time + 2.9.4 + + + org.yaml + snakeyaml + 1.17 + + + + + + + + maven-jar-plugin + + + true + + + **/logback.xml + **/canal-client.yml + + + + + + org.apache.maven.plugins + maven-assembly-plugin + + 2.2.1 + + + assemble + + single + + package + + + + false + false + + + + + + + + dev + + true + + env + !release + + + + + + + maven-assembly-plugin + + + + ${basedir}/src/main/assembly/dev.xml + + canal_client + ${project.build.directory} + + + + + + + + + release + + + env + release + + + + + + + maven-assembly-plugin + + + + ${basedir}/src/main/assembly/release.xml + + + ${project.artifactId}-${project.version} + + ${project.parent.build.directory} + + + + + + + + \ No newline at end of file diff --git a/client-launcher/src/main/assembly/dev.xml b/client-launcher/src/main/assembly/dev.xml new file mode 100644 index 00000000..a3cbc20c --- /dev/null +++ b/client-launcher/src/main/assembly/dev.xml @@ -0,0 +1,57 @@ + + dist + + dir + + false + + + . + / + + README* + + + + ./src/main/bin + bin + + **/* + + 0755 + + + ./src/main/conf + /conf + + **/* + + + + ./src/main/resources + /conf + + **/* + + + META-INF/** + + + + target + logs + + **/* + + + + + + lib + + junit:junit + + + + diff --git a/client-launcher/src/main/assembly/release.xml b/client-launcher/src/main/assembly/release.xml new file mode 100644 index 00000000..9350da6e --- /dev/null +++ b/client-launcher/src/main/assembly/release.xml @@ -0,0 +1,57 @@ + + dist + + tar.gz + + false + + + . + / + + README* + + + + ./src/main/bin + bin + + **/* + + 0755 + + + ./src/main/conf + /conf + + **/* + + + + ./src/main/resources + /conf + + **/* + + + META-INF/** + + + + target + logs + + **/* + + + + + + lib + + junit:junit + + + + diff --git a/client-launcher/src/main/bin/startup.bat b/client-launcher/src/main/bin/startup.bat new file mode 100644 index 00000000..33fd7945 --- /dev/null +++ b/client-launcher/src/main/bin/startup.bat @@ -0,0 +1,25 @@ +@echo off +@if not "%ECHO%" == "" echo %ECHO% +@if "%OS%" == "Windows_NT" setlocal + +set ENV_PATH=.\ +if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% + +set conf_dir=%ENV_PATH%\..\conf +set canal_conf=%conf_dir%\canal-client.yml +set logback_configurationFile=%conf_dir%\logback.xml + +set CLASSPATH=%conf_dir% +set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% + +set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m +set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 +set JAVA_DEBUG_OPT= -server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9099,server=y,suspend=n +set CANAL_OPTS= -DappName=otter-canal -Dlogback.configurationFile="%logback_configurationFile%" -Dcanal.conf="%canal_conf%" + +set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS% + +set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher +echo start cmd : %CMD_STR% + +java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher \ No newline at end of file diff --git a/client-launcher/src/main/bin/startup.sh b/client-launcher/src/main/bin/startup.sh new file mode 100644 index 00000000..d467b4d2 --- /dev/null +++ b/client-launcher/src/main/bin/startup.sh @@ -0,0 +1,100 @@ +#!/bin/bash + +current_path=`pwd` +case "`uname`" in + Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + ;; + *) + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +base=${bin_abs_path}/.. +canal_conf=$base/conf/canal-client.yml +logback_configurationFile=$base/conf/logback.xml +export LANG=en_US.UTF-8 +export BASE=$base + +if [ -f $base/bin/canal_client.pid ] ; then + echo "found canal_client.pid , Please run stop.sh first ,then startup.sh" 2>&2 + exit 1 +fi + +## set java path +if [ -z "$JAVA" ] ; then + JAVA=$(which java) +fi + +ALIBABA_JAVA="/usr/alibaba/java/bin/java" +TAOBAO_JAVA="/opt/taobao/java/bin/java" +if [ -z "$JAVA" ]; then + if [ -f $ALIBABA_JAVA ] ; then + JAVA=$ALIBABA_JAVA + elif [ -f $TAOBAO_JAVA ] ; then + JAVA=$TAOBAO_JAVA + else + echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2 + exit 1 + fi +fi + +case "$#" +in +0 ) + ;; +1 ) + var=$* + if [ -f $var ] ; then + canal_conf=$var + else + echo "THE PARAMETER IS NOT CORRECT.PLEASE CHECK AGAIN." + exit + fi;; +2 ) + var=$1 + if [ -f $var ] ; then + canal_conf=$var + else + if [ "$1" = "debug" ]; then + DEBUG_PORT=$2 + DEBUG_SUSPEND="n" + JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND" + fi + fi;; +* ) + echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN." + exit;; +esac + +str=`file -L $JAVA | grep 64-bit` +if [ -n "$str" ]; then + JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError" +else + JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m " +fi + +JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" +CANAL_OPTS="-DappName=otter-canal -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf" + +if [ -e $canal_conf -a -e $logback_configurationFile ] +then + + for i in $base/lib/*; + do CLASSPATH=$i:"$CLASSPATH"; + done + CLASSPATH="$base/conf:$CLASSPATH"; + + echo "cd to $bin_abs_path for workaround relative path" + cd $bin_abs_path + + echo LOG CONFIGURATION : $logback_configurationFile + echo canal conf : $canal_conf + echo CLASSPATH :$CLASSPATH + $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.client.ClientLauncher 1>>$base/logs/canal_client.log 2>&1 & + echo $! > $base/bin/canal_client.pid + + echo "cd to $current_path for continue" + cd $current_path +else + echo "canal client conf("$canal_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!" +fi diff --git a/client-launcher/src/main/bin/stop.sh b/client-launcher/src/main/bin/stop.sh new file mode 100644 index 00000000..0cfcc712 --- /dev/null +++ b/client-launcher/src/main/bin/stop.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +cygwin=false; +linux=false; +case "`uname`" in + CYGWIN*) + cygwin=true + ;; + Linux*) + linux=true + ;; +esac + +get_pid() { + STR=$1 + PID=$2 + if $cygwin; then + JAVA_CMD="$JAVA_HOME\bin\java" + JAVA_CMD=`cygpath --path --unix $JAVA_CMD` + JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'` + else + if $linux; then + if [ ! -z "$PID" ]; then + JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` + else + JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'` + fi + else + if [ ! -z "$PID" ]; then + JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` + else + JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'` + fi + fi + fi + echo $JAVA_PID; +} + +base=`dirname $0`/.. +pidfile=$base/bin/canal_client.pid +if [ ! -f "$pidfile" ];then + echo "canal client is not running. exists" + exit +fi + +pid=`cat $pidfile` +if [ "$pid" == "" ] ; then + pid=`get_pid "appName=otter-canal"` +fi + +echo -e "`hostname`: stopping canal $pid ... " +kill $pid + +LOOPS=0 +while (true); +do + gpid=`get_pid "appName=otter-canal" "$pid"` + if [ "$gpid" == "" ] ; then + echo "Oook! cost:$LOOPS" + `rm $pidfile` + break; + fi + let LOOPS=LOOPS+1 + sleep 1 +done \ No newline at end of file diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/CanalAdapterLoader.java new file mode 100644 index 00000000..f60a830e --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/CanalAdapterLoader.java @@ -0,0 +1,160 @@ +package com.alibaba.otter.canal.client; + +import com.alibaba.otter.canal.client.adapter.CanalAdapterKafkaWorker; +import com.alibaba.otter.canal.client.adapter.CanalAdapterWorker; +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.support.CanalClientConfig; +import com.alibaba.otter.canal.client.support.ExtensionLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * 外部适配器的加载器 + * + * @author machengyuan 2018-8-19 下午11:45:49 + * @version 1.0.0 + */ +public class CanalAdapterLoader { + private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); + + private CanalClientConfig canalClientConfig; + + private Map canalWorkers = new HashMap<>(); + + private Map canalKafkaWorkers = new HashMap<>(); + + private ExtensionLoader loader; + + public CanalAdapterLoader(CanalClientConfig canalClientConfig) { + this.canalClientConfig = canalClientConfig; + } + + /** + * 初始化canal-client、 canal-client-kafka的适配器 + */ + public void init() { + // canal instances 和 kafka topics 配置不能同时为空 + if (canalClientConfig.getCanalInstances().isEmpty() && canalClientConfig.getKafkaTopics().isEmpty()) { + throw new RuntimeException("Blank config property: canalInstances or canalKafkaTopics"); + } + + loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class, "" /*TODO canalClientConfig.getClassloaderPolicy()*/); + + String canalServerHost = this.canalClientConfig.getCanalServerHost(); + SocketAddress sa = null; + if (canalServerHost != null) { + String[] ipPort = canalServerHost.split(":"); + sa = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1])); + } + String zkHosts = this.canalClientConfig.getZookeeperHosts(); + + if (zkHosts == null && sa == null) { + throw new RuntimeException("Blank config property: canalServerHost or zookeeperHosts"); + } + + // 初始化canal-client的适配器 + for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { + List> canalOuterAdapterGroups = new ArrayList<>(); + + for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { + List canalOutConnectors = new ArrayList<>(); + for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) { + loadConnector(c, canalOutConnectors); + } + canalOuterAdapterGroups.add(canalOutConnectors); + } + CanalAdapterWorker worker; + if (zkHosts != null) { + worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups); + } else { + worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups); + } + canalWorkers.put(instance.getInstance(), worker); + worker.start(); + logger.info("Start adapter for canal instance: {} succeed", instance.getInstance()); + } + + // 初始化canal-client-kafka的适配器 + for (CanalClientConfig.KafkaTopic kafkaTopic : canalClientConfig.getKafkaTopics()) { + for (CanalClientConfig.Group group : kafkaTopic.getGroups()) { + List> canalOuterAdapterGroups = new ArrayList<>(); + + List canalOuterAdapters = new ArrayList<>(); + + for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { +// for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) { + loadConnector(config, canalOuterAdapters); +// } + } + canalOuterAdapterGroups.add(canalOuterAdapters); + + String zkServers = canalClientConfig.getZookeeperHosts(); + CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkServers, + canalClientConfig.getBootstrapServers(), + kafkaTopic.getTopic(), group.getGroupId(), canalOuterAdapterGroups); + canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker); + canalKafkaWorker.start(); + logger.info("Start adapter for canal-client kafka topic: {} succeed", kafkaTopic.getTopic() + "-" + group.getGroupId()); + } + } + } + + private void loadConnector(CanalOuterAdapterConfiguration config, List canalOutConnectors) { + try { + CanalOuterAdapter adapter = loader.getExtension(config.getName()); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + // 替换ClassLoader + Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader()); + adapter.init(config); + Thread.currentThread().setContextClassLoader(cl); + canalOutConnectors.add(adapter); + logger.info("Load canal adapter: {} succeed", config.getName()); + } catch (Exception e) { + logger.error("Load canal adapter: {} failed", config.getName(), e); + } + } + + /** + * 销毁所有适配器 + * 为防止canal实例太多造成销毁阻塞, 并行销毁 + */ + public void destroy() { + if (canalWorkers.size() > 0) { + ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size()); + for (CanalAdapterWorker v : canalWorkers.values()) { + final CanalAdapterWorker caw = v; + stopExecutorService.submit(new Runnable() { + @Override + public void run() { + caw.stop(); + } + }); + } + stopExecutorService.shutdown(); + } + if (canalKafkaWorkers.size() > 0) { + ExecutorService stopKafkaExecutorService = Executors.newFixedThreadPool(canalKafkaWorkers.size()); + for (CanalAdapterKafkaWorker v : canalKafkaWorkers.values()) { + final CanalAdapterKafkaWorker cakw = v; + stopKafkaExecutorService.submit(new Runnable() { + @Override + public void run() { + cakw.stop(); + } + }); + } + stopKafkaExecutorService.shutdown(); + } + logger.info("All canal adapters destroyed"); + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java new file mode 100644 index 00000000..556910dd --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java @@ -0,0 +1,63 @@ +package com.alibaba.otter.canal.client; + +import com.alibaba.otter.canal.client.support.CanalClientConfig; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import java.io.FileInputStream; + +public class ClientLauncher { + private static final String CLASSPATH_URL_PREFIX = "classpath:"; + private static final Logger logger = LoggerFactory.getLogger(ClientLauncher.class); + + public static void main(String[] args) { + try { + logger.info("## set default uncaught exception handler"); + setGlobalUncaughtExceptionHandler(); + + logger.info("## load canal client configurations"); + String conf = System.getProperty("client.conf", "classpath:canal-client.yml"); + CanalClientConfig canalClientConfig; + if (conf.startsWith(CLASSPATH_URL_PREFIX)) { + conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX); + canalClientConfig = new Yaml().loadAs(ClientLauncher.class.getClassLoader().getResourceAsStream(conf), + CanalClientConfig.class); + } else { + canalClientConfig = new Yaml().loadAs(new FileInputStream(conf), CanalClientConfig.class); + } + logger.info("## start the canal client adapters."); + final CanalAdapterLoader adapterLoader = new CanalAdapterLoader(canalClientConfig); + adapterLoader.init(); + logger.info("## the canal client adapters are running now ......"); + Runtime.getRuntime().addShutdownHook(new Thread() { + + public void run() { + try { + logger.info("## stop the canal client adapters"); + adapterLoader.destroy(); + } catch (Throwable e) { + logger.warn("## something goes wrong when stopping canal client adapters:", e); + } finally { + logger.info("## canal client adapters are down."); + } + } + + }); + } catch (Throwable e) { + logger.error("## something goes wrong when starting up the canal client adapters:", e); + System.exit(0); + } + } + + private static void setGlobalUncaughtExceptionHandler() { + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.error("UnCaughtException", e); + } + }); + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java new file mode 100644 index 00000000..42a154d4 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java @@ -0,0 +1,156 @@ +package com.alibaba.otter.canal.client.adapter; + +import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.client.support.Dml; +import com.alibaba.otter.canal.client.support.JdbcTypeUtil; +import com.alibaba.otter.canal.protocol.CanalEntry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +/** + * 适配器工作线程抽象类 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ +public abstract class AbstractCanalAdapterWorker { + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected String canalDestination; // canal实例 + protected List> canalOuterAdapters; // 外部适配器 + protected ExecutorService groupInnerExecutorService; // 组内工作线程池 + protected volatile boolean running = false; // 是否运行中 + protected Thread thread = null; + protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.error("parse events has an error", e); + } + }; + + /** + * 将每个entry转换为DML操作对象并调用所有适配器写入 + * + * @param entries + */ + protected void convertAndWrite(List entries) { + for (CanalEntry.Entry entry : entries) { + if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { + continue; + } + + CanalEntry.RowChange rowChange; + try { + rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e); + } + + CanalEntry.EventType eventType = rowChange.getEventType(); + + final Dml dml = new Dml(); + dml.setCanalDestination(canalDestination); + dml.setDatabase(entry.getHeader().getSchemaName()); + dml.setTable(entry.getHeader().getTableName()); + dml.setType(eventType.toString()); + dml.setTs(System.currentTimeMillis()); + dml.setSql(rowChange.getSql()); + List> data = new ArrayList<>(); + List> old = new ArrayList<>(); + + if (!rowChange.getIsDdl()) { + Set updateSet = new HashSet<>(); + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + Map row = new LinkedHashMap<>(); + List columns; + + if (eventType == CanalEntry.EventType.DELETE) { + columns = rowData.getBeforeColumnsList(); + } else { + columns = rowData.getAfterColumnsList(); + } + + for (CanalEntry.Column column : columns) { + row.put(column.getName(), JdbcTypeUtil.typeConvert(dml.getTable(), column.getName(), column.getValue(), + column.getSqlType(), column.getMysqlType())); + //获取update为true的字段 + if (column.getUpdated()) { + updateSet.add(column.getName()); + } + } + if (!row.isEmpty()) { + data.add(row); + } + + if (eventType == CanalEntry.EventType.UPDATE) { + Map rowOld = new LinkedHashMap<>(); + for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { + if (updateSet.contains(column.getName())) { + rowOld.put(column.getName(), JdbcTypeUtil.typeConvert(dml.getTable(), column.getName(), + column.getValue(), column.getSqlType(), column.getMysqlType())); + } + } + // update操作将记录修改前的值 + if (!rowOld.isEmpty()) { + old.add(rowOld); + } + } + } + if (!data.isEmpty()) { + dml.setData(data); + } + if (!old.isEmpty()) { + dml.setOld(old); + } + } + List> futures = new ArrayList<>(); + // 组间适配器并行运行 + for (List outerAdapters : canalOuterAdapters) { + final List adapters = outerAdapters; + futures.add(groupInnerExecutorService.submit( + new Callable() { + @Override + public Boolean call() { + boolean flag = true; + // 组内适配器穿行运行,尽量不要配置组内适配器 + for (CanalOuterAdapter c : adapters) { + long begin = System.currentTimeMillis(); + if (!c.writeOut(dml)) { + logger.error("{} write fail! data: {}", c.getClass().getName(), JSON.toJSONString(dml)); + + flag = false; + } + if (logger.isDebugEnabled()) { + logger.debug("{} elapsed time: {}", c.getClass().getName(), (System.currentTimeMillis() - begin)); + } + } + return flag; + } + }) + ); + + // 等待所有适配器写入完成 + // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 + for (Future f : futures) { + try { + if (!f.get()) { + logger.error("Outer adapter write failed"); + } + } catch (InterruptedException | ExecutionException e) { + logger.error(e.getMessage(), e); + } + } + } + } + } + + public abstract void start(); + + public abstract void stop(); +} \ No newline at end of file diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterKafkaWorker.java new file mode 100644 index 00000000..2e64f8b8 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterKafkaWorker.java @@ -0,0 +1,154 @@ +package com.alibaba.otter.canal.client.adapter; + +import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector; +import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors; +import com.alibaba.otter.canal.protocol.Message; +import org.apache.kafka.clients.consumer.CommitFailedException; +import org.apache.kafka.common.errors.WakeupException; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { + private KafkaCanalConnector connector; + + private String topic; + + public CanalAdapterKafkaWorker(String zkServers, String bootstrapServers, String topic, String groupId, List> canalOuterAdapters) { + this.canalOuterAdapters = canalOuterAdapters; + this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); + this.topic = topic; + this.canalDestination = topic; + connector = KafkaCanalConnectors.newKafkaConnector(zkServers, bootstrapServers, topic, null, groupId); + // connector.setSessionTimeout(5L, TimeUnit.MINUTES); + +// super.initSwitcher(topic); + } + + @Override + public void start() { + if (!running) { + thread = new Thread(new Runnable() { + @Override + public void run() { + process(); + } + }); + thread.setUncaughtExceptionHandler(handler); + running = true; + thread.start(); + } + } + + @Override + public void stop() { + try { + if (!running) { + return; + } + + connector.stopRunning(); + running = false; + +// if (switcher != null && !switcher.state()) { +// switcher.set(true); +// } + + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } + } + groupInnerExecutorService.shutdown(); + logger.info("topic {} connectors' worker thread dead!", this.topic); + for (List outerAdapters : canalOuterAdapters) { + for (CanalOuterAdapter adapter : outerAdapters) { + adapter.destroy(); + } + } + logger.info("topic {} all connectors destroyed!", this.topic); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void process() { + while (!running) ; + ExecutorService executor = Executors.newFixedThreadPool(1); + final AtomicBoolean executing = new AtomicBoolean(true); + while (running) { + try { + logger.info("=============> Start to connect topic: {} <=============", this.topic); + connector.connect(); + logger.info("=============> Start to subscribe topic: {}<=============", this.topic); + connector.subscribe(); + logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); + while (running) { + try { +// switcher.get(); //等待开关开启 + + final Message message = connector.getWithoutAck(); + + executing.set(true); + if (message != null) { + executor.submit( + new Runnable() { + @Override + public void run() { + try { + if (logger.isDebugEnabled()) { + logger.debug("topic: {} batchId: {} batchSize: {} ", topic, message.getId(), message.getEntries().size()); + } + long begin = System.currentTimeMillis(); + convertAndWrite(message.getEntries()); + long now = System.currentTimeMillis(); + if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { + logger.error("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); + } + if (logger.isDebugEnabled()) { + logger.debug("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } finally { + executing.compareAndSet(true, false); + } + } + } + ); + + while (executing.get()) { // keeping kafka client active + connector.ack(); + Thread.sleep(500); + } + } else { + connector.ack(); + } + } catch (CommitFailedException e) { + logger.warn(e.getMessage()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + TimeUnit.SECONDS.sleep(1L); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + executor.shutdown(); + + try { + connector.unsubscribe(); + } catch (WakeupException e) { + // No-op. Continue process + } + connector.disconnnect(); + logger.info("=============> Disconnect topic: {} <=============", this.topic); + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterWorker.java new file mode 100644 index 00000000..2af88798 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterWorker.java @@ -0,0 +1,174 @@ +package com.alibaba.otter.canal.client.adapter; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; +import com.alibaba.otter.canal.protocol.Message; + +import java.net.SocketAddress; +import java.util.List; +import java.util.concurrent.Executors; + +/** + * 原生canal-server对应的client适配器工作线程 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ +public class CanalAdapterWorker extends AbstractCanalAdapterWorker { + private static final int BATCH_SIZE = 50; + private static final int SO_TIMEOUT = 0; + + private CanalConnector connector; + + /** + * 单台client适配器worker的构造方法 + * + * @param canalDestination canal实例名 + * @param address canal-server地址 + * @param canalOuterAdapters 外部适配器组 + */ + public CanalAdapterWorker(String canalDestination, SocketAddress address, List> canalOuterAdapters) { + this.canalOuterAdapters = canalOuterAdapters; + this.canalDestination = canalDestination; + groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); + connector = CanalConnectors.newSingleConnector(address, canalDestination, "", ""); + } + + /** + * HA模式下client适配器worker的构造方法 + * + * @param canalDestination canal实例名 + * @param zookeeperHosts zookeeper地址 + * @param canalOuterAdapters 外部适配器组 + */ + public CanalAdapterWorker(String canalDestination, String zookeeperHosts, List> canalOuterAdapters) { + this.canalOuterAdapters = canalOuterAdapters; + this.canalDestination = canalDestination; + groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); + connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", ""); + ((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT); + +// super.initSwitcher(canalDestination); + } + + @Override + public void start() { + if (!running) { + thread = new Thread(new Runnable() { + @Override + public void run() { + process(); + } + }); + thread.setUncaughtExceptionHandler(handler); + thread.start(); + running = true; + } + } + + @Override + public void stop() { + try { + if (!running) { + return; + } + +// if (switcher != null && !switcher.state()) { +// switcher.set(true); +// } + + connector.stopRunning(); + running = false; + + logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination); + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } + } + groupInnerExecutorService.shutdown(); + logger.info("destination {} adapters' worker thread dead!", canalDestination); + for (List outerAdapters : canalOuterAdapters) { + for (CanalOuterAdapter adapter : outerAdapters) { + adapter.destroy(); + } + } + logger.info("destination {} all adapters destroyed!", canalDestination); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void process() { + while (!running) ; //waiting until running == true + while (running) { + try { +// if (switcher != null) { +// switcher.get(); +// } + logger.info("=============> Start to connect destination: {} <=============", this.canalDestination); + connector.connect(); + logger.info("=============> Start to subscribe destination: {} <=============", this.canalDestination); + connector.subscribe(); + logger.info("=============> Subscribe destination: {} succeed <=============", this.canalDestination); + while (running) { +// try { +// if (switcher != null) { +// switcher.get(); +// } +// } catch (TimeoutException e) { +// break; +// } + + // server配置canal.instance.network.soTimeout(默认: 30s) 范围内未与server交互,server将关闭本次socket连接 + Message message = connector.getWithoutAck(BATCH_SIZE); // 获取指定数量的数据 + long batchId = message.getId(); + try { + int size = message.getEntries().size(); + + if (batchId == -1 || size == 0) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + //ignore + } + } else { + if (logger.isDebugEnabled()) { + logger.debug("destination: {} batchId: {} batchSize: {} ", this.canalDestination, batchId, size); + } + long begin = System.currentTimeMillis(); + convertAndWrite(message.getEntries()); + long now = System.currentTimeMillis(); + if (logger.isDebugEnabled()) { + logger.debug("destination: {} batchId: {} elapsed time: {} ms", this.canalDestination, batchId, now - begin); + } + } + connector.ack(batchId); // 提交确认 + } catch (Exception e) { + connector.rollback(batchId); // 处理失败, 回滚数据 + throw e; + } + } + + + } catch (Exception e) { + logger.error("process error!", e); + } finally { + connector.disconnect(); + logger.info("=============> Disconnect destination: {} <=============", this.canalDestination); + } + + if (running) { //is reconnect + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + //ignore + } + } + + } + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java new file mode 100644 index 00000000..19e3af29 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java @@ -0,0 +1,33 @@ +package com.alibaba.otter.canal.client.adapter; + +import com.alibaba.otter.canal.client.support.Dml; +import com.alibaba.otter.canal.client.support.SPI; + +/** + * 外部适配器接口 + * + * @author machengyuan 2018-8-18 下午10:14:02 + * @version 1.0.0 + */ +@SPI("logger") +public interface CanalOuterAdapter { + /** + * 外部适配器初始化接口 + * + * @param configuration 外部适配器配置信息 + */ + void init(CanalOuterAdapterConfiguration configuration); + + /** + * 往适配器中写入数据 + * + * @param dml DML操作数据包 + * @return 是否成功 + */ + Boolean writeOut(Dml dml); + + /** + * 外部适配器销毁接口 + */ + void destroy(); +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapterConfiguration.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapterConfiguration.java new file mode 100644 index 00000000..8a14502d --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapterConfiguration.java @@ -0,0 +1,51 @@ +package com.alibaba.otter.canal.client.adapter; + +import java.util.Properties; + +/** + * 外部适配器配置信息类 + * + * @author machengyuan 2018-8-18 下午10:15:12 + * @version 1.0.0 + */ +public class CanalOuterAdapterConfiguration { + private String name; // 适配器名称, 如: logger, hbase, es + + private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址 + + private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址 + + private Properties properties; // 其余参数, 可填写适配器中的所需的配置信息 + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getHosts() { + return hosts; + } + + public void setHosts(String hosts) { + this.hosts = hosts; + } + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties properties) { + this.properties = properties; + } + + public String getZkHosts() { + return zkHosts; + } + + public void setZkHosts(String zkHosts) { + this.zkHosts = zkHosts; + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java new file mode 100644 index 00000000..0f2b4612 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java @@ -0,0 +1,36 @@ +package com.alibaba.otter.canal.client.example; + +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.support.Dml; +import com.alibaba.otter.canal.client.support.SPI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 外部适配器示例 + * + * @author machengyuan 2018-8-19 下午11:45:38 + * @version 1.0.0 + */ +@SPI("logger") // logger参数对应CanalOuterAdapterConfiguration配置中的name +public class LoggerAdapterExample implements CanalOuterAdapter { + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public Boolean writeOut(Dml dml) { + // 直接输出一个日志信息 + logger.info(dml.toString()); + return true; + } + + @Override + public void init(CanalOuterAdapterConfiguration configuration) { + + } + + @Override + public void destroy() { + + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/CanalClientConfig.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/CanalClientConfig.java new file mode 100644 index 00000000..cb595822 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/CanalClientConfig.java @@ -0,0 +1,177 @@ +package com.alibaba.otter.canal.client.support; + +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapterConfiguration; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * 配置信息类 + * + * @author machengyuan 2018-8-18 下午10:40:12 + * @version 1.0.0 + */ +public class CanalClientConfig { + private String canalServerHost; + + private String zookeeperHosts; + + private Properties properties; + + private String bootstrapServers; + + private List kafkaTopics = new ArrayList<>(); + + private List canalInstances = new ArrayList<>(); + + public String getCanalServerHost() { + return canalServerHost; + } + + public void setCanalServerHost(String canalServerHost) { + this.canalServerHost = canalServerHost; + } + + public String getZookeeperHosts() { + return zookeeperHosts; + } + + public void setZookeeperHosts(String zookeeperHosts) { + this.zookeeperHosts = zookeeperHosts; + } + + public Properties getProperties() { + return properties; + } + + public void setProperties(Properties properties) { + this.properties = properties; + } + + public String getBootstrapServers() { + return bootstrapServers; + } + + public void setBootstrapServers(String bootstrapServers) { + this.bootstrapServers = bootstrapServers; + } + + public List getKafkaTopics() { + return kafkaTopics; + } + + public void setKafkaTopics(List kafkaTopics) { + this.kafkaTopics = kafkaTopics; + } + + public List getCanalInstances() { + return canalInstances; + } + + public void setCanalInstances(List canalInstances) { + this.canalInstances = canalInstances; + } + + public static class CanalInstance { + private String instance; + + private List adapterGroups; + + public String getInstance() { + return instance; + } + + public void setInstance(String instance) { + if (instance != null) { + this.instance = instance.trim(); + } + } + + public List getAdapterGroups() { + return adapterGroups; + } + + public void setAdapterGroups(List adapterGroups) { + this.adapterGroups = adapterGroups; + } + } + + public static class AdapterGroup { + private List outAdapters; + + public List getOutAdapters() { + return outAdapters; + } + + public void setOutAdapters(List outAdapters) { + this.outAdapters = outAdapters; + } + } + + public static class KafkaTopic { + private String topic; + + private List groups = new ArrayList<>(); + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public List getGroups() { + return groups; + } + + public void setGroups(List groups) { + this.groups = groups; + } + } + + public static class Group { + private String groupId; + +// private List adapters = new ArrayList<>(); + + private List outAdapters; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public List getOutAdapters() { + return outAdapters; + } + + public void setOutAdapters(List outAdapters) { + this.outAdapters = outAdapters; + } + + // public List getAdapters() { +// return adapters; +// } +// +// public void setAdapters(List adapters) { +// this.adapters = adapters; +// } + } + +// public static class Adaptor { +// private List outAdapters; +// +// public List getOutAdapters() { +// return outAdapters; +// } +// +// public void setOutAdapters(List outAdapters) { +// this.outAdapters = outAdapters; +// } +// } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/Dml.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/Dml.java new file mode 100644 index 00000000..549b604b --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/Dml.java @@ -0,0 +1,115 @@ +package com.alibaba.otter.canal.client.support; + + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * DML操作转换对象 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ +public class Dml implements Serializable { + private static final long serialVersionUID = 2611556444074013268L; + + private String canalDestination; + private String database; + private String table; + private String type; + private Long ts; + private String sql; + private List> data; + private List> old; + + + public String getCanalDestination() { + return canalDestination; + } + + public void setCanalDestination(String canalDestination) { + this.canalDestination = canalDestination; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Long getTs() { + return ts; + } + + public void setTs(Long ts) { + this.ts = ts; + } + + public String getSql() { + return sql; + } + + public void setSql(String sql) { + this.sql = sql; + } + + public List> getData() { + return data; + } + + public void setData(List> data) { + this.data = data; + } + + public List> getOld() { + return old; + } + + public void setOld(List> old) { + this.old = old; + } + + public void clear() { + canalDestination = null; + database = null; + table = null; + type = null; + ts = null; + data = null; + old = null; + sql = null; + } + + @Override + public String toString() { + return "Dml{" + + "canalDestination='" + canalDestination + '\'' + + ", database='" + database + '\'' + + ", table='" + table + '\'' + + ", type='" + type + '\'' + + ", ts=" + ts + + ", sql='" + sql + '\'' + + ", data=" + data + + ", old=" + old + + '}'; + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/ExtensionLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/ExtensionLoader.java new file mode 100644 index 00000000..fb7ebfed --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/ExtensionLoader.java @@ -0,0 +1,527 @@ +package com.alibaba.otter.canal.client.support; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FilenameFilter; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Paths; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Pattern; + +/** + * SPI 类加载器 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ +public class ExtensionLoader { + private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); + + private static final String SERVICES_DIRECTORY = "META-INF/services/"; + + private static final String CANAL_DIRECTORY = "META-INF/canal/"; + + private static final String DEFAULT_CLASSLOADER_POLICY = "internal"; + + private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); + + private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); + + private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); + + private final Class type; + + private final String classLoaderPolicy; + + private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap<>(); + + private final Holder>> cachedClasses = new Holder<>(); + + private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>(); + + private String cachedDefaultName; + + private ConcurrentHashMap exceptions = new ConcurrentHashMap<>(); + + private static boolean withExtensionAnnotation(Class type) { + return type.isAnnotationPresent(SPI.class); + } + + public static ExtensionLoader getExtensionLoader(Class type) { + return getExtensionLoader(type, DEFAULT_CLASSLOADER_POLICY); + } + + @SuppressWarnings("unchecked") + public static ExtensionLoader getExtensionLoader(Class type, String classLoaderPolicy) { + if (type == null) + throw new IllegalArgumentException("Extension type == null"); + if (!type.isInterface()) { + throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); + } + if (!withExtensionAnnotation(type)) { + throw new IllegalArgumentException("Extension type(" + type + + ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); + } + + ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); + if (loader == null) { + EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type, classLoaderPolicy)); + loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); + } + return loader; + } + + private ExtensionLoader(Class type, String classLoaderPolicy) { + this.type = type; + this.classLoaderPolicy = classLoaderPolicy; + } + + public String getExtensionName(T extensionInstance) { + return getExtensionName(extensionInstance.getClass()); + } + + public String getExtensionName(Class extensionClass) { + return cachedNames.get(extensionClass); + } + + + public ConcurrentHashMap getExceptions() { + return exceptions; + } + + /** + * 返回扩展点实例,如果没有指定的扩展点或是还没加载(即实例化)则返回null注意:此方法不会触发扩展点的加载 + *

      + * 一般应该调用{@link #getExtension(String)}方法获得扩展,这个方法会触发扩展点加载 + * + * @see #getExtension(String) + */ + @SuppressWarnings("unchecked") + public T getLoadedExtension(String name) { + if (name == null || name.length() == 0) + throw new IllegalArgumentException("Extension name == null"); + Holder holder = cachedInstances.get(name); + if (holder == null) { + cachedInstances.putIfAbsent(name, new Holder<>()); + holder = cachedInstances.get(name); + } + return (T) holder.get(); + } + + /** + * 返回已经加载的扩展点的名字 + *

      + * 一般应该调用{@link #getSupportedExtensions()}方法获得扩展,这个方法会返回所有的扩展点 + * + * @see #getSupportedExtensions() + */ + public Set getLoadedExtensions() { + return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet())); + } + + /** + * 返回指定名字的扩展 + * + * @param name + * @return + */ + @SuppressWarnings("unchecked") + public T getExtension(String name) { + if (name == null || name.length() == 0) + throw new IllegalArgumentException("Extension name == null"); + if ("true".equals(name)) { + return getDefaultExtension(); + } + Holder holder = cachedInstances.get(name); + if (holder == null) { + cachedInstances.putIfAbsent(name, new Holder<>()); + holder = cachedInstances.get(name); + } + Object instance = holder.get(); + if (instance == null) { + synchronized (holder) { + instance = holder.get(); + if (instance == null) { + instance = createExtension(name); + holder.set(instance); + } + } + } + return (T) instance; + } + + /** + * 返回缺省的扩展,如果没有设置则返回null + */ + public T getDefaultExtension() { + getExtensionClasses(); + if (null == cachedDefaultName || cachedDefaultName.length() == 0 + || "true".equals(cachedDefaultName)) { + return null; + } + return getExtension(cachedDefaultName); + } + + public boolean hasExtension(String name) { + if (name == null || name.length() == 0) + throw new IllegalArgumentException("Extension name == null"); + try { + return getExtensionClass(name) != null; + } catch (Throwable t) { + return false; + } + } + + public Set getSupportedExtensions() { + Map> clazzes = getExtensionClasses(); + return Collections.unmodifiableSet(new TreeSet(clazzes.keySet())); + } + + /** + * 返回缺省的扩展点名,如果没有设置缺省则返回null + */ + public String getDefaultExtensionName() { + getExtensionClasses(); + return cachedDefaultName; + } + + /** + * 编程方式添加新扩展点 + * + * @param name 扩展点名 + * @param clazz 扩展点类 + * @throws IllegalStateException 要添加扩展点名已经存在 + */ + public void addExtension(String name, Class clazz) { + getExtensionClasses(); // load classes + + if (!type.isAssignableFrom(clazz)) { + throw new IllegalStateException("Input type " + + clazz + "not implement Extension " + type); + } + if (clazz.isInterface()) { + throw new IllegalStateException("Input type " + + clazz + "can not be interface!"); + } + + if (StringUtils.isBlank(name)) { + throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); + } + if (cachedClasses.get().containsKey(name)) { + throw new IllegalStateException("Extension name " + + name + " already existed(Extension " + type + ")!"); + } + + cachedNames.put(clazz, name); + cachedClasses.get().put(name, clazz); + } + + /** + * 编程方式添加替换已有扩展点 + * + * @param name 扩展点名 + * @param clazz 扩展点类 + * @throws IllegalStateException 要添加扩展点名已经存在 + * @deprecated 不推荐应用使用,一般只在测试时可以使用 + */ + @Deprecated + public void replaceExtension(String name, Class clazz) { + getExtensionClasses(); // load classes + + if (!type.isAssignableFrom(clazz)) { + throw new IllegalStateException("Input type " + + clazz + "not implement Extension " + type); + } + if (clazz.isInterface()) { + throw new IllegalStateException("Input type " + + clazz + "can not be interface!"); + } + + if (StringUtils.isBlank(name)) { + throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); + } + if (!cachedClasses.get().containsKey(name)) { + throw new IllegalStateException("Extension name " + + name + " not existed(Extension " + type + ")!"); + } + + cachedNames.put(clazz, name); + cachedClasses.get().put(name, clazz); + cachedInstances.remove(name); + } + + + @SuppressWarnings("unchecked") + private T createExtension(String name) { + Class clazz = getExtensionClasses().get(name); + if (clazz == null) { + throw new IllegalStateException("Extension instance(name: " + name + ", class: " + + type + ") could not be instantiated: class could not be found"); + } + try { + T instance = (T) EXTENSION_INSTANCES.get(clazz); + if (instance == null) { + EXTENSION_INSTANCES.putIfAbsent(clazz, (T) clazz.newInstance()); + instance = (T) EXTENSION_INSTANCES.get(clazz); + } + return instance; + } catch (Throwable t) { + throw new IllegalStateException("Extension instance(name: " + name + ", class: " + + type + ") could not be instantiated: " + t.getMessage(), t); + } + } + + + private Class getExtensionClass(String name) { + if (type == null) + throw new IllegalArgumentException("Extension type == null"); + if (name == null) + throw new IllegalArgumentException("Extension name == null"); + Class clazz = getExtensionClasses().get(name); + if (clazz == null) + throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!"); + return clazz; + } + + private Map> getExtensionClasses() { + Map> classes = cachedClasses.get(); + if (classes == null) { + synchronized (cachedClasses) { + classes = cachedClasses.get(); + if (classes == null) { + classes = loadExtensionClasses(); + cachedClasses.set(classes); + } + } + } + return classes; + } + + private String getJarDirectoryPath() { + URL url = Thread.currentThread().getContextClassLoader().getResource(""); + if (url == null) { + throw new IllegalStateException("failed to get class loader resource"); + } + String dirtyPath = url.toString(); + String jarPath = dirtyPath.replaceAll("^.*file:/", ""); //removes file:/ and everything before it + jarPath = jarPath.replaceAll("jar!.*", "jar"); //removes everything after .jar, if .jar exists in dirtyPath + jarPath = jarPath.replaceAll("%20", " "); //necessary if path has spaces within + if (!jarPath.endsWith(".jar")) { // this is needed if you plan to run the app using Spring Tools Suit play button. + jarPath = jarPath.replaceAll("/classes/.*", "/classes/"); + } + return Paths.get(jarPath).getParent().toString(); //Paths - from java 8 + } + + private Map> loadExtensionClasses() { + final SPI defaultAnnotation = type.getAnnotation(SPI.class); + if (defaultAnnotation != null) { + String value = defaultAnnotation.value(); + if ((value = value.trim()).length() > 0) { + String[] names = NAME_SEPARATOR.split(value); + if (names.length > 1) { + throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() + + ": " + Arrays.toString(names)); + } + if (names.length == 1) cachedDefaultName = names[0]; + } + } + + Map> extensionClasses = new HashMap>(); + + //1. lib folder,customized extension classLoader (jar_dir/lib) + String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib"; + logger.info("extension classpath dir: " + dir); + File externalLibDir = new File(dir); + if (externalLibDir.exists()) { + File[] files = externalLibDir.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".jar"); + } + }); + if (files != null) { + for (File f : files) { + URL url = null; + try { + url = f.toURI().toURL(); + } catch (MalformedURLException e) { + throw new RuntimeException("load extension jar failed!", e); + } + + ClassLoader parent = Thread.currentThread().getContextClassLoader(); + URLClassLoader localClassLoader; + if (!StringUtils.isEmpty(classLoaderPolicy) && DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) { + localClassLoader = new URLClassLoader(new URL[]{url}, parent) { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + Class c = findLoadedClass(name); + if (c != null) { + return c; + } + + if (name.startsWith("java.") + || name.startsWith("org.slf4j.") + || name.startsWith("org.apache.logging") + || name.startsWith("org.apache.commons.logging.")) { + //|| name.startsWith("org.apache.hadoop.")) { + c = super.loadClass(name); + } + if (c != null) + return c; + + try { + // 先加载jar内的class,可避免jar冲突 + c = findClass(name); + } catch (ClassNotFoundException e) { + c = null; + } + if (c != null) { + return c; + } + + return super.loadClass(name); + } + + // @Override + // public Enumeration getResources(String name) throws IOException { + // @SuppressWarnings("unchecked") + // Enumeration[] tmp = (Enumeration[]) new Enumeration[2]; + + // tmp[0] = findResources(name); //local class path first + // //tmp[1] = super.getResources(name); + + // return new CompoundEnumeration<>(tmp); + // } + }; + } else { + localClassLoader = new URLClassLoader(new URL[]{url}, parent); + } + + loadFile(extensionClasses, CANAL_DIRECTORY, localClassLoader); + loadFile(extensionClasses, SERVICES_DIRECTORY, localClassLoader); + } + } + } + //2. load inner extension class with default classLoader + ClassLoader classLoader = findClassLoader(); + loadFile(extensionClasses, CANAL_DIRECTORY, classLoader); + loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader); + + return extensionClasses; + } + + private void loadFile(Map> extensionClasses, String dir, ClassLoader classLoader) { + String fileName = dir + type.getName(); + try { + Enumeration urls; + if (classLoader != null) { + urls = classLoader.getResources(fileName); + } else { + urls = ClassLoader.getSystemResources(fileName); + } + if (urls != null) { + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + try { + BufferedReader reader = null; + try { + reader = new BufferedReader(new InputStreamReader(url.openStream(), "utf-8")); + String line = null; + while ((line = reader.readLine()) != null) { + final int ci = line.indexOf('#'); + if (ci >= 0) line = line.substring(0, ci); + line = line.trim(); + if (line.length() > 0) { + try { + String name = null; + int i = line.indexOf('='); + if (i > 0) { + name = line.substring(0, i).trim(); + line = line.substring(i + 1).trim(); + } + if (line.length() > 0) { + Class clazz = classLoader.loadClass(line); + //Class clazz = Class.forName(line, true, classLoader); + if (!type.isAssignableFrom(clazz)) { + throw new IllegalStateException("Error when load extension class(interface: " + + type + ", class line: " + clazz.getName() + "), class " + + clazz.getName() + "is not subtype of interface."); + } else { + try { + clazz.getConstructor(type); + } catch (NoSuchMethodException e) { + clazz.getConstructor(); + String[] names = NAME_SEPARATOR.split(name); + if (names != null && names.length > 0) { + for (String n : names) { + if (!cachedNames.containsKey(clazz)) { + cachedNames.put(clazz, n); + } + Class c = extensionClasses.get(n); + if (c == null) { + extensionClasses.put(n, clazz); + } else if (c != clazz) { + cachedNames.remove(clazz); + throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); + } + } + } + } + } + } + } catch (Throwable t) { + IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); + exceptions.put(line, e); + } + } + } // end of while read lines + } finally { + if (reader != null) { + reader.close(); + } + } + } catch (Throwable t) { + logger.error("Exception when load extension class(interface: " + + type + ", class file: " + url + ") in " + url, t); + } + } // end of while urls + } + } catch (Throwable t) { + logger.error("Exception when load extension class(interface: " + + type + ", description file: " + fileName + ").", t); + } + } + + + private static ClassLoader findClassLoader() { + return ExtensionLoader.class.getClassLoader(); + } + + @Override + public String toString() { + return this.getClass().getName() + "[" + type.getName() + "]"; + } + + private static class Holder { + + private volatile T value; + + private void set(T value) { + this.value = value; + } + + private T get() { + return value; + } + + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/JdbcTypeUtil.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/JdbcTypeUtil.java new file mode 100644 index 00000000..e8ecff77 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/JdbcTypeUtil.java @@ -0,0 +1,137 @@ +package com.alibaba.otter.canal.client.support; + +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Date; +import java.sql.Time; +import java.sql.Timestamp; +import java.sql.Types; + +public class JdbcTypeUtil { + private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class); + + public static Class jdbcType2javaType(int jdbcType) { + switch (jdbcType) { + case Types.BIT: + case Types.BOOLEAN: + // return Boolean.class; + case Types.TINYINT: + return Byte.TYPE; + case Types.SMALLINT: + return Short.class; + case Types.INTEGER: + return Integer.class; + case Types.BIGINT: + return Long.class; + case Types.DECIMAL: + case Types.NUMERIC: + return BigDecimal.class; + case Types.REAL: + return Float.class; + case Types.FLOAT: + case Types.DOUBLE: + return Double.class; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + return String.class; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BLOB: + return byte[].class; + case Types.DATE: + return Date.class; + case Types.TIME: + return Time.class; + case Types.TIMESTAMP: + return Timestamp.class; + default: + return String.class; + } + } + + public static Object typeConvert(String tableName, String columnName, String value, int sqlType, String mysqlType) { + if (value == null || value.equals("")) { + return null; + } + + try { + Object res; + switch (sqlType) { + case Types.INTEGER: + res = Integer.parseInt(value); + break; + case Types.SMALLINT: + res = Short.parseShort(value); + break; + case Types.BIT: + case Types.TINYINT: + res = Byte.parseByte(value); + break; + case Types.BIGINT: + if (mysqlType.startsWith("bigint") && mysqlType.endsWith("unsigned")) { + res = new BigInteger(value); + } else { + res = Long.parseLong(value); + } + break; + //case Types.BIT: + case Types.BOOLEAN: + res = !"0".equals(value); + break; + case Types.DOUBLE: + case Types.FLOAT: + res = Double.parseDouble(value); + break; + case Types.REAL: + res = Float.parseFloat(value); + break; + case Types.DECIMAL: + case Types.NUMERIC: + res = new BigDecimal(value); + break; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BLOB: + res = value.getBytes("ISO-8859-1"); + break; + case Types.DATE: + if (!value.startsWith("0000-00-00")) { + value = value.trim().replace(" ", "T"); + DateTime dt = new DateTime(value); + res = new Date(dt.toDate().getTime()); + } else { + res = null; + } + break; + case Types.TIME: + value = "T" + value; + DateTime dt = new DateTime(value); + res = new Time(dt.toDate().getTime()); + break; + case Types.TIMESTAMP: + if (!value.startsWith("0000-00-00")) { + value = value.trim().replace(" ", "T"); + dt = new DateTime(value); + res = new Timestamp(dt.toDate().getTime()); + } else { + res = null; + } + break; + case Types.CLOB: + default: + res = value; + } + return res; + } catch (Exception e) { + logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName,value, sqlType); + return value; + } + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/SPI.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/SPI.java new file mode 100644 index 00000000..7a731a34 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/SPI.java @@ -0,0 +1,11 @@ +package com.alibaba.otter.canal.client.support; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface SPI { + //Default SPI name + String value() default ""; +} diff --git a/client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter b/client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter new file mode 100644 index 00000000..787f15d7 --- /dev/null +++ b/client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter @@ -0,0 +1 @@ +logger=com.alibaba.otter.canal.client.example.LoggerAdapterExample \ No newline at end of file diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml new file mode 100644 index 00000000..80a912a2 --- /dev/null +++ b/client-launcher/src/main/resources/canal-client.yml @@ -0,0 +1,22 @@ +#canalServerHost: 127.0.0.1:11111 +zookeeperHosts: 127.0.0.1:2181 +#bootstrapServers: kafka1.mytest.com:9092,kafka2.mytest.com:9092 + +canalInstances: +- instance: example + adapterGroups: + - outAdapters: + - name: logger +# - name: hbase +# hosts: +# properties: {znodeParent: /hbase} + +#kafkaTopics: +#- topic: devmysql4308 +# groups: +# - groupId: devmysql4308_es +# adapters: +# - name: es +# hosts: +# zkHosts: +# properties: {clusterName: es-service-test} diff --git a/client-launcher/src/main/resources/logback.xml b/client-launcher/src/main/resources/logback.xml new file mode 100644 index 00000000..41979ba0 --- /dev/null +++ b/client-launcher/src/main/resources/logback.xml @@ -0,0 +1,47 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n + + + + + + + destination + canal_client + + + + ../logs/${destination}.log + + + ../logs/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz + + + 512MB + + 60 + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 3101aca6..19b07a06 100644 --- a/pom.xml +++ b/pom.xml @@ -120,6 +120,7 @@ kafka kafka-client prometheus + client-launcher From 1bef1b72bcd2f142be764deee12d5bab5400ad06 Mon Sep 17 00:00:00 2001 From: lixiang Date: Tue, 21 Aug 2018 17:20:00 +0800 Subject: [PATCH 104/360] =?UTF-8?q?Client=20QPS=E6=8C=87=E6=A0=87=E7=9A=84?= =?UTF-8?q?datasource=E9=85=8D=E7=BD=AE=E9=A1=B9=E7=94=B1"Prometheus"?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E5=8F=96$datasource=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/metrics/Canal_instances_tmpl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json index f9fde344..0d7c94d2 100644 --- a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json +++ b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json @@ -938,7 +938,7 @@ "bars": false, "dashLength": 10, "dashes": false, - "datasource": "Prometheus", + "datasource": "$datasource", "description": "client 请求的GET与ACK包的QPS。", "fill": 1, "gridPos": { From 20b0b39b14c94e65844b53064308902a127cac56 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 23 Aug 2018 12:02:44 +0800 Subject: [PATCH 105/360] =?UTF-8?q?fix=20#849:=20hbase=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/common/pom.xml | 33 + .../client/adapter/CanalOuterAdapter.java | 11 +- .../adapter}/support/CanalClientConfig.java | 64 +- .../CanalOuterAdapterConfiguration.java | 11 +- .../canal/client/adapter}/support/Dml.java | 39 +- .../adapter}/support/ExtensionLoader.java | 236 ++++--- .../client/adapter}/support/JdbcTypeUtil.java | 64 +- .../client/adapter/support/MessageUtil.java | 113 ++++ .../canal/client/adapter}/support/SPI.java | 7 +- client-adapter/hbase/pom.xml | 101 +++ .../client/adapter/hbase/HbaseAdapter.java | 115 ++++ .../adapter/hbase/config/MappingConfig.java | 337 ++++++++++ .../hbase/config/MappingConfigLoader.java | 149 +++++ .../hbase/service/HbaseSyncService.java | 418 ++++++++++++ .../client/adapter/hbase/support/HRow.java | 84 +++ .../adapter/hbase/support/HbaseTemplate.java | 159 +++++ .../client/adapter/hbase/support/PhType.java | 150 +++++ .../adapter/hbase/support/PhTypeUtil.java | 614 ++++++++++++++++++ .../client/adapter/hbase/support/Type.java | 104 +++ .../adapter/hbase/support/TypeUtil.java | 188 ++++++ ...ter.canal.client.adapter.CanalOuterAdapter | 1 + .../main/resources/hbase-mapping/configs.conf | 5 + .../hbase-mapping/mytest_person2.yml | 56 ++ client-adapter/logger/pom.xml | 47 ++ .../adapter/logger/LoggerAdapterExample.java | 45 ++ ...ter.canal.client.adapter.CanalOuterAdapter | 1 + client-adapter/pom.xml | 21 + client-launcher/pom.xml | 25 +- .../otter/canal/client/ClientLauncher.java | 8 +- .../adapter/AbstractCanalAdapterWorker.java | 156 ----- .../loader/AbstractCanalAdapterWorker.java | 83 +++ .../{ => loader}/CanalAdapterKafkaWorker.java | 78 ++- .../loader}/CanalAdapterLoader.java | 42 +- .../{ => loader}/CanalAdapterWorker.java | 75 ++- .../client/example/LoggerAdapterExample.java | 36 - ...ter.canal.client.adapter.CanalOuterAdapter | 1 - .../src/main/resources/canal-client.yml | 10 +- .../src/main/resources/logback.xml | 4 +- pom.xml | 3 +- 39 files changed, 3182 insertions(+), 512 deletions(-) create mode 100644 client-adapter/common/pom.xml rename {client-launcher => client-adapter/common}/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java (65%) rename {client-launcher/src/main/java/com/alibaba/otter/canal/client => client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter}/support/CanalClientConfig.java (75%) rename {client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter => client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support}/CanalOuterAdapterConfiguration.java (62%) rename {client-launcher/src/main/java/com/alibaba/otter/canal/client => client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter}/support/Dml.java (62%) rename {client-launcher/src/main/java/com/alibaba/otter/canal/client => client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter}/support/ExtensionLoader.java (65%) rename {client-launcher/src/main/java/com/alibaba/otter/canal/client => client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter}/support/JdbcTypeUtil.java (69%) create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java rename {client-launcher/src/main/java/com/alibaba/otter/canal/client => client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter}/support/SPI.java (55%) create mode 100644 client-adapter/hbase/pom.xml create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HRow.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HbaseTemplate.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhTypeUtil.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java create mode 100644 client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter create mode 100644 client-adapter/hbase/src/main/resources/hbase-mapping/configs.conf create mode 100644 client-adapter/hbase/src/main/resources/hbase-mapping/mytest_person2.yml create mode 100644 client-adapter/logger/pom.xml create mode 100644 client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java create mode 100644 client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter create mode 100644 client-adapter/pom.xml delete mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java rename client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/{ => loader}/CanalAdapterKafkaWorker.java (62%) rename client-launcher/src/main/java/com/alibaba/otter/canal/client/{ => adapter/loader}/CanalAdapterLoader.java (82%) rename client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/{ => loader}/CanalAdapterWorker.java (74%) delete mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java delete mode 100644 client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml new file mode 100644 index 00000000..a3a78073 --- /dev/null +++ b/client-adapter/common/pom.xml @@ -0,0 +1,33 @@ + + + + canal.client-adapter + com.alibaba.otter + 1.1.0-SNAPSHOT + + 4.0.0 + com.alibaba.otter + client-adapter.common + jar + canal client adapter common module for otter ${project.version} + + + com.alibaba.otter + canal.protocol + ${project.version} + + + org.slf4j + slf4j-api + 1.7.12 + + + joda-time + joda-time + 2.9.4 + + + + \ No newline at end of file diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java similarity index 65% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java index 19e3af29..53368472 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java @@ -1,7 +1,8 @@ package com.alibaba.otter.canal.client.adapter; -import com.alibaba.otter.canal.client.support.Dml; -import com.alibaba.otter.canal.client.support.SPI; +import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.SPI; +import com.alibaba.otter.canal.protocol.Message; /** * 外部适配器接口 @@ -11,6 +12,7 @@ import com.alibaba.otter.canal.client.support.SPI; */ @SPI("logger") public interface CanalOuterAdapter { + /** * 外部适配器初始化接口 * @@ -21,10 +23,9 @@ public interface CanalOuterAdapter { /** * 往适配器中写入数据 * - * @param dml DML操作数据包 - * @return 是否成功 + * @param message message数据包 */ - Boolean writeOut(Dml dml); + void writeOut(Message message); /** * 外部适配器销毁接口 diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java similarity index 75% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/support/CanalClientConfig.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index cb595822..4f8ff396 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -1,6 +1,4 @@ -package com.alibaba.otter.canal.client.support; - -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapterConfiguration; +package com.alibaba.otter.canal.client.adapter.support; import java.util.ArrayList; import java.util.List; @@ -13,15 +11,16 @@ import java.util.Properties; * @version 1.0.0 */ public class CanalClientConfig { - private String canalServerHost; - private String zookeeperHosts; + private String canalServerHost; - private Properties properties; + private String zookeeperHosts; - private String bootstrapServers; + private Properties properties; - private List kafkaTopics = new ArrayList<>(); + private String bootstrapServers; + + private List kafkaTopics = new ArrayList<>(); private List canalInstances = new ArrayList<>(); @@ -74,7 +73,8 @@ public class CanalClientConfig { } public static class CanalInstance { - private String instance; + + private String instance; private List adapterGroups; @@ -98,6 +98,7 @@ public class CanalClientConfig { } public static class AdapterGroup { + private List outAdapters; public List getOutAdapters() { @@ -110,7 +111,8 @@ public class CanalClientConfig { } public static class KafkaTopic { - private String topic; + + private String topic; private List groups = new ArrayList<>(); @@ -132,9 +134,10 @@ public class CanalClientConfig { } public static class Group { - private String groupId; -// private List adapters = new ArrayList<>(); + private String groupId; + + // private List adapters = new ArrayList<>(); private List outAdapters; @@ -154,24 +157,25 @@ public class CanalClientConfig { this.outAdapters = outAdapters; } - // public List getAdapters() { -// return adapters; -// } -// -// public void setAdapters(List adapters) { -// this.adapters = adapters; -// } + // public List getAdapters() { + // return adapters; + // } + // + // public void setAdapters(List adapters) { + // this.adapters = adapters; + // } } -// public static class Adaptor { -// private List outAdapters; -// -// public List getOutAdapters() { -// return outAdapters; -// } -// -// public void setOutAdapters(List outAdapters) { -// this.outAdapters = outAdapters; -// } -// } + // public static class Adaptor { + // private List outAdapters; + // + // public List getOutAdapters() { + // return outAdapters; + // } + // + // public void setOutAdapters(List outAdapters) + // { + // this.outAdapters = outAdapters; + // } + // } } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapterConfiguration.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalOuterAdapterConfiguration.java similarity index 62% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapterConfiguration.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalOuterAdapterConfiguration.java index 8a14502d..e6e34b67 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapterConfiguration.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalOuterAdapterConfiguration.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.client.adapter; +package com.alibaba.otter.canal.client.adapter.support; import java.util.Properties; @@ -9,13 +9,14 @@ import java.util.Properties; * @version 1.0.0 */ public class CanalOuterAdapterConfiguration { - private String name; // 适配器名称, 如: logger, hbase, es - private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址 + private String name; // 适配器名称, 如: logger, hbase, es - private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址 + private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址 - private Properties properties; // 其余参数, 可填写适配器中的所需的配置信息 + private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址 + + private Properties properties; // 其余参数, 可填写适配器中的所需的配置信息 public String getName() { return name; diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/Dml.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java similarity index 62% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/support/Dml.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java index 549b604b..fe7333e5 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/Dml.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java @@ -1,5 +1,4 @@ -package com.alibaba.otter.canal.client.support; - +package com.alibaba.otter.canal.client.adapter.support; import java.io.Serializable; import java.util.List; @@ -12,26 +11,17 @@ import java.util.Map; * @version 1.0.0 */ public class Dml implements Serializable { - private static final long serialVersionUID = 2611556444074013268L; - private String canalDestination; - private String database; - private String table; - private String type; - private Long ts; - private String sql; + private static final long serialVersionUID = 2611556444074013268L; + + private String database; + private String table; + private String type; + private Long ts; + private String sql; private List> data; private List> old; - - public String getCanalDestination() { - return canalDestination; - } - - public void setCanalDestination(String canalDestination) { - this.canalDestination = canalDestination; - } - public String getDatabase() { return database; } @@ -89,7 +79,6 @@ public class Dml implements Serializable { } public void clear() { - canalDestination = null; database = null; table = null; type = null; @@ -101,15 +90,7 @@ public class Dml implements Serializable { @Override public String toString() { - return "Dml{" + - "canalDestination='" + canalDestination + '\'' + - ", database='" + database + '\'' + - ", table='" + table + '\'' + - ", type='" + type + '\'' + - ", ts=" + ts + - ", sql='" + sql + '\'' + - ", data=" + data + - ", old=" + old + - '}'; + return "Dml{" + "database='" + database + '\'' + ", table='" + table + '\'' + ", type='" + type + '\'' + ", ts=" + + ts + ", sql='" + sql + '\'' + ", data=" + data + ", old=" + old + '}'; } } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java similarity index 65% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/support/ExtensionLoader.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index fb7ebfed..01b05054 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -1,13 +1,6 @@ -package com.alibaba.otter.canal.client.support; +package com.alibaba.otter.canal.client.adapter.support; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FilenameFilter; -import java.io.InputStreamReader; +import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; @@ -17,6 +10,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * SPI 类加载器 * @@ -24,33 +20,36 @@ import java.util.regex.Pattern; * @version 1.0.0 */ public class ExtensionLoader { - private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); - private static final String SERVICES_DIRECTORY = "META-INF/services/"; + private static final Logger logger = LoggerFactory + .getLogger(ExtensionLoader.class); - private static final String CANAL_DIRECTORY = "META-INF/canal/"; + private static final String SERVICES_DIRECTORY = "META-INF/services/"; - private static final String DEFAULT_CLASSLOADER_POLICY = "internal"; + private static final String CANAL_DIRECTORY = "META-INF/canal/"; - private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); + private static final String DEFAULT_CLASSLOADER_POLICY = "internal"; - private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); + private static final Pattern NAME_SEPARATOR = Pattern + .compile("\\s*[,]+\\s*"); - private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); + private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); - private final Class type; + private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); - private final String classLoaderPolicy; + private final Class type; - private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap<>(); + private final String classLoaderPolicy; - private final Holder>> cachedClasses = new Holder<>(); + private final ConcurrentMap, String> cachedNames = new ConcurrentHashMap<>(); - private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>(); + private final Holder>> cachedClasses = new Holder<>(); - private String cachedDefaultName; + private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>(); - private ConcurrentHashMap exceptions = new ConcurrentHashMap<>(); + private String cachedDefaultName; + + private ConcurrentHashMap exceptions = new ConcurrentHashMap<>(); private static boolean withExtensionAnnotation(Class type) { return type.isAnnotationPresent(SPI.class); @@ -62,14 +61,13 @@ public class ExtensionLoader { @SuppressWarnings("unchecked") public static ExtensionLoader getExtensionLoader(Class type, String classLoaderPolicy) { - if (type == null) - throw new IllegalArgumentException("Extension type == null"); + if (type == null) throw new IllegalArgumentException("Extension type == null"); if (!type.isInterface()) { throw new IllegalArgumentException("Extension type(" + type + ") is not interface!"); } if (!withExtensionAnnotation(type)) { - throw new IllegalArgumentException("Extension type(" + type + - ") is not extension, because WITHOUT @" + SPI.class.getSimpleName() + " Annotation!"); + throw new IllegalArgumentException("Extension type(" + type + ") is not extension, because WITHOUT @" + + SPI.class.getSimpleName() + " Annotation!"); } ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); @@ -80,7 +78,7 @@ public class ExtensionLoader { return loader; } - private ExtensionLoader(Class type, String classLoaderPolicy) { + private ExtensionLoader(Class type, String classLoaderPolicy){ this.type = type; this.classLoaderPolicy = classLoaderPolicy; } @@ -93,7 +91,6 @@ public class ExtensionLoader { return cachedNames.get(extensionClass); } - public ConcurrentHashMap getExceptions() { return exceptions; } @@ -107,8 +104,7 @@ public class ExtensionLoader { */ @SuppressWarnings("unchecked") public T getLoadedExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); + if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); Holder holder = cachedInstances.get(name); if (holder == null) { cachedInstances.putIfAbsent(name, new Holder<>()); @@ -136,8 +132,7 @@ public class ExtensionLoader { */ @SuppressWarnings("unchecked") public T getExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); + if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); if ("true".equals(name)) { return getDefaultExtension(); } @@ -164,16 +159,14 @@ public class ExtensionLoader { */ public T getDefaultExtension() { getExtensionClasses(); - if (null == cachedDefaultName || cachedDefaultName.length() == 0 - || "true".equals(cachedDefaultName)) { + if (null == cachedDefaultName || cachedDefaultName.length() == 0 || "true".equals(cachedDefaultName)) { return null; } return getExtension(cachedDefaultName); } public boolean hasExtension(String name) { - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Extension name == null"); + if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); try { return getExtensionClass(name) != null; } catch (Throwable t) { @@ -197,7 +190,7 @@ public class ExtensionLoader { /** * 编程方式添加新扩展点 * - * @param name 扩展点名 + * @param name 扩展点名 * @param clazz 扩展点类 * @throws IllegalStateException 要添加扩展点名已经存在 */ @@ -205,20 +198,17 @@ public class ExtensionLoader { getExtensionClasses(); // load classes if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + - clazz + "not implement Extension " + type); + throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type); } if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + - clazz + "can not be interface!"); + throw new IllegalStateException("Input type " + clazz + "can not be interface!"); } - if (StringUtils.isBlank(name)) { + if (name == null || "".equals(name)) { throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); } if (cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + - name + " already existed(Extension " + type + ")!"); + throw new IllegalStateException("Extension name " + name + " already existed(Extension " + type + ")!"); } cachedNames.put(clazz, name); @@ -228,7 +218,7 @@ public class ExtensionLoader { /** * 编程方式添加替换已有扩展点 * - * @param name 扩展点名 + * @param name 扩展点名 * @param clazz 扩展点类 * @throws IllegalStateException 要添加扩展点名已经存在 * @deprecated 不推荐应用使用,一般只在测试时可以使用 @@ -238,20 +228,17 @@ public class ExtensionLoader { getExtensionClasses(); // load classes if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + - clazz + "not implement Extension " + type); + throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type); } if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + - clazz + "can not be interface!"); + throw new IllegalStateException("Input type " + clazz + "can not be interface!"); } - if (StringUtils.isBlank(name)) { + if (name == null || "".equals(name)) { throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); } if (!cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + - name + " not existed(Extension " + type + ")!"); + throw new IllegalStateException("Extension name " + name + " not existed(Extension " + type + ")!"); } cachedNames.put(clazz, name); @@ -259,13 +246,12 @@ public class ExtensionLoader { cachedInstances.remove(name); } - @SuppressWarnings("unchecked") private T createExtension(String name) { Class clazz = getExtensionClasses().get(name); if (clazz == null) { - throw new IllegalStateException("Extension instance(name: " + name + ", class: " + - type + ") could not be instantiated: class could not be found"); + throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + + ") could not be instantiated: class could not be found"); } try { T instance = (T) EXTENSION_INSTANCES.get(clazz); @@ -275,17 +261,15 @@ public class ExtensionLoader { } return instance; } catch (Throwable t) { - throw new IllegalStateException("Extension instance(name: " + name + ", class: " + - type + ") could not be instantiated: " + t.getMessage(), t); + throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + + ") could not be instantiated: " + t.getMessage(), + t); } } - private Class getExtensionClass(String name) { - if (type == null) - throw new IllegalArgumentException("Extension type == null"); - if (name == null) - throw new IllegalArgumentException("Extension name == null"); + if (type == null) throw new IllegalArgumentException("Extension type == null"); + if (name == null) throw new IllegalArgumentException("Extension name == null"); Class clazz = getExtensionClasses().get(name); if (clazz == null) throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!"); @@ -312,13 +296,14 @@ public class ExtensionLoader { throw new IllegalStateException("failed to get class loader resource"); } String dirtyPath = url.toString(); - String jarPath = dirtyPath.replaceAll("^.*file:/", ""); //removes file:/ and everything before it - jarPath = jarPath.replaceAll("jar!.*", "jar"); //removes everything after .jar, if .jar exists in dirtyPath - jarPath = jarPath.replaceAll("%20", " "); //necessary if path has spaces within - if (!jarPath.endsWith(".jar")) { // this is needed if you plan to run the app using Spring Tools Suit play button. + String jarPath = dirtyPath.replaceAll("^.*file:/", ""); // removes file:/ and everything before it + jarPath = jarPath.replaceAll("jar!.*", "jar"); // removes everything after .jar, if .jar exists in dirtyPath + jarPath = jarPath.replaceAll("%20", " "); // necessary if path has spaces within + if (!jarPath.endsWith(".jar")) { // this is needed if you plan to run the app using Spring Tools Suit play + // button. jarPath = jarPath.replaceAll("/classes/.*", "/classes/"); } - return Paths.get(jarPath).getParent().toString(); //Paths - from java 8 + return Paths.get(jarPath).getParent().toString(); // Paths - from java 8 } private Map> loadExtensionClasses() { @@ -329,7 +314,7 @@ public class ExtensionLoader { String[] names = NAME_SEPARATOR.split(value); if (names.length > 1) { throw new IllegalStateException("more than 1 default extension name on extension " + type.getName() - + ": " + Arrays.toString(names)); + + ": " + Arrays.toString(names)); } if (names.length == 1) cachedDefaultName = names[0]; } @@ -337,12 +322,17 @@ public class ExtensionLoader { Map> extensionClasses = new HashMap>(); - //1. lib folder,customized extension classLoader (jar_dir/lib) + // 1. lib folder,customized extension classLoader (jar_dir/lib) String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib"; logger.info("extension classpath dir: " + dir); File externalLibDir = new File(dir); + if (!externalLibDir.exists()) { + externalLibDir = new File( + File.separator + this.getJarDirectoryPath() + File.separator + "canal_client" + File.separator + "lib"); + } if (externalLibDir.exists()) { File[] files = externalLibDir.listFiles(new FilenameFilter() { + @Override public boolean accept(File dir, String name) { return name.endsWith(".jar"); @@ -359,8 +349,10 @@ public class ExtensionLoader { ClassLoader parent = Thread.currentThread().getContextClassLoader(); URLClassLoader localClassLoader; - if (!StringUtils.isEmpty(classLoaderPolicy) && DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) { - localClassLoader = new URLClassLoader(new URL[]{url}, parent) { + if (classLoaderPolicy == null || "".equals(classLoaderPolicy) + || DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) { + localClassLoader = new URLClassLoader(new URL[] { url }, parent) { + @Override public Class loadClass(String name) throws ClassNotFoundException { Class c = findLoadedClass(name); @@ -368,15 +360,13 @@ public class ExtensionLoader { return c; } - if (name.startsWith("java.") - || name.startsWith("org.slf4j.") - || name.startsWith("org.apache.logging") - || name.startsWith("org.apache.commons.logging.")) { - //|| name.startsWith("org.apache.hadoop.")) { + if (name.startsWith("java.") || name.startsWith("org.slf4j.") + || name.startsWith("org.apache.logging") + || name.startsWith("org.apache.commons.logging.")) { + // || name.startsWith("org.apache.hadoop.")) { c = super.loadClass(name); } - if (c != null) - return c; + if (c != null) return c; try { // 先加载jar内的class,可避免jar冲突 @@ -391,19 +381,19 @@ public class ExtensionLoader { return super.loadClass(name); } - // @Override - // public Enumeration getResources(String name) throws IOException { - // @SuppressWarnings("unchecked") - // Enumeration[] tmp = (Enumeration[]) new Enumeration[2]; + @Override + public Enumeration getResources(String name) throws IOException { + @SuppressWarnings("unchecked") + Enumeration[] tmp = (Enumeration[]) new Enumeration[2]; - // tmp[0] = findResources(name); //local class path first - // //tmp[1] = super.getResources(name); + tmp[0] = findResources(name); // local class path first + // tmp[1] = super.getResources(name); - // return new CompoundEnumeration<>(tmp); - // } + return new CompoundEnumeration<>(tmp); + } }; } else { - localClassLoader = new URLClassLoader(new URL[]{url}, parent); + localClassLoader = new URLClassLoader(new URL[] { url }, parent); } loadFile(extensionClasses, CANAL_DIRECTORY, localClassLoader); @@ -411,7 +401,7 @@ public class ExtensionLoader { } } } - //2. load inner extension class with default classLoader + // 2. load inner extension class with default classLoader ClassLoader classLoader = findClassLoader(); loadFile(extensionClasses, CANAL_DIRECTORY, classLoader); loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader); @@ -419,6 +409,40 @@ public class ExtensionLoader { return extensionClasses; } + public static class CompoundEnumeration implements Enumeration { + + private Enumeration[] enums; + private int index = 0; + + public CompoundEnumeration(Enumeration[] enums){ + this.enums = enums; + } + + private boolean next() { + while (this.index < this.enums.length) { + if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) { + return true; + } + + ++this.index; + } + + return false; + } + + public boolean hasMoreElements() { + return this.next(); + } + + public E nextElement() { + if (!this.next()) { + throw new NoSuchElementException(); + } else { + return this.enums[this.index].nextElement(); + } + } + } + private void loadFile(Map> extensionClasses, String dir, ClassLoader classLoader) { String fileName = dir + type.getName(); try { @@ -450,11 +474,13 @@ public class ExtensionLoader { } if (line.length() > 0) { Class clazz = classLoader.loadClass(line); - //Class clazz = Class.forName(line, true, classLoader); + // Class clazz = Class.forName(line, true, classLoader); if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Error when load extension class(interface: " + - type + ", class line: " + clazz.getName() + "), class " - + clazz.getName() + "is not subtype of interface."); + throw new IllegalStateException( + "Error when load extension class(interface: " + type + + ", class line: " + clazz.getName() + + "), class " + clazz.getName() + + "is not subtype of interface."); } else { try { clazz.getConstructor(type); @@ -471,7 +497,11 @@ public class ExtensionLoader { extensionClasses.put(n, clazz); } else if (c != clazz) { cachedNames.remove(clazz); - throw new IllegalStateException("Duplicate extension " + type.getName() + " name " + n + " on " + c.getName() + " and " + clazz.getName()); + throw new IllegalStateException( + "Duplicate extension " + type.getName() + " name " + + n + " on " + + c.getName() + " and " + + clazz.getName()); } } } @@ -479,7 +509,12 @@ public class ExtensionLoader { } } } catch (Throwable t) { - IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + type + ", class line: " + line + ") in " + url + ", cause: " + t.getMessage(), t); + IllegalStateException e = new IllegalStateException( + "Failed to load extension class(interface: " + type + ", class line: " + + line + ") in " + url + + ", cause: " + + t.getMessage(), + t); exceptions.put(line, e); } } @@ -490,18 +525,19 @@ public class ExtensionLoader { } } } catch (Throwable t) { - logger.error("Exception when load extension class(interface: " + - type + ", class file: " + url + ") in " + url, t); + logger.error("Exception when load extension class(interface: " + type + ", class file: " + url + + ") in " + url, + t); } } // end of while urls } } catch (Throwable t) { - logger.error("Exception when load extension class(interface: " + - type + ", description file: " + fileName + ").", t); + logger.error( + "Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", + t); } } - private static ClassLoader findClassLoader() { return ExtensionLoader.class.getClassLoader(); } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/JdbcTypeUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java similarity index 69% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/support/JdbcTypeUtil.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java index e8ecff77..fd65589f 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/JdbcTypeUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java @@ -1,8 +1,4 @@ -package com.alibaba.otter.canal.client.support; - -import org.joda.time.DateTime; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package com.alibaba.otter.canal.client.adapter.support; import java.math.BigDecimal; import java.math.BigInteger; @@ -11,49 +7,19 @@ import java.sql.Time; import java.sql.Timestamp; import java.sql.Types; -public class JdbcTypeUtil { - private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class); +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; - public static Class jdbcType2javaType(int jdbcType) { - switch (jdbcType) { - case Types.BIT: - case Types.BOOLEAN: - // return Boolean.class; - case Types.TINYINT: - return Byte.TYPE; - case Types.SMALLINT: - return Short.class; - case Types.INTEGER: - return Integer.class; - case Types.BIGINT: - return Long.class; - case Types.DECIMAL: - case Types.NUMERIC: - return BigDecimal.class; - case Types.REAL: - return Float.class; - case Types.FLOAT: - case Types.DOUBLE: - return Double.class; - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - return String.class; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - case Types.BLOB: - return byte[].class; - case Types.DATE: - return Date.class; - case Types.TIME: - return Time.class; - case Types.TIMESTAMP: - return Timestamp.class; - default: - return String.class; - } - } +/** + * 类型转换工具类 + * + * @author machengyuan 2018-8-19 下午06:14:23 + * @version 1.0.0 + */ +public class JdbcTypeUtil { + + private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class); public static Object typeConvert(String tableName, String columnName, String value, int sqlType, String mysqlType) { if (value == null || value.equals("")) { @@ -80,7 +46,7 @@ public class JdbcTypeUtil { res = Long.parseLong(value); } break; - //case Types.BIT: + // case Types.BIT: case Types.BOOLEAN: res = !"0".equals(value); break; @@ -130,7 +96,7 @@ public class JdbcTypeUtil { } return res; } catch (Exception e) { - logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName,value, sqlType); + logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName, value, sqlType); return value; } } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java new file mode 100644 index 00000000..7f3722f1 --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -0,0 +1,113 @@ +package com.alibaba.otter.canal.client.adapter.support; + +import java.util.*; + +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.Message; + +/** + * Message对象解析工具类 + * + * @author machengyuan 2018-8-19 下午06:14:23 + * @version 1.0.0 + */ +public class MessageUtil { + + public static void parse4Dml(Message message, Consumer consumer) { + if (message == null) { + return; + } + List entries = message.getEntries(); + + for (CanalEntry.Entry entry : entries) { + if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN + || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { + continue; + } + + CanalEntry.RowChange rowChange; + try { + rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); + } catch (Exception e) { + throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), + e); + } + + CanalEntry.EventType eventType = rowChange.getEventType(); + + final Dml dml = new Dml(); + dml.setDatabase(entry.getHeader().getSchemaName()); + dml.setTable(entry.getHeader().getTableName()); + dml.setType(eventType.toString()); + dml.setTs(System.currentTimeMillis()); + dml.setSql(rowChange.getSql()); + List> data = new ArrayList<>(); + List> old = new ArrayList<>(); + + if (!rowChange.getIsDdl()) { + Set updateSet = new HashSet<>(); + for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { + if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE + && eventType != CanalEntry.EventType.DELETE) { + continue; + } + + Map row = new LinkedHashMap<>(); + List columns; + + if (eventType == CanalEntry.EventType.DELETE) { + columns = rowData.getBeforeColumnsList(); + } else { + columns = rowData.getAfterColumnsList(); + } + + for (CanalEntry.Column column : columns) { + row.put(column.getName(), + JdbcTypeUtil.typeConvert(dml.getTable(), + column.getName(), + column.getValue(), + column.getSqlType(), + column.getMysqlType())); + // 获取update为true的字段 + if (column.getUpdated()) { + updateSet.add(column.getName()); + } + } + if (!row.isEmpty()) { + data.add(row); + } + + if (eventType == CanalEntry.EventType.UPDATE) { + Map rowOld = new LinkedHashMap<>(); + for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { + if (updateSet.contains(column.getName())) { + rowOld.put(column.getName(), + JdbcTypeUtil.typeConvert(dml.getTable(), + column.getName(), + column.getValue(), + column.getSqlType(), + column.getMysqlType())); + } + } + // update操作将记录修改前的值 + if (!rowOld.isEmpty()) { + old.add(rowOld); + } + } + } + if (!data.isEmpty()) { + dml.setData(data); + } + if (!old.isEmpty()) { + dml.setOld(old); + } + consumer.accept(dml); + } + } + } + + public interface Consumer { + + void accept(T t); + } +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/SPI.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/SPI.java similarity index 55% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/support/SPI.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/SPI.java index 7a731a34..9c0116c6 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/support/SPI.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/SPI.java @@ -1,11 +1,12 @@ -package com.alibaba.otter.canal.client.support; +package com.alibaba.otter.canal.client.adapter.support; import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) +@Target({ ElementType.TYPE }) public @interface SPI { - //Default SPI name + + // Default SPI name String value() default ""; } diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml new file mode 100644 index 00000000..a8ec67a0 --- /dev/null +++ b/client-adapter/hbase/pom.xml @@ -0,0 +1,101 @@ + + + + canal.client-adapter + com.alibaba.otter + 1.1.0-SNAPSHOT + + 4.0.0 + com.alibaba.otter + client-adapter.hbase + jar + canal client adapter hbase module for otter ${project.version} + + + com.alibaba.otter + client-adapter.common + ${project.version} + provided + + + org.yaml + snakeyaml + 1.17 + + + org.apache.hbase + hbase-client + 1.1.2 + + + org.slf4j + slf4j-log4j12 + + + + + com.google.guava + guava + 12.0.1 + + + + + + + maven-jar-plugin + + + true + + + **/hbase-mapping/** + + + + + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java new file mode 100644 index 00000000..03103b8b --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -0,0 +1,115 @@ +package com.alibaba.otter.canal.client.adapter.hbase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; + +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader; +import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService; +import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.MessageUtil; +import com.alibaba.otter.canal.client.adapter.support.SPI; +import com.alibaba.otter.canal.protocol.Message; + +/** + * HBase外部适配器 + * + * @author machengyuan 2018-8-21 下午8:45:38 + * @version 1.0.0 + */ +@SPI("hbase") +public class HbaseAdapter implements CanalOuterAdapter { + + private static volatile Map mappingConfigCache = null; + + private Connection conn; + private HbaseSyncService hbaseSyncService; + + @Override + public void init(CanalOuterAdapterConfiguration configuration) { + try { + if (mappingConfigCache == null) { + synchronized (MappingConfig.class) { + if (mappingConfigCache == null) { + Map hbaseMapping = MappingConfigLoader.load(); + mappingConfigCache = new HashMap<>(); + for (MappingConfig mappingConfig : hbaseMapping.values()) { + mappingConfigCache.put(mappingConfig.getHbaseOrm().getDatabase() + "-" + + mappingConfig.getHbaseOrm().getTable(), + mappingConfig); + } + } + } + } + + String hosts = configuration.getZkHosts(); + if (StringUtils.isEmpty(hosts)) { + hosts = configuration.getHosts(); + } + if (StringUtils.isEmpty(hosts)) { + throw new RuntimeException("Empty zookeeper hosts"); + } + String[] zkHosts = StringUtils.split(hosts, ","); + int zkPort = 0; + StringBuilder hostsWithoutPort = new StringBuilder(); + for (String host : zkHosts) { + int i = host.indexOf(":"); + hostsWithoutPort.append(host, 0, i); + hostsWithoutPort.append(","); + if (zkPort == 0) zkPort = Integer.parseInt(host.substring(i + 1)); + } + hostsWithoutPort.deleteCharAt(hostsWithoutPort.length() - 1); + + String znode = configuration.getProperties().getProperty("znodeParent"); + if (StringUtils.isEmpty(znode)) { + znode = "/hbase"; + } + + Configuration hbaseConfig = HBaseConfiguration.create(); + hbaseConfig.set("hbase.zookeeper.quorum", hostsWithoutPort.toString()); + hbaseConfig.set("hbase.zookeeper.property.clientPort", Integer.toString(zkPort)); + hbaseConfig.set("zookeeper.znode.parent", znode); + conn = ConnectionFactory.createConnection(hbaseConfig); + hbaseSyncService = new HbaseSyncService(conn); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void writeOut(Message message) { + MessageUtil.parse4Dml(message, new MessageUtil.Consumer() { + + @Override + public void accept(Dml dml) { + if (dml == null) { + return; + } + String database = dml.getDatabase(); + String table = dml.getTable(); + MappingConfig config = mappingConfigCache.get(database + "-" + table); + hbaseSyncService.sync(config, dml); + } + }); + } + + @Override + public void destroy() { + if (conn != null) { + try { + conn.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java new file mode 100644 index 00000000..0e4cbfe6 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -0,0 +1,337 @@ +package com.alibaba.otter.canal.client.adapter.hbase.config; + +import java.util.*; + +/** + * HBase表映射配置 + * + * @author machengyuan 2018-8-21 下午06:45:49 + * @version 1.0.0 + */ +public class MappingConfig { + + private HbaseOrm hbaseOrm; + + public HbaseOrm getHbaseOrm() { + return hbaseOrm; + } + + public void setHbaseOrm(HbaseOrm hbaseOrm) { + this.hbaseOrm = hbaseOrm; + } + + public void validate() { + if (hbaseOrm.database == null || hbaseOrm.database.isEmpty()) { + throw new NullPointerException("hbaseOrm.database"); + } + if (hbaseOrm.table == null || hbaseOrm.table.isEmpty()) { + throw new NullPointerException("hbaseOrm.table"); + } + if (hbaseOrm.hbaseTable == null || hbaseOrm.hbaseTable.isEmpty()) { + throw new NullPointerException("hbaseOrm.hbaseTable"); + } + if (hbaseOrm.mode == null) { + throw new NullPointerException("hbaseOrm.mode"); + } + if (hbaseOrm.rowKey != null && hbaseOrm.rowKeyColumn != null) { + throw new RuntimeException("已配置了复合主键作为RowKey,无需再指定RowKey列"); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MappingConfig config = (MappingConfig) o; + + return hbaseOrm != null ? hbaseOrm.equals(config.hbaseOrm) : config.hbaseOrm == null; + } + + @Override + public int hashCode() { + return hbaseOrm != null ? hbaseOrm.hashCode() : 0; + } + + public static class ColumnItem { + + private boolean isRowKey = false; + private String column; + private String family; + private String qualifier; + private String type; + + public boolean isRowKey() { + return isRowKey; + } + + public void setRowKey(boolean rowKey) { + isRowKey = rowKey; + } + + public String getColumn() { + return column; + } + + public void setColumn(String column) { + this.column = column; + } + + public String getFamily() { + return family; + } + + public void setFamily(String family) { + this.family = family; + } + + public String getQualifier() { + return qualifier; + } + + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ColumnItem that = (ColumnItem) o; + return Objects.equals(column, that.column); + } + + @Override + public int hashCode() { + + return Objects.hash(column); + } + } + + public enum Mode { + STRING("STRING"), NATIVE("NATIVE"), PHOENIX("PHOENIX"); + + private String type; + + public String getType() { + return type; + } + + Mode(String type){ + this.type = type; + } + } + + public static class HbaseOrm { + + private Mode mode = Mode.STRING; + private String database; + private String table; + private String hbaseTable; + private String family = "CF"; + private boolean uppercaseQualifier = true; + private boolean autoCreateTable = false; // 同步时HBase中表不存在的情况下自动建表 + private String rowKey; // 指定复合主键为rowKey + private Map columns; + private ColumnItem rowKeyColumn; + private String etlCondition; + + private Map columnItems = new LinkedHashMap<>(); + private Set families = new LinkedHashSet<>(); + private int readBatch = 5000; + private int commitBatch = 5000; + + public Mode getMode() { + return mode; + } + + public void setMode(Mode mode) { + this.mode = mode; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public String getHbaseTable() { + return hbaseTable; + } + + public void setHbaseTable(String hbaseTable) { + this.hbaseTable = hbaseTable; + } + + public Map getColumns() { + return columns; + } + + public boolean isAutoCreateTable() { + return autoCreateTable; + } + + public void setAutoCreateTable(boolean autoCreateTable) { + this.autoCreateTable = autoCreateTable; + } + + public int getReadBatch() { + return readBatch; + } + + public void setReadBatch(int readBatch) { + this.readBatch = readBatch; + } + + public int getCommitBatch() { + return commitBatch; + } + + public void setCommitBatch(int commitBatch) { + this.commitBatch = commitBatch; + } + + public String getRowKey() { + return rowKey; + } + + public void setRowKey(String rowKey) { + this.rowKey = rowKey; + } + + public String getEtlCondition() { + return etlCondition; + } + + public void setEtlCondition(String etlCondition) { + this.etlCondition = etlCondition; + } + + public void setColumns(Map columns) { + this.columns = columns; + + if (columns != null) { + for (Map.Entry columnField : columns.entrySet()) { + String field = columnField.getValue(); + String type = null; + if (field != null) { + // 解析类型 + int i = field.indexOf("$"); + if (i > -1) { + type = field.substring(i + 1); + field = field.substring(0, i); + } + } + ColumnItem columnItem = new ColumnItem(); + columnItem.setColumn(columnField.getKey()); + columnItem.setType(type); + if ("rowKey".equalsIgnoreCase(field)) { + columnItem.setRowKey(true); + rowKeyColumn = columnItem; + } else { + if (field == null || field.equals("")) { + columnItem.setFamily(family); + columnItem.setQualifier(columnField.getKey()); + } else { + int len = field.indexOf(":"); + if (len > -1) { + columnItem.setFamily(field.substring(0, len)); + columnItem.setQualifier(field.substring(len + 1)); + } else { + columnItem.setFamily(family); + columnItem.setQualifier(field); + } + } + if (uppercaseQualifier) { + columnItem.setQualifier(columnItem.getQualifier().toUpperCase()); + } + families.add(columnItem.getFamily()); + } + + columnItems.put(columnField.getKey(), columnItem); + } + } else { + this.columns = new LinkedHashMap<>(); + } + } + + public String getFamily() { + return family; + } + + public void setFamily(String family) { + this.family = family; + if (family == null) { + this.family = "CF"; + } + } + + public boolean isUppercaseQualifier() { + return uppercaseQualifier; + } + + public void setUppercaseQualifier(boolean uppercaseQualifier) { + this.uppercaseQualifier = uppercaseQualifier; + } + + public ColumnItem getRowKeyColumn() { + return rowKeyColumn; + } + + public void setRowKeyColumn(ColumnItem rowKeyColumn) { + this.rowKeyColumn = rowKeyColumn; + } + + public Map getColumnItems() { + return columnItems; + } + + public void setColumnItems(Map columnItems) { + this.columnItems = columnItems; + } + + public Set getFamilies() { + return families; + } + + public void setFamilies(Set families) { + this.families = families; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HbaseOrm hbaseOrm = (HbaseOrm) o; + + if (table != null ? !table.equals(hbaseOrm.table) : hbaseOrm.table != null) return false; + return hbaseTable != null ? hbaseTable.equals(hbaseOrm.hbaseTable) : hbaseOrm.hbaseTable == null; + } + + @Override + public int hashCode() { + int result = table != null ? table.hashCode() : 0; + result = 31 * result + (hbaseTable != null ? hbaseTable.hashCode() : 0); + return result; + } + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java new file mode 100644 index 00000000..f00e6282 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -0,0 +1,149 @@ +package com.alibaba.otter.canal.client.adapter.hbase.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +/** + * HBase表映射配置加载器 + *

      + * 配置统一从hbase-mapping/configs.conf文件作为入口, 该文件包含所有表映射配置的名称或者文件名列表。 + * 每个对应的表配置可以yml配置文件或者以database.table为配置名的简化形式 + *

      + * + * @author machengyuan 2018-8-21 下午06:45:49 + * @version 1.0.0 + */ +public class MappingConfigLoader { + + private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class); + + private static final String BASE_PATH = "hbase-mapping/"; + + /** + * 加载HBase表映射配置 + * + * @return 配置名/配置文件名--对象 + */ + public static Map load() { + logger.info("## Start loading mapping config ... "); + String mappingConfigContent = readConfigContent(BASE_PATH + "configs.conf"); + + Map result = new LinkedHashMap<>(); + + String[] configLines = mappingConfigContent.split("\n"); + for (String c : configLines) { + if (c == null) { + continue; + } + c = c.trim(); + if (c.startsWith("#")) { + continue; + } + + MappingConfig config; + String configContent = null; + + if (c.endsWith(".yml")) { + configContent = readConfigContent(BASE_PATH + "/" + c); + } + + // 简单配置database.table@datasourcekey?rowKey=key1,key2 + if (StringUtils.isEmpty(configContent)) { + String[] mapping = c.split("\\?"); + String params = mapping.length == 2 ? mapping[1] : null; + String rowKey = null; + String srcMeta = mapping[0]; + // + if (params != null) { + for (String entry : params.split("&")) { + if ("rowKey".equals(entry.split("=")[0])) { + rowKey = entry.split("=")[1]; + } + } + } + String dsKey = srcMeta.split("@").length == 2 ? srcMeta.split("@")[1] : null; + String[] dbTable; + if (dsKey == null) { + dbTable = srcMeta.split("\\."); + } else { + dbTable = srcMeta.split("@")[0].split("\\."); + } + + if (dbTable.length == 2) { + config = new MappingConfig(); + + MappingConfig.HbaseOrm hbaseOrm = new MappingConfig.HbaseOrm(); + hbaseOrm.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase()); + hbaseOrm.setAutoCreateTable(true); + hbaseOrm.setDatabase(dbTable[0]); + hbaseOrm.setTable(dbTable[1]); + hbaseOrm.setMode(MappingConfig.Mode.STRING); + hbaseOrm.setRowKey(rowKey); + // 有定义rowKey + if (rowKey != null) { + MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem(); + columnItem.setRowKey(true); + columnItem.setColumn(rowKey); + hbaseOrm.setRowKeyColumn(columnItem); + } + config.setHbaseOrm(hbaseOrm); + + } else { + throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c)); + } + + } else { // 配置文件配置 + config = new Yaml().loadAs(configContent, MappingConfig.class); + } + + try { + config.validate(); + } catch (Exception e) { + throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e); + } + result.put(c, config); + } + + logger.info("## Mapping config loaded"); + return result; + } + + public static String readConfigContent(String config) { + InputStream in = null; + try { + // 先取本地文件,再取类路径 + File configFile = new File("config/" + config); + if (configFile.exists()) { + in = new FileInputStream(configFile); + } else { + in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config); + } + if (in == null) { + throw new RuntimeException("Config file not found."); + } + + byte[] bytes = new byte[in.available()]; + in.read(bytes); + return new String(bytes, "UTF-8"); + } catch (IOException e) { + throw new RuntimeException("Read ds-config.yml or hbase-mappings.conf error. ", e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + // ignore + } + } + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java new file mode 100644 index 00000000..13f6a3ac --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -0,0 +1,418 @@ +package com.alibaba.otter.canal.client.adapter.hbase.service; + +import java.util.*; + +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.util.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.hbase.support.*; +import com.alibaba.otter.canal.client.adapter.support.Dml; + +/** + * HBase同步操作业务 + * + * @author machengyuan 2018-8-21 下午06:45:49 + * @version 1.0.0 + */ +public class HbaseSyncService { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + private HbaseTemplate hbaseTemplate; // HBase操作模板 + + public HbaseSyncService(Connection conn){ + hbaseTemplate = new HbaseTemplate(conn); + } + + public void sync(MappingConfig config, Dml dml) { + try { + if (config != null) { + String type = dml.getType(); + if (type != null && type.equalsIgnoreCase("INSERT")) { + insert(config, dml); + } else if (type != null && type.equalsIgnoreCase("UPDATE")) { + update(config, dml); + } else if (type != null && type.equalsIgnoreCase("DELETE")) { + delete(config, dml); + } + if (logger.isDebugEnabled()) { + String res = dml.toString(); + logger.debug(res); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + /** + * 插入操作 + * + * @param config 配置项 + * @param dml DML数据 + */ + private void insert(MappingConfig config, Dml dml) { + List> data = dml.getData(); + if (data == null || data.isEmpty()) { + return; + } + + MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + + // if (!validHTable(config)) { + // logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable()); + // return; + // } + int i = 1; + boolean complete = false; + List rows = new ArrayList<>(); + for (Map r : data) { + HRow hRow = new HRow(); + + // 拼接复合rowKey + if (hbaseOrm.getRowKey() != null) { + String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + String rowKeyVale = getRowKeys(rowKeyColumns, r); + // params.put("rowKey", Bytes.toBytes(rowKeyVale)); + hRow.setRowKey(Bytes.toBytes(rowKeyVale)); + } + + convertData2Row(hbaseOrm, hRow, r); + if (hRow.getRowKey() == null) { + throw new RuntimeException("empty rowKey"); + } + rows.add(hRow); + complete = false; + if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + rows.clear(); + complete = true; + } + i++; + } + if (!complete && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + } + + } + + /** + * 将Map数据转换为HRow行数据 + * + * @param hbaseOrm hbase映射配置 + * @param hRow 行对象 + * @param data Map数据 + */ + private static void convertData2Row(MappingConfig.HbaseOrm hbaseOrm, HRow hRow, Map data) { + Map columnItems = hbaseOrm.getColumnItems(); + int i = 0; + for (Map.Entry entry : data.entrySet()) { + if (entry.getValue() != null) { + MappingConfig.ColumnItem columnItem = columnItems.get(entry.getKey()); + + byte[] bytes = typeConvert(columnItem, hbaseOrm, entry.getValue()); + + if (columnItem == null) { + String familyName = hbaseOrm.getFamily(); + String qualifier = entry.getKey(); + if (hbaseOrm.isUppercaseQualifier()) { + qualifier = qualifier.toUpperCase(); + } + + if (hbaseOrm.getRowKey() == null && i == 0) { + hRow.setRowKey(bytes); + } else { + hRow.addCell(familyName, qualifier, bytes); + } + } else { + if (columnItem.isRowKey()) { + // row.put("rowKey", bytes); + hRow.setRowKey(bytes); + } else { + hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), bytes); + } + } + } + i++; + } + } + + /** + * 更新操作 + * + * @param config + * @param dml + */ + private void update(MappingConfig config, Dml dml) { + List> data = dml.getData(); + List> old = dml.getOld(); + if (old == null || old.isEmpty() || data == null || data.isEmpty()) { + return; + } + + MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + + // if (!validHTable(config)) { + // logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable()); + // return; + // } + + MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn(); + int index = 0; + int i = 1; + boolean complete = false; + List rows = new ArrayList<>(); + out: for (Map r : data) { + byte[] rowKeyBytes; + + if (hbaseOrm.getRowKey() != null) { + String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + + // 判断是否有复合主键修改 + for (String updateColumn : old.get(index).keySet()) { + for (String rowKeyColumnName : rowKeyColumns) { + if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) { + // 调用删除插入操作 + deleteAndInsert(config, dml); + continue out; + } + } + } + + String rowKeyVale = getRowKeys(rowKeyColumns, r); + rowKeyBytes = Bytes.toBytes(rowKeyVale); + } else if (rowKeyColumn == null) { + Map rowKey = data.get(0); + rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next()); + } else { + rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, r.get(rowKeyColumn.getColumn())); + } + if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空"); + + Map columnItems = hbaseOrm.getColumnItems(); + HRow hRow = new HRow(rowKeyBytes); + for (String updateColumn : old.get(index).keySet()) { + MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn); + if (columnItem == null) { + String family = hbaseOrm.getFamily(); + String qualifier = updateColumn; + if (hbaseOrm.isUppercaseQualifier()) { + qualifier = qualifier.toUpperCase(); + } + + Object newVal = r.get(updateColumn); + + if (newVal == null) { + hRow.addCell(family, qualifier, null); + } else { + hRow.addCell(family, qualifier, typeConvert(null, hbaseOrm, newVal)); + } + } else { + // 排除修改id的情况 + if (columnItem.isRowKey()) continue; + + Object newVal = r.get(updateColumn); + if (newVal == null) { + hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), null); + } else { + hRow.addCell(columnItem.getFamily(), + columnItem.getQualifier(), + typeConvert(columnItem, hbaseOrm, newVal)); + } + } + } + rows.add(hRow); + complete = false; + if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + rows.clear(); + complete = true; + } + i++; + index++; + } + if (!complete && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + } + } + + private void delete(MappingConfig config, Dml dml) { + List> data = dml.getData(); + if (data == null || data.isEmpty()) { + return; + } + + MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + + // if (!validHTable(config)) { + // logger.error("HBase table '{}' not exists", hbaseOrm.getHbaseTable()); + // return; + // } + + MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn(); + boolean complete = false; + int i = 1; + Set rowKeys = new HashSet<>(); + for (Map r : data) { + byte[] rowKeyBytes; + + if (hbaseOrm.getRowKey() != null) { + String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + String rowKeyVale = getRowKeys(rowKeyColumns, r); + rowKeyBytes = Bytes.toBytes(rowKeyVale); + } else if (rowKeyColumn == null) { + // 如果不需要类型转换 + Map rowKey = data.get(0); + rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next()); + } else { + Object val = r.get(rowKeyColumn.getColumn()); + rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, val); + } + if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空"); + rowKeys.add(rowKeyBytes); + complete = false; + if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rowKeys.isEmpty()) { + hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); + rowKeys.clear(); + complete = true; + } + i++; + } + if (!complete && !rowKeys.isEmpty()) { + hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); + } + } + + private void deleteAndInsert(MappingConfig config, Dml dml) { + List> data = dml.getData(); + List> old = dml.getOld(); + if (old == null || old.isEmpty() || data == null || data.isEmpty()) { + return; + } + MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + + String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + + int index = 0; + int i = 1; + boolean complete = false; + Set rowKeys = new HashSet<>(); + List rows = new ArrayList<>(); + for (Map r : data) { + // 拼接老的rowKey + List updateSubRowKey = new ArrayList<>(); + for (String rowKeyColumnName : rowKeyColumns) { + for (String updateColumn : old.get(index).keySet()) { + if (rowKeyColumnName.equalsIgnoreCase(updateColumn)) { + updateSubRowKey.add(rowKeyColumnName); + } + } + } + if (updateSubRowKey.isEmpty()) { + throw new RuntimeException("没有更新复合主键的RowKey"); + } + StringBuilder oldRowKey = new StringBuilder(); + StringBuilder newRowKey = new StringBuilder(); + for (String rowKeyColumnName : rowKeyColumns) { + newRowKey.append(r.get(rowKeyColumnName).toString()).append("|"); + if (!updateSubRowKey.contains(rowKeyColumnName)) { + // 从data取 + oldRowKey.append(r.get(rowKeyColumnName).toString()).append("|"); + } else { + // 从old取 + oldRowKey.append(old.get(index).get(rowKeyColumnName).toString()).append("|"); + } + } + int len = newRowKey.length(); + newRowKey.delete(len - 1, len); + len = oldRowKey.length(); + oldRowKey.delete(len - 1, len); + byte[] newRowKeyBytes = Bytes.toBytes(newRowKey.toString()); + byte[] oldRowKeyBytes = Bytes.toBytes(oldRowKey.toString()); + + rowKeys.add(oldRowKeyBytes); + HRow row = new HRow(newRowKeyBytes); + convertData2Row(hbaseOrm, row, r); + rows.add(row); + complete = false; + if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); + + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + rowKeys.clear(); + rows.clear(); + complete = true; + } + i++; + index++; + } + if (!complete && !rows.isEmpty()) { + hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + } + } + + /** + * 根据对应的类型进行转换 + * + * @param columnItem 列项配置 + * @param hbaseOrm hbase映射配置 + * @param value 值 + * @return 复合字段rowKey + */ + private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseOrm hbaseOrm, + Object value) { + if (value == null) { + return null; + } + byte[] bytes = null; + if (columnItem == null || columnItem.getType() == null || "".equals(columnItem.getType())) { + if (MappingConfig.Mode.STRING == hbaseOrm.getMode()) { + bytes = Bytes.toBytes(value.toString()); + } else if (MappingConfig.Mode.NATIVE == hbaseOrm.getMode()) { + bytes = TypeUtil.toBytes(value); + } else if (MappingConfig.Mode.PHOENIX == hbaseOrm.getMode()) { + PhType phType = PhType.getType(value.getClass()); + bytes = PhTypeUtil.toBytes(value, phType); + } + } else { + if (hbaseOrm.getMode() == MappingConfig.Mode.STRING) { + bytes = Bytes.toBytes(value.toString()); + } else if (hbaseOrm.getMode() == MappingConfig.Mode.NATIVE) { + Type type = Type.getType(columnItem.getType()); + bytes = TypeUtil.toBytes(value, type); + } else if (hbaseOrm.getMode() == MappingConfig.Mode.PHOENIX) { + PhType phType = PhType.getType(columnItem.getType()); + bytes = PhTypeUtil.toBytes(value, phType); + } + } + return bytes; + } + + /** + * 获取复合字段作为rowKey的拼接 + * + * @param rowKeyColumns 复合rowK对应的字段 + * @param data 数据 + * @return + */ + private static String getRowKeys(String[] rowKeyColumns, Map data) { + StringBuilder rowKeyValue = new StringBuilder(); + for (String rowKeyColumnName : rowKeyColumns) { + Object obj = data.get(rowKeyColumnName); + if (obj != null) { + rowKeyValue.append(obj.toString()); + } + rowKeyValue.append("|"); + } + int len = rowKeyValue.length(); + if (len > 0) { + rowKeyValue.delete(len - 1, len); + } + return rowKeyValue.toString(); + } + +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HRow.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HRow.java new file mode 100644 index 00000000..adeb2ff5 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HRow.java @@ -0,0 +1,84 @@ +package com.alibaba.otter.canal.client.adapter.hbase.support; + +import java.util.ArrayList; +import java.util.List; + +/** + * HBase操作对象类 + * + * @author machengyuan 2018-8-21 下午10:12:34 + * @version 1.0.0 + */ +public class HRow { + + private byte[] rowKey; + private List cells = new ArrayList<>(); + + public HRow(){ + } + + public HRow(byte[] rowKey){ + this.rowKey = rowKey; + } + + public byte[] getRowKey() { + return rowKey; + } + + public void setRowKey(byte[] rowKey) { + this.rowKey = rowKey; + } + + public List getCells() { + return cells; + } + + public void setCells(List cells) { + this.cells = cells; + } + + public void addCell(String family, String qualifier, byte[] value) { + HCell hCell = new HCell(family, qualifier, value); + cells.add(hCell); + } + + public static class HCell { + + private String family; + private String qualifier; + private byte[] value; + + public HCell(){ + } + + public HCell(String family, String qualifier, byte[] value){ + this.family = family; + this.qualifier = qualifier; + this.value = value; + } + + public String getFamily() { + return family; + } + + public void setFamily(String family) { + this.family = family; + } + + public String getQualifier() { + return qualifier; + } + + public void setQualifier(String qualifier) { + this.qualifier = qualifier; + } + + public byte[] getValue() { + return value; + } + + public void setValue(byte[] value) { + this.value = value; + } + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HbaseTemplate.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HbaseTemplate.java new file mode 100644 index 00000000..62b7ca03 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/HbaseTemplate.java @@ -0,0 +1,159 @@ +package com.alibaba.otter.canal.client.adapter.hbase.support; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.TableName; +import org.apache.hadoop.hbase.client.*; +import org.apache.hadoop.hbase.util.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * HBase操作模板 + * + * @author machengyuan 2018-8-21 下午10:12:34 + * @version 1.0.0 + */ +public class HbaseTemplate { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + private Connection conn; + + public HbaseTemplate(Connection conn){ + this.conn = conn; + } + + public boolean tableExists(String tableName) { + try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) { + + return admin.tableExists(TableName.valueOf(tableName)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void createTable(String tableName, String... familyNames) { + try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) { + + HTableDescriptor desc = new HTableDescriptor(TableName.valueOf(tableName)); + // 添加列簇 + if (familyNames != null) { + for (String familyName : familyNames) { + HColumnDescriptor hcd = new HColumnDescriptor(familyName); + desc.addFamily(hcd); + } + } + admin.createTable(desc); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void disableTable(String tableName) { + try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) { + admin.disableTable(tableName); + } catch (IOException e) { + logger.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + public void deleteTable(String tableName) { + try (HBaseAdmin admin = (HBaseAdmin) conn.getAdmin()) { + if (admin.isTableEnabled(tableName)) { + disableTable(tableName); + } + admin.deleteTable(tableName); + } catch (IOException e) { + logger.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + /** + * 插入一行数据 + * + * @param tableName 表名 + * @param hRow 行数据对象 + * @return 是否成功 + */ + public Boolean put(String tableName, HRow hRow) { + boolean flag = false; + try { + HTable table = (HTable) conn.getTable(TableName.valueOf(tableName)); + Put put = new Put(hRow.getRowKey()); + for (HRow.HCell hCell : hRow.getCells()) { + put.addColumn(Bytes.toBytes(hCell.getFamily()), Bytes.toBytes(hCell.getQualifier()), hCell.getValue()); + } + table.put(put); + flag = true; + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return flag; + + } + + /** + * 批量插入 + * + * @param tableName 表名 + * @param rows 行数据对象集合 + * @return 是否成功 + */ + public Boolean puts(String tableName, List rows) { + boolean flag = false; + try { + HTable table = (HTable) conn.getTable(TableName.valueOf(tableName)); + List puts = new ArrayList<>(); + for (HRow hRow : rows) { + Put put = new Put(hRow.getRowKey()); + for (HRow.HCell hCell : hRow.getCells()) { + put.addColumn(Bytes.toBytes(hCell.getFamily()), + Bytes.toBytes(hCell.getQualifier()), + hCell.getValue()); + } + puts.add(put); + } + if (!puts.isEmpty()) { + table.put(puts); + } + flag = true; + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return flag; + } + + /** + * 批量删除数据 + * + * @param tableName 表名 + * @param rowKeys rowKey集合 + * @return 是否成功 + */ + public Boolean deletes(String tableName, Set rowKeys) { + boolean flag = false; + try { + HTable table = (HTable) conn.getTable(TableName.valueOf(tableName)); + List deletes = new ArrayList<>(); + for (byte[] rowKey : rowKeys) { + Delete delete = new Delete(rowKey); + deletes.add(delete); + } + if (!deletes.isEmpty()) { + table.delete(deletes); + } + flag = true; + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return flag; + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java new file mode 100644 index 00000000..09c63a6c --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java @@ -0,0 +1,150 @@ +package com.alibaba.otter.canal.client.adapter.hbase.support; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Date; + +/** + * Phoenix类型 + * + * @author machengyuan 2018-8-21 下午06:12:34 + * @version 1.0.0 + */ +public enum PhType { + DEFAULT(-1, "VARCHAR"), + UNSIGNED_INT(4, "UNSIGNED_INT"), + UNSIGNED_LONG(8, "UNSIGNED_LONG"), + UNSIGNED_TINYINT(1, "UNSIGNED_TINYINT"), + UNSIGNED_SMALLINT(2, "UNSIGNED_SMALLINT"), + UNSIGNED_FLOAT(4, "UNSIGNED_FLOAT"), + UNSIGNED_DOUBLE(8, "UNSIGNED_DOUBLE"), + INTEGER(4, "INTEGER"), + BIGINT(8, "BIGINT"), + TINYINT(1, "TINYINT"), + SMALLINT(2, "SMALLINT"), + FLOAT(4, "FLOAT"), + DOUBLE(8, "DOUBLE"), + DECIMAL(-1, "DECIMAL"), + BOOLEAN(1, "BOOLEAN"), + UNSIGNED_TIME(8, "UNSIGNED_TIME"), + UNSIGNED_DATE(8, "UNSIGNED_DATE"), + UNSIGNED_TIMESTAMP(12, "UNSIGNED_TIMESTAMP"), + TIME(8, "TIME"), + DATE(8, "DATE"), + TIMESTAMP(12, "TIMESTAMP"), + VARCHAR(-1, "VARCHAR"), + VARBINARY(-1, "VARBINARY"); + + /** + * -1:长度可变 + */ + private int len; + private String type; + + PhType(int len, String type) { + this.len = len; + this.type = type; + } + + public int getLen() { + return len; + } + + public String getType() { + return this.type; + } + + public static PhType getType(Class javaType) { + if (javaType == null) return DEFAULT; + PhType phType; + if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) { + phType = INTEGER; + } else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) { + phType = BIGINT; + } else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) { + phType = TINYINT; + } else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) { + phType = SMALLINT; + } else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) { + phType = FLOAT; + } else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) { + phType = DOUBLE; + } else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) { + phType = BOOLEAN; + } else if (java.sql.Date.class.isAssignableFrom(javaType)) { + phType = DATE; + } else if (Time.class.isAssignableFrom(javaType)) { + phType = DATE; + } else if (Timestamp.class.isAssignableFrom(javaType)) { + phType = TIMESTAMP; + } else if (Date.class.isAssignableFrom(javaType)) { + phType = DATE; + } else if (byte[].class.isAssignableFrom(javaType)) { + phType = VARBINARY; + } else if (String.class.isAssignableFrom(javaType)) { + phType = VARCHAR; + } else if (BigDecimal.class.isAssignableFrom(javaType)) { + phType = DECIMAL; + } else if (BigInteger.class.isAssignableFrom(javaType)) { + phType = UNSIGNED_LONG; + } else { + phType = DEFAULT; + } + return phType; + } + + public static PhType getType(String type) { + if (type == null) return DEFAULT; + PhType phType; + if (type.equalsIgnoreCase(UNSIGNED_INT.type)) { + phType = UNSIGNED_INT; + } else if (type.equalsIgnoreCase(UNSIGNED_LONG.type)) { + phType = UNSIGNED_LONG; + } else if (type.equalsIgnoreCase(UNSIGNED_TINYINT.type)) { + phType = UNSIGNED_TINYINT; + } else if (type.equalsIgnoreCase(UNSIGNED_SMALLINT.type)) { + phType = UNSIGNED_SMALLINT; + } else if (type.equalsIgnoreCase(UNSIGNED_FLOAT.type)) { + phType = UNSIGNED_FLOAT; + } else if (type.equalsIgnoreCase(UNSIGNED_DOUBLE.type)) { + phType = UNSIGNED_DOUBLE; + } else if (type.equalsIgnoreCase(INTEGER.type)) { + phType = INTEGER; + } else if (type.equalsIgnoreCase(BIGINT.type)) { + phType = BIGINT; + } else if (type.equalsIgnoreCase(TINYINT.type)) { + phType = TINYINT; + } else if (type.equalsIgnoreCase(SMALLINT.type)) { + phType = SMALLINT; + } else if (type.equalsIgnoreCase(FLOAT.type)) { + phType = FLOAT; + } else if (type.equalsIgnoreCase(DOUBLE.type)) { + phType = DOUBLE; + } else if (type.equalsIgnoreCase(BOOLEAN.type)) { + phType = BOOLEAN; + } else if (type.equalsIgnoreCase(UNSIGNED_TIME.type)) { + phType = UNSIGNED_TIME; + } else if (type.equalsIgnoreCase(UNSIGNED_DATE.type)) { + phType = UNSIGNED_DATE; + } else if (type.equalsIgnoreCase(UNSIGNED_TIMESTAMP.type)) { + phType = UNSIGNED_TIMESTAMP; + } else if (type.equalsIgnoreCase(TIME.type)) { + phType = TIME; + } else if (type.equalsIgnoreCase(DATE.type)) { + phType = DATE; + } else if (type.equalsIgnoreCase(TIMESTAMP.type)) { + phType = TIMESTAMP; + } else if (type.equalsIgnoreCase(VARCHAR.type)) { + phType = VARCHAR; + } else if (type.equalsIgnoreCase(VARBINARY.type)) { + phType = VARBINARY; + } else if (type.equalsIgnoreCase(DECIMAL.type)) { + phType = DECIMAL; + } else { + phType = DEFAULT; + } + return phType; + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhTypeUtil.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhTypeUtil.java new file mode 100644 index 00000000..25473681 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhTypeUtil.java @@ -0,0 +1,614 @@ +package com.alibaba.otter.canal.client.adapter.hbase.support; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.MathContext; +import java.math.RoundingMode; +import java.sql.Timestamp; +import java.util.Date; + +import org.apache.hadoop.hbase.util.Bytes; +import org.joda.time.DateTime; + +import com.google.common.math.LongMath; + +/** + * Phoenix类型转换工具类 + * + * @author machengyuan 2018-8-21 下午06:14:26 + * @version 1.0.0 + */ +public class PhTypeUtil { + + public static byte[] toBytes(Object v, PhType phType) { + if (v == null) return null; + byte[] b = null; + if (phType == PhType.DEFAULT) { + PhType phType1 = PhType.getType(v.getClass()); + if (phType1 != null && phType1 != PhType.DEFAULT) { + toBytes(v, phType1); + } + } else if (phType == PhType.INTEGER) { + b = new byte[Bytes.SIZEOF_INT]; + encodeInt(((Number) v).intValue(), b, 0); + } else if (phType == PhType.UNSIGNED_INT) { + b = new byte[Bytes.SIZEOF_INT]; + encodeUnsignedInt(((Number) v).intValue(), b, 0); + } else if (phType == PhType.BIGINT) { + b = new byte[Bytes.SIZEOF_LONG]; + encodeLong(((Number) v).longValue(), b, 0); + } else if (phType == PhType.UNSIGNED_LONG) { + b = new byte[Bytes.SIZEOF_LONG]; + encodeUnsignedLong(((Number) v).longValue(), b, 0); + } else if (phType == PhType.SMALLINT) { + b = new byte[Bytes.SIZEOF_SHORT]; + encodeShort(((Number) v).shortValue(), b, 0); + } else if (phType == PhType.UNSIGNED_SMALLINT) { + b = new byte[Bytes.SIZEOF_SHORT]; + encodeUnsignedShort(((Number) v).shortValue(), b, 0); + } else if (phType == PhType.TINYINT) { + b = new byte[Bytes.SIZEOF_BYTE]; + encodeByte(((Number) v).byteValue(), b, 0); + } else if (phType == PhType.UNSIGNED_TINYINT) { + b = new byte[Bytes.SIZEOF_BYTE]; + encodeUnsignedByte(((Number) v).byteValue(), b, 0); + } else if (phType == PhType.FLOAT) { + b = new byte[Bytes.SIZEOF_FLOAT]; + encodeFloat(((Number) v).floatValue(), b, 0); + } else if (phType == PhType.UNSIGNED_FLOAT) { + b = new byte[Bytes.SIZEOF_FLOAT]; + encodeUnsignedFloat(((Number) v).floatValue(), b, 0); + } else if (phType == PhType.DOUBLE) { + b = new byte[Bytes.SIZEOF_DOUBLE]; + encodeDouble(((Number) v).doubleValue(), b, 0); + } else if (phType == PhType.UNSIGNED_DOUBLE) { + b = new byte[Bytes.SIZEOF_DOUBLE]; + encodeUnsignedDouble(((Number) v).doubleValue(), b, 0); + } else if (phType == PhType.BOOLEAN) { + if ((Boolean) v) { + b = new byte[] { 1 }; + } else { + b = new byte[] { 0 }; + } + } else if (phType == PhType.TIME || phType == PhType.DATE) { + b = new byte[Bytes.SIZEOF_LONG]; + encodeDate(v, b, 0); + } else if (phType == PhType.TIMESTAMP) { + b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT]; + encodeTimestamp(v, b, 0); + } else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) { + b = new byte[Bytes.SIZEOF_LONG]; + encodeUnsignedDate(v, b, 0); + } else if (phType == PhType.UNSIGNED_TIMESTAMP) { + b = new byte[Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT]; + encodeUnsignedTimestamp(v, b, 0); + } else if (phType == PhType.VARBINARY) { + b = (byte[]) v; + } else if (phType == PhType.VARCHAR) { + b = Bytes.toBytes(v.toString()); + } else if (phType == PhType.DECIMAL) { + if (v instanceof BigDecimal) { + b = encodeDecimal(v); + } else if (v instanceof Number) { + b = encodeDecimal(new BigDecimal(v.toString())); + } + } + return b; + } + + public static Object toObject(byte[] b, PhType phType) { + if (b == null) return null; + Object v = null; + if (phType == PhType.INTEGER) { + v = decodeInt(b, 0); + } else if (phType == PhType.UNSIGNED_INT) { + v = decodeUnsignedInt(b, 0); + } else if (phType == PhType.BIGINT) { + v = decodeLong(b, 0); + } else if (phType == PhType.UNSIGNED_LONG) { + v = decodeUnsignedLong(b, 0); + } else if (phType == PhType.SMALLINT) { + v = decodeShort(b, 0); + } else if (phType == PhType.UNSIGNED_SMALLINT) { + v = decodeUnsignedShort(b, 0); + } else if (phType == PhType.TINYINT) { + v = decodeByte(b, 0); + } else if (phType == PhType.UNSIGNED_TINYINT) { + v = decodeUnsignedByte(b, 0); + } else if (phType == PhType.FLOAT) { + v = decodeFloat(b, 0); + } else if (phType == PhType.UNSIGNED_FLOAT) { + v = decodeUnsignedFloat(b, 0); + } else if (phType == PhType.DOUBLE) { + v = decodeDouble(b, 0); + } else if (phType == PhType.UNSIGNED_DOUBLE) { + v = decodeUnsignedDouble(b, 0); + } else if (phType == PhType.BOOLEAN) { + checkForSufficientLength(b, 0, Bytes.SIZEOF_BOOLEAN); + if (b[0] == 1) { + v = true; + } else if (b[0] == 0) { + v = false; + } + } else if (phType == PhType.TIME || phType == PhType.DATE) { + v = new Date(decodeLong(b, 0)); + } else if (phType == PhType.TIMESTAMP) { + long millisDeserialized = decodeLong(b, 0); + Timestamp ts = new Timestamp(millisDeserialized); + int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG); + ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized); + v = ts; + } else if (phType == PhType.UNSIGNED_TIME || phType == PhType.UNSIGNED_DATE) { + v = new Date(decodeUnsignedLong(b, 0)); + } else if (phType == PhType.UNSIGNED_TIMESTAMP) { + long millisDeserialized = decodeUnsignedLong(b, 0); + Timestamp ts = new Timestamp(millisDeserialized); + int nanosDeserialized = decodeUnsignedInt(b, Bytes.SIZEOF_LONG); + ts.setNanos(nanosDeserialized < 1000000 ? ts.getNanos() + nanosDeserialized : nanosDeserialized); + v = ts; + } else if (phType == PhType.VARBINARY) { + v = b; + } else if (phType == PhType.VARCHAR || phType == PhType.DEFAULT) { + v = Bytes.toString(b); + } else if (phType == PhType.DECIMAL) { + v = decodeDecimal(b, 0, b.length); + } + + return v; + } + + private static int decodeInt(byte[] bytes, int o) { + checkForSufficientLength(bytes, o, Bytes.SIZEOF_INT); + int v; + v = bytes[o] ^ 0x80; // Flip sign bit back + for (int i = 1; i < Bytes.SIZEOF_INT; i++) { + v = (v << 8) + (bytes[o + i] & 0xff); + } + return v; + } + + private static int encodeInt(int v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_INT); + b[o + 0] = (byte) ((v >> 24) ^ 0x80); // Flip sign bit so that INTEGER is binary comparable + b[o + 1] = (byte) (v >> 16); + b[o + 2] = (byte) (v >> 8); + b[o + 3] = (byte) v; + return Bytes.SIZEOF_INT; + } + + private static int decodeUnsignedInt(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_INT); + + int v = Bytes.toInt(b, o); + if (v < 0) { + throw new RuntimeException(); + } + return v; + } + + private static int encodeUnsignedInt(int v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_INT); + if (v < 0) { + throw new RuntimeException(); + } + Bytes.putInt(b, o, v); + return Bytes.SIZEOF_INT; + } + + private static long decodeLong(byte[] bytes, int o) { + checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG); + long v; + byte b = bytes[o]; + v = b ^ 0x80; // Flip sign bit back + for (int i = 1; i < Bytes.SIZEOF_LONG; i++) { + b = bytes[o + i]; + v = (v << 8) + (b & 0xff); + } + return v; + } + + private static int encodeLong(long v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_LONG); + b[o + 0] = (byte) ((v >> 56) ^ 0x80); // Flip sign bit so that INTEGER is binary comparable + b[o + 1] = (byte) (v >> 48); + b[o + 2] = (byte) (v >> 40); + b[o + 3] = (byte) (v >> 32); + b[o + 4] = (byte) (v >> 24); + b[o + 5] = (byte) (v >> 16); + b[o + 6] = (byte) (v >> 8); + b[o + 7] = (byte) v; + return Bytes.SIZEOF_LONG; + } + + private static long decodeUnsignedLong(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_LONG); + long v = 0; + for (int i = o; i < o + Bytes.SIZEOF_LONG; i++) { + v <<= 8; + v ^= b[i] & 0xFF; + } + + if (v < 0) { + throw new RuntimeException(); + } + return v; + } + + private static int encodeUnsignedLong(long v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_LONG); + if (v < 0) { + throw new RuntimeException(); + } + Bytes.putLong(b, o, v); + return Bytes.SIZEOF_LONG; + } + + private static short decodeShort(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT); + int v; + v = b[o] ^ 0x80; // Flip sign bit back + for (int i = 1; i < Bytes.SIZEOF_SHORT; i++) { + v = (v << 8) + (b[o + i] & 0xff); + } + return (short) v; + } + + private static int encodeShort(short v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT); + b[o + 0] = (byte) ((v >> 8) ^ 0x80); // Flip sign bit so that Short is binary comparable + b[o + 1] = (byte) v; + return Bytes.SIZEOF_SHORT; + } + + private static short decodeUnsignedShort(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT); + short v = Bytes.toShort(b, o); + if (v < 0) { + throw new RuntimeException(); + } + return v; + } + + private static int encodeUnsignedShort(short v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_SHORT); + if (v < 0) { + throw new RuntimeException(); + } + Bytes.putShort(b, o, v); + return Bytes.SIZEOF_SHORT; + } + + private static byte decodeByte(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE); + int v; + v = b[o] ^ 0x80; // Flip sign bit back + return (byte) v; + } + + private static int encodeByte(byte v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE); + b[o] = (byte) (v ^ 0x80); // Flip sign bit so that Short is binary comparable + return Bytes.SIZEOF_BYTE; + } + + private static byte decodeUnsignedByte(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_BYTE); + byte v = b[o]; + if (v < 0) { + throw new RuntimeException(); + } + return v; + } + + private static int encodeUnsignedByte(byte v, byte[] b, int o) { + if (v < 0) { + throw new RuntimeException(); + } + Bytes.putByte(b, o, v); + return Bytes.SIZEOF_BYTE; + } + + private static float decodeFloat(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_INT); + int value; + value = Bytes.toInt(b, o); + value--; + value ^= (~value >> Integer.SIZE - 1) | Integer.MIN_VALUE; + return Float.intBitsToFloat(value); + } + + private static int encodeFloat(float v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT); + int i = Float.floatToIntBits(v); + i = (i ^ ((i >> Integer.SIZE - 1) | Integer.MIN_VALUE)) + 1; + Bytes.putInt(b, o, i); + return Bytes.SIZEOF_FLOAT; + } + + private static float decodeUnsignedFloat(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT); + float v = Bytes.toFloat(b, o); + if (v < 0) { + throw new RuntimeException(); + } + return v; + } + + private static int encodeUnsignedFloat(float v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_FLOAT); + if (v < 0) { + throw new RuntimeException(); + } + Bytes.putFloat(b, o, v); + return Bytes.SIZEOF_FLOAT; + } + + private static double decodeDouble(byte[] bytes, int o) { + checkForSufficientLength(bytes, o, Bytes.SIZEOF_LONG); + long l; + l = Bytes.toLong(bytes, o); + l--; + l ^= (~l >> Long.SIZE - 1) | Long.MIN_VALUE; + return Double.longBitsToDouble(l); + } + + private static int encodeDouble(double v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_LONG); + long l = Double.doubleToLongBits(v); + l = (l ^ ((l >> Long.SIZE - 1) | Long.MIN_VALUE)) + 1; + Bytes.putLong(b, o, l); + return Bytes.SIZEOF_LONG; + } + + private static double decodeUnsignedDouble(byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE); + double v = Bytes.toDouble(b, o); + if (v < 0) { + throw new RuntimeException(); + } + return v; + } + + private static int encodeUnsignedDouble(double v, byte[] b, int o) { + checkForSufficientLength(b, o, Bytes.SIZEOF_DOUBLE); + if (v < 0) { + throw new RuntimeException(); + } + Bytes.putDouble(b, o, v); + return Bytes.SIZEOF_DOUBLE; + } + + private static int encodeDate(Object v, byte[] b, int o) { + if (v instanceof Date) { + encodeLong(((Date) v).getTime(), b, 0); + } else if (v instanceof String) { + String dateStr = (String) v; + Date date; + try { + date = parseDatetime(dateStr); + if (date != null) { + encodeLong(date.getTime(), b, 0); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return Bytes.SIZEOF_LONG; + } + + private static int encodeTimestamp(Object v, byte[] b, int o) { + if (v instanceof Timestamp) { + Timestamp ts = (Timestamp) v; + encodeLong(ts.getTime(), b, o); + Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000); + } else { + encodeDate(v, b, o); + } + return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT; + } + + private static int encodeUnsignedDate(Object v, byte[] b, int o) { + if (v instanceof Date) { + encodeUnsignedLong(((Date) v).getTime(), b, 0); + } else if (v instanceof String) { + String dateStr = (String) v; + Date date; + try { + date = parseDatetime(dateStr); + if (date != null) { + encodeUnsignedLong(date.getTime(), b, 0); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + return Bytes.SIZEOF_LONG; + } + + private static int encodeUnsignedTimestamp(Object v, byte[] b, int o) { + if (v instanceof Timestamp) { + Timestamp ts = (Timestamp) v; + encodeUnsignedLong(ts.getTime(), b, o); + Bytes.putInt(b, Bytes.SIZEOF_LONG, ts.getNanos() % 1000000); + } else { + encodeUnsignedDate(v, b, o); + } + return Bytes.SIZEOF_LONG + Bytes.SIZEOF_INT; + } + + private static byte[] encodeDecimal(Object object) { + if (object == null) { + return new byte[0]; + } + BigDecimal v = (BigDecimal) object; + v = v.round(DEFAULT_MATH_CONTEXT).stripTrailingZeros(); + int len = getLength(v); + byte[] result = new byte[Math.min(len, 21)]; + decimalToBytes(v, result, 0, len); + return result; + } + + private static BigDecimal decodeDecimal(byte[] bytes, int offset, int length) { + if (length == 1 && bytes[offset] == ZERO_BYTE) { + return BigDecimal.ZERO; + } + int signum = ((bytes[offset] & 0x80) == 0) ? -1 : 1; + int scale; + int index; + int digitOffset; + long multiplier = 100L; + int begIndex = offset + 1; + if (signum == 1) { + scale = (byte) (((bytes[offset] & 0x7F) - 65) * -2); + index = offset + length; + digitOffset = POS_DIGIT_OFFSET; + } else { + scale = (byte) ((~bytes[offset] - 65 - 128) * -2); + index = offset + length - (bytes[offset + length - 1] == NEG_TERMINAL_BYTE ? 1 : 0); + digitOffset = -NEG_DIGIT_OFFSET; + } + length = index - offset; + long l = signum * bytes[--index] - digitOffset; + if (l % 10 == 0) { // trailing zero + scale--; // drop trailing zero and compensate in the scale + l /= 10; + multiplier = 10; + } + // Use long arithmetic for as long as we can + while (index > begIndex) { + if (l >= MAX_LONG_FOR_DESERIALIZE || multiplier >= Long.MAX_VALUE / 100) { + multiplier = LongMath.divide(multiplier, 100L, RoundingMode.UNNECESSARY); + break; // Exit loop early so we don't overflow our multiplier + } + int digit100 = signum * bytes[--index] - digitOffset; + l += digit100 * multiplier; + multiplier = LongMath.checkedMultiply(multiplier, 100); + } + + BigInteger bi; + // If still more digits, switch to BigInteger arithmetic + if (index > begIndex) { + bi = BigInteger.valueOf(l); + BigInteger biMultiplier = BigInteger.valueOf(multiplier).multiply(ONE_HUNDRED); + do { + int digit100 = signum * bytes[--index] - digitOffset; + bi = bi.add(biMultiplier.multiply(BigInteger.valueOf(digit100))); + biMultiplier = biMultiplier.multiply(ONE_HUNDRED); + } while (index > begIndex); + if (signum == -1) { + bi = bi.negate(); + } + } else { + bi = BigInteger.valueOf(l * signum); + } + // Update the scale based on the precision + scale += (length - 2) * 2; + BigDecimal v = new BigDecimal(bi, scale); + return v; + } + + private static int getLength(BigDecimal v) { + int signum = v.signum(); + if (signum == 0) { // Special case for zero + return 1; + } + return (signum < 0 ? 2 : 1) + (v.precision() + 1 + (v.scale() % 2 == 0 ? 0 : 1)) / 2; + } + + private static final int MAX_PRECISION = 38; + private static final MathContext DEFAULT_MATH_CONTEXT = new MathContext(MAX_PRECISION, RoundingMode.HALF_UP); + private static final Integer MAX_BIG_DECIMAL_BYTES = 21; + private static final byte ZERO_BYTE = (byte) 0x80; + private static final byte NEG_TERMINAL_BYTE = (byte) 102; + private static final int EXP_BYTE_OFFSET = 65; + private static final int POS_DIGIT_OFFSET = 1; + private static final int NEG_DIGIT_OFFSET = 101; + private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); + private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); + private static final BigInteger ONE_HUNDRED = BigInteger.valueOf(100); + private static final long MAX_LONG_FOR_DESERIALIZE = Long.MAX_VALUE / 1000; + + private static int decimalToBytes(BigDecimal v, byte[] result, final int offset, int length) { + int signum = v.signum(); + if (signum == 0) { + result[offset] = ZERO_BYTE; + return 1; + } + int index = offset + length; + int scale = v.scale(); + int expOffset = scale % 2 * (scale < 0 ? -1 : 1); + int multiplyBy; + BigInteger divideBy; + if (expOffset == 0) { + multiplyBy = 1; + divideBy = ONE_HUNDRED; + } else { + multiplyBy = 10; + divideBy = BigInteger.TEN; + } + // Normalize the scale based on what is necessary to end up with a base 100 + // decimal (i.e. 10.123e3) + int digitOffset; + BigInteger compareAgainst; + if (signum == 1) { + digitOffset = POS_DIGIT_OFFSET; + compareAgainst = MAX_LONG; + scale -= (length - 2) * 2; + result[offset] = (byte) ((-(scale + expOffset) / 2 + EXP_BYTE_OFFSET) | 0x80); + } else { + digitOffset = NEG_DIGIT_OFFSET; + compareAgainst = MIN_LONG; + // Scale adjustment shouldn't include terminal byte in length + scale -= (length - 2 - 1) * 2; + result[offset] = (byte) (~(-(scale + expOffset) / 2 + EXP_BYTE_OFFSET + 128) & 0x7F); + if (length <= MAX_BIG_DECIMAL_BYTES) { + result[--index] = NEG_TERMINAL_BYTE; + } else { + // Adjust length and offset down because we don't have enough room + length = MAX_BIG_DECIMAL_BYTES; + index = offset + length; + } + } + BigInteger bi = v.unscaledValue(); + // Use BigDecimal arithmetic until we can fit into a long + while (bi.compareTo(compareAgainst) * signum > 0) { + BigInteger[] dandr = bi.divideAndRemainder(divideBy); + bi = dandr[0]; + int digit = dandr[1].intValue(); + result[--index] = (byte) (digit * multiplyBy + digitOffset); + multiplyBy = 1; + divideBy = ONE_HUNDRED; + } + long l = bi.longValue(); + do { + long divBy = 100 / multiplyBy; + long digit = l % divBy; + l /= divBy; + result[--index] = (byte) (digit * multiplyBy + digitOffset); + multiplyBy = 1; + } while (l != 0); + + return length; + } + + private static void checkForSufficientLength(byte[] b, int offset, int requiredLength) { + if (b.length < offset + requiredLength) { + throw new RuntimeException( + "Expected length of at least " + requiredLength + " bytes, but had " + (b.length - offset)); + } + } + + private static Date parseDatetime(String dateStr) { + Date date = null; + int len = dateStr.length(); + if (len == 10 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-') { + date = new DateTime(dateStr).toDate(); + } else if (len == 8 && dateStr.charAt(2) == ':' && dateStr.charAt(5) == ':') { + date = new DateTime("T" + dateStr).toDate(); + } else if (len >= 19 && dateStr.charAt(4) == '-' && dateStr.charAt(7) == '-' && dateStr.charAt(13) == ':' + && dateStr.charAt(16) == ':') { + date = new DateTime(dateStr.replace(" ", "T")).toDate(); + } + return date; + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java new file mode 100644 index 00000000..9a567191 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java @@ -0,0 +1,104 @@ +package com.alibaba.otter.canal.client.adapter.hbase.support; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Date; + +/** + * Java类型 + * + * @author machengyuan 2018-8-21 下午06:11:36 + * @version 1.0.0 + */ +public enum Type { + DEFAULT("STRING"), + STRING("STRING"), + INTEGER("INTEGER"), + LONG("LONG"), + SHORT("SHORT"), + BOOLEAN("BOOLEAN"), + FLOAT("FLOAT"), + DOUBLE("DOUBLE"), + BIGDECIMAL("BIGDECIMAL"), + DATE("DATE"), + BYTE("BYTE"), + BYTES("BYTES"); + + private String type; + + Type(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public static Type getType(String type) { + if (type == null) { + return DEFAULT; + } + Type res; + if (type.equalsIgnoreCase("STRING")) { + res = STRING; + } else if (type.equalsIgnoreCase("INTEGER")) { + res = INTEGER; + } else if (type.equalsIgnoreCase("LONG")) { + res = LONG; + } else if (type.equalsIgnoreCase("SHORT")) { + res = SHORT; + } else if (type.equalsIgnoreCase("BOOLEAN")) { + res = BOOLEAN; + } else if (type.equalsIgnoreCase("FLOAT")) { + res = FLOAT; + } else if (type.equalsIgnoreCase("DOUBLE")) { + res = DOUBLE; + } else if (type.equalsIgnoreCase("BIGDECIMAL")) { + res = BIGDECIMAL; + } else if (type.equalsIgnoreCase("DATE")) { + res = DATE; + } else if (type.equalsIgnoreCase("BYTE")) { + res = BYTE; + } else if (type.equalsIgnoreCase("BYTES")) { + res = BYTES; + } else { + res = DEFAULT; + } + return res; + } + + public static Type getType(Class javaType) { + if (javaType == null) { + return DEFAULT; + } + Type type; + if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) { + type = INTEGER; + } else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) { + type = LONG; + } else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) { + type = BYTE; + } else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) { + type = SHORT; + } else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) { + type = FLOAT; + } else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) { + type = DOUBLE; + } else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) { + type = BOOLEAN; + } else if (Date.class.isAssignableFrom(javaType)) { + type = DATE; + } else if (byte[].class.isAssignableFrom(javaType)) { + type = BYTES; + } else if (String.class.isAssignableFrom(javaType)) { + type = STRING; + } else if (BigDecimal.class.isAssignableFrom(javaType)) { + type = BIGDECIMAL; + } else if (BigInteger.class.isAssignableFrom(javaType)) { + type = LONG; + } else { + type = DEFAULT; + } + return type; + } +} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java new file mode 100644 index 00000000..07aad00f --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java @@ -0,0 +1,188 @@ +package com.alibaba.otter.canal.client.adapter.hbase.support; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.Date; + +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Java类型转换工具类 + * + * @author machengyuan 2018-8-21 下午06:12:34 + * @version 1.0.0 + */ +public class TypeUtil { + + public static byte[] toBytes(Object obj) { + if (obj == null) { + return null; + } + byte[] bytes; + if (obj instanceof String) { + bytes = Bytes.toBytes((String) obj); + } else if (obj instanceof Integer) { + bytes = Bytes.toBytes((Integer) obj); + } else if (obj instanceof Long) { + bytes = Bytes.toBytes((Long) obj); + } else if (obj instanceof Short) { + bytes = Bytes.toBytes((Short) obj); + } else if (obj instanceof Boolean) { + bytes = Bytes.toBytes((Boolean) obj); + } else if (obj instanceof Float) { + bytes = Bytes.toBytes((Float) obj); + } else if (obj instanceof Double) { + bytes = Bytes.toBytes((Double) obj); + } else if (obj instanceof BigDecimal) { + bytes = Bytes.toBytes((BigDecimal) obj); + } else if (obj instanceof BigInteger) { + bytes = Bytes.toBytes(((BigInteger) obj).longValue()); + } else if (obj instanceof Date) { + bytes = Bytes.toBytes(((Date) obj).getTime()); + } else if (obj instanceof Byte) { + bytes = new byte[] { (byte) obj }; + } else if (obj instanceof byte[]) { + bytes = (byte[]) obj; + } else { + // 其余类型统一转换为string + bytes = Bytes.toBytes(obj.toString()); + } + return bytes; + } + + public static byte[] toBytes(Object v, Type type) { + if (v == null) { + return null; + } + byte[] b = null; + if (type == Type.DEFAULT) { + Type type1 = Type.getType(v.getClass()); + if (type1 != null && type1 != Type.DEFAULT) { + b = toBytes(v, type1); + } + } else if (type == Type.STRING) { + b = Bytes.toBytes(v.toString()); + } else if (type == Type.INTEGER) { + b = Bytes.toBytes(((Number) v).intValue()); + } else if (type == Type.LONG) { + b = Bytes.toBytes(((Number) v).longValue()); + } else if (type == Type.SHORT) { + b = Bytes.toBytes(((Number) v).shortValue()); + } else if (type == Type.BYTE) { + b = Bytes.toBytes(((Number) v).byteValue()); + } else if (type == Type.FLOAT) { + b = Bytes.toBytes(((Number) v).floatValue()); + } else if (type == Type.DOUBLE) { + b = Bytes.toBytes(((Number) v).doubleValue()); + } else if (type == Type.BOOLEAN) { + b = Bytes.toBytes(((Boolean) v)); + } else if (type == Type.DATE) { + b = Bytes.toBytes(((Date) v).getTime()); + } else if (type == Type.BYTES) { + b = (byte[]) v; + } else if (type == Type.BIGDECIMAL) { + if (v instanceof BigDecimal) { + b = Bytes.toBytes((BigDecimal) v); + } else { + b = Bytes.toBytes(new BigDecimal(v.toString())); + } + } + return b; + } + + public static T toObject(byte[] bytes, Class clazz) { + if (bytes == null) { + return null; + } + Object res; + if (String.class.isAssignableFrom(clazz)) { + res = Bytes.toString(bytes); + } else if (Integer.class.isAssignableFrom(clazz)) { + res = Bytes.toInt(bytes); + } else if (Long.class.isAssignableFrom(clazz)) { + res = Bytes.toLong(bytes); + } else if (Short.class.isAssignableFrom(clazz)) { + res = Bytes.toShort(bytes); + } else if (Boolean.class.isAssignableFrom(clazz)) { + res = Bytes.toBoolean(bytes); + } else if (Float.class.isAssignableFrom(clazz)) { + res = Bytes.toFloat(bytes); + } else if (Double.class.isAssignableFrom(clazz)) { + res = Bytes.toDouble(bytes); + } else if (BigDecimal.class.isAssignableFrom(clazz)) { + res = Bytes.toBigDecimal(bytes); + } else if (BigInteger.class.isAssignableFrom(clazz)) { + res = Bytes.toLong(bytes); + } else if (java.sql.Date.class.isAssignableFrom(clazz)) { + long ts = Bytes.toLong(bytes); + res = new java.sql.Date(ts); + } else if (Time.class.isAssignableFrom(clazz)) { + long ts = Bytes.toLong(bytes); + res = new Time(ts); + } else if (Timestamp.class.isAssignableFrom(clazz)) { + long ts = Bytes.toLong(bytes); + res = new Timestamp(ts); + } else if (Date.class.isAssignableFrom(clazz)) { + long ts = Bytes.toLong(bytes); + res = new Date(ts); + } else if (Byte.class.isAssignableFrom(clazz)) { + res = bytes[0]; + } else { + throw new IllegalArgumentException("mismatch class type"); + } + // noinspection unchecked + return (T) res; + } + + public static T toObject(byte[] bytes, Type type) { + if (bytes == null) { + return null; + } + Object res = null; + if (type == Type.STRING || type == Type.DEFAULT) { + res = Bytes.toString(bytes); + } else if (type == Type.INTEGER) { + if (bytes.length == Bytes.SIZEOF_INT) { + res = Bytes.toInt(bytes); + } + } else if (type == Type.LONG) { + if (bytes.length == Bytes.SIZEOF_LONG) { + res = Bytes.toLong(bytes); + } + } else if (type == Type.SHORT) { + if (bytes.length == Bytes.SIZEOF_SHORT) { + res = Bytes.toShort(bytes); + } + } else if (type == Type.BYTE) { + if (bytes.length == Bytes.SIZEOF_BYTE) { + res = bytes[0]; + } + } else if (type == Type.FLOAT) { + if (bytes.length == Bytes.SIZEOF_FLOAT) { + res = Bytes.toFloat(bytes); + } + } else if (type == Type.DOUBLE) { + if (bytes.length == Bytes.SIZEOF_DOUBLE) { + res = Bytes.toDouble(bytes); + } + } else if (type == Type.BOOLEAN) { + if (bytes.length == Bytes.SIZEOF_BOOLEAN) { + res = Bytes.toBoolean(bytes); + } + } else if (type == Type.DATE) { + if (bytes.length == Bytes.SIZEOF_LONG) { + res = new Date(Bytes.toLong(bytes)); + } + } else if (type == Type.BYTES) { + res = bytes; + } else if (type == Type.BIGDECIMAL) { + res = Bytes.toBigDecimal(bytes); + } else { + throw new IllegalArgumentException("mismatch class type"); + } + // noinspection unchecked + return (T) res; + } +} diff --git a/client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter b/client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter new file mode 100644 index 00000000..321d49cf --- /dev/null +++ b/client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter @@ -0,0 +1 @@ +hbase=com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter \ No newline at end of file diff --git a/client-adapter/hbase/src/main/resources/hbase-mapping/configs.conf b/client-adapter/hbase/src/main/resources/hbase-mapping/configs.conf new file mode 100644 index 00000000..ff5b292e --- /dev/null +++ b/client-adapter/hbase/src/main/resources/hbase-mapping/configs.conf @@ -0,0 +1,5 @@ +# 详细映射配置 +mytest_person2.yml + +# 简易配置, 只用指定数据库名.表名, 详细配置全部使用默认 +mytest.person \ No newline at end of file diff --git a/client-adapter/hbase/src/main/resources/hbase-mapping/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase-mapping/mytest_person2.yml new file mode 100644 index 00000000..575a1bf5 --- /dev/null +++ b/client-adapter/hbase/src/main/resources/hbase-mapping/mytest_person2.yml @@ -0,0 +1,56 @@ +hbaseOrm: + mode: PHOENIX #NATIVE #STRING + database: mytest # 数据库名 + table: person2 # 数据库表名 + hbaseTable: MYTEST.PERSON2 # HBase表名 + family: CF # 默认统一Family名称 + uppercaseQualifier: true # 字段名转大写, 默认为true + commitBatch: 3000 # 批量提交的大小 + #rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复 + columns: + # 数据库字段:HBase对应字段 + id: ROWKEY$UNSIGNED_LONG + name: NAME + email: EMAIL + type: $DECIMAL + c_time: C_TIME$UNSIGNED_TIMESTAMP + birthday: BIRTHDAY$DATE + +# -- NATIVE类型 +# $DEFAULT +# $STRING +# $INTEGER +# $LONG +# $SHORT +# $BOOLEAN +# $FLOAT +# $DOUBLE +# $BIGDECIMAL +# $DATE +# $BYTE +# $BYTES + +# -- PHOENIX类型 +# $DEFAULT 对应PHOENIX里的VARCHAR +# $UNSIGNED_INT 对应PHOENIX里的UNSIGNED_INT 4字节 +# $UNSIGNED_LONG 对应PHOENIX里的UNSIGNED_LONG 8字节 +# $UNSIGNED_TINYINT 对应PHOENIX里的UNSIGNED_TINYINT 1字节 +# $UNSIGNED_SMALLINT 对应PHOENIX里的UNSIGNED_SMALLINT 2字节 +# $UNSIGNED_FLOAT 对应PHOENIX里的UNSIGNED_FLOAT 4字节 +# $UNSIGNED_DOUBLE 对应PHOENIX里的UNSIGNED_DOUBLE 8字节 +# $INTEGER 对应PHOENIX里的INTEGER 4字节 +# $BIGINT 对应PHOENIX里的BIGINT 8字节 +# $TINYINT 对应PHOENIX里的TINYINT 1字节 +# $SMALLINT 对应PHOENIX里的SMALLINT 2字节 +# $FLOAT 对应PHOENIX里的FLOAT 4字节 +# DOUBLE 对应PHOENIX里的DOUBLE 8字节 +# $BOOLEAN 对应PHOENIX里的BOOLEAN 1字节 +# $TIME 对应PHOENIX里的TIME 8字节 +# $DATE 对应PHOENIX里的DATE 8字节 +# $TIMESTAMP 对应PHOENIX里的TIMESTAMP 12字节 +# $UNSIGNED_TIME 对应PHOENIX里的UNSIGNED_TIME 8字节 +# $UNSIGNED_DATE 对应PHOENIX里的UNSIGNED_DATE 8字节 +# $UNSIGNED_TIMESTAMP 对应PHOENIX里的UNSIGNED_TIMESTAMP 12字节 +# $VARCHAR 对应PHOENIX里的VARCHAR 动态长度 +# $VARBINARY 对应PHOENIX里的VARBINARY 动态长度 +# $DECIMAL 对应PHOENIX里的DECIMAL 动态长度 \ No newline at end of file diff --git a/client-adapter/logger/pom.xml b/client-adapter/logger/pom.xml new file mode 100644 index 00000000..e6c5d218 --- /dev/null +++ b/client-adapter/logger/pom.xml @@ -0,0 +1,47 @@ + + + + canal.client-adapter + com.alibaba.otter + 1.1.0-SNAPSHOT + + 4.0.0 + com.alibaba.otter + client-adapter.logger + jar + canal client adapter logger example module for otter ${project.version} + + + com.alibaba.otter + client-adapter.common + ${project.version} + provided + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java new file mode 100644 index 00000000..c6c1cb44 --- /dev/null +++ b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java @@ -0,0 +1,45 @@ +package com.alibaba.otter.canal.client.adapter.logger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.MessageUtil; +import com.alibaba.otter.canal.client.adapter.support.SPI; +import com.alibaba.otter.canal.protocol.Message; + +/** + * 外部适配器示例 + * + * @author machengyuan 2018-8-19 下午11:45:38 + * @version 1.0.0 + */ +@SPI("logger") // logger参数对应CanalOuterAdapterConfiguration配置中的name +public class LoggerAdapterExample implements CanalOuterAdapter { + + private Logger logger = LoggerFactory.getLogger(this.getClass()); + + @Override + public void writeOut(Message message) { + // 直接输出日志信息 + MessageUtil.parse4Dml(message, new MessageUtil.Consumer() { + + @Override + public void accept(Dml dml) { + logger.info(dml.toString()); + } + }); + } + + @Override + public void init(CanalOuterAdapterConfiguration configuration) { + + } + + @Override + public void destroy() { + + } +} diff --git a/client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter b/client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter new file mode 100644 index 00000000..bfeae48e --- /dev/null +++ b/client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter @@ -0,0 +1 @@ +logger=com.alibaba.otter.canal.client.adapter.logger.LoggerAdapterExample \ No newline at end of file diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml new file mode 100644 index 00000000..5398a9c5 --- /dev/null +++ b/client-adapter/pom.xml @@ -0,0 +1,21 @@ + + + + canal + com.alibaba.otter + 1.1.0-SNAPSHOT + + 4.0.0 + com.alibaba.otter + canal.client-adapter + pom + canal client adapter module for otter ${project.version} + + common + logger + hbase + + + \ No newline at end of file diff --git a/client-launcher/pom.xml b/client-launcher/pom.xml index 42d605a7..952aeb8a 100644 --- a/client-launcher/pom.xml +++ b/client-launcher/pom.xml @@ -13,6 +13,12 @@ jar canal client launcher module for otter ${project.version} + + com.alibaba.otter + client-adapter.common + ${project.version} + + com.alibaba.otter canal.client @@ -24,16 +30,25 @@ canal.kafka.client ${project.version} - - joda-time - joda-time - 2.9.4 - org.yaml snakeyaml 1.17 + + + + com.alibaba.otter + client-adapter.logger + ${project.version} + jar-with-dependencies + + + com.alibaba.otter + client-adapter.hbase + ${project.version} + jar-with-dependencies + diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java index 556910dd..c94fcd28 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java @@ -1,6 +1,7 @@ package com.alibaba.otter.canal.client; -import com.alibaba.otter.canal.client.support.CanalClientConfig; +import com.alibaba.otter.canal.client.adapter.loader.CanalAdapterLoader; +import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,8 +10,9 @@ import org.yaml.snakeyaml.Yaml; import java.io.FileInputStream; public class ClientLauncher { + private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(ClientLauncher.class); + private static final Logger logger = LoggerFactory.getLogger(ClientLauncher.class); public static void main(String[] args) { try { @@ -23,7 +25,7 @@ public class ClientLauncher { if (conf.startsWith(CLASSPATH_URL_PREFIX)) { conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX); canalClientConfig = new Yaml().loadAs(ClientLauncher.class.getClassLoader().getResourceAsStream(conf), - CanalClientConfig.class); + CanalClientConfig.class); } else { canalClientConfig = new Yaml().loadAs(new FileInputStream(conf), CanalClientConfig.class); } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java deleted file mode 100644 index 42a154d4..00000000 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/AbstractCanalAdapterWorker.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.alibaba.otter.canal.client.adapter; - -import com.alibaba.fastjson.JSON; -import com.alibaba.otter.canal.client.support.Dml; -import com.alibaba.otter.canal.client.support.JdbcTypeUtil; -import com.alibaba.otter.canal.protocol.CanalEntry; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -/** - * 适配器工作线程抽象类 - * - * @author machengyuan 2018-8-19 下午11:30:49 - * @version 1.0.0 - */ -public abstract class AbstractCanalAdapterWorker { - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - - protected String canalDestination; // canal实例 - protected List> canalOuterAdapters; // 外部适配器 - protected ExecutorService groupInnerExecutorService; // 组内工作线程池 - protected volatile boolean running = false; // 是否运行中 - protected Thread thread = null; - protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread t, Throwable e) { - logger.error("parse events has an error", e); - } - }; - - /** - * 将每个entry转换为DML操作对象并调用所有适配器写入 - * - * @param entries - */ - protected void convertAndWrite(List entries) { - for (CanalEntry.Entry entry : entries) { - if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { - continue; - } - - CanalEntry.RowChange rowChange; - try { - rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); - } catch (Exception e) { - throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), e); - } - - CanalEntry.EventType eventType = rowChange.getEventType(); - - final Dml dml = new Dml(); - dml.setCanalDestination(canalDestination); - dml.setDatabase(entry.getHeader().getSchemaName()); - dml.setTable(entry.getHeader().getTableName()); - dml.setType(eventType.toString()); - dml.setTs(System.currentTimeMillis()); - dml.setSql(rowChange.getSql()); - List> data = new ArrayList<>(); - List> old = new ArrayList<>(); - - if (!rowChange.getIsDdl()) { - Set updateSet = new HashSet<>(); - for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) { - Map row = new LinkedHashMap<>(); - List columns; - - if (eventType == CanalEntry.EventType.DELETE) { - columns = rowData.getBeforeColumnsList(); - } else { - columns = rowData.getAfterColumnsList(); - } - - for (CanalEntry.Column column : columns) { - row.put(column.getName(), JdbcTypeUtil.typeConvert(dml.getTable(), column.getName(), column.getValue(), - column.getSqlType(), column.getMysqlType())); - //获取update为true的字段 - if (column.getUpdated()) { - updateSet.add(column.getName()); - } - } - if (!row.isEmpty()) { - data.add(row); - } - - if (eventType == CanalEntry.EventType.UPDATE) { - Map rowOld = new LinkedHashMap<>(); - for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { - if (updateSet.contains(column.getName())) { - rowOld.put(column.getName(), JdbcTypeUtil.typeConvert(dml.getTable(), column.getName(), - column.getValue(), column.getSqlType(), column.getMysqlType())); - } - } - // update操作将记录修改前的值 - if (!rowOld.isEmpty()) { - old.add(rowOld); - } - } - } - if (!data.isEmpty()) { - dml.setData(data); - } - if (!old.isEmpty()) { - dml.setOld(old); - } - } - List> futures = new ArrayList<>(); - // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { - final List adapters = outerAdapters; - futures.add(groupInnerExecutorService.submit( - new Callable() { - @Override - public Boolean call() { - boolean flag = true; - // 组内适配器穿行运行,尽量不要配置组内适配器 - for (CanalOuterAdapter c : adapters) { - long begin = System.currentTimeMillis(); - if (!c.writeOut(dml)) { - logger.error("{} write fail! data: {}", c.getClass().getName(), JSON.toJSONString(dml)); - - flag = false; - } - if (logger.isDebugEnabled()) { - logger.debug("{} elapsed time: {}", c.getClass().getName(), (System.currentTimeMillis() - begin)); - } - } - return flag; - } - }) - ); - - // 等待所有适配器写入完成 - // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 - for (Future f : futures) { - try { - if (!f.get()) { - logger.error("Outer adapter write failed"); - } - } catch (InterruptedException | ExecutionException e) { - logger.error(e.getMessage(), e); - } - } - } - } - } - - public abstract void start(); - - public abstract void stop(); -} \ No newline at end of file diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java new file mode 100644 index 00000000..9dd328e3 --- /dev/null +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java @@ -0,0 +1,83 @@ +package com.alibaba.otter.canal.client.adapter.loader; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.protocol.Message; + +/** + * 适配器工作线程抽象类 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ +public abstract class AbstractCanalAdapterWorker { + + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + protected String canalDestination; // canal实例 + protected List> canalOuterAdapters; // 外部适配器 + protected ExecutorService groupInnerExecutorService; // 组内工作线程池 + protected volatile boolean running = false; // 是否运行中 + protected Thread thread = null; + protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread t, Throwable e) { + logger.error("parse events has an error", e); + } + }; + + protected void writeOut(final Message message) { + List> futures = new ArrayList<>(); + // 组间适配器并行运行 + for (List outerAdapters : canalOuterAdapters) { + final List adapters = outerAdapters; + futures.add(groupInnerExecutorService.submit(new Callable() { + + @Override + public Boolean call() { + try { + // 组内适配器穿行运行,尽量不要配置组内适配器 + for (CanalOuterAdapter c : adapters) { + long begin = System.currentTimeMillis(); + c.writeOut(message); + if (logger.isDebugEnabled()) { + logger.debug("{} elapsed time: {}", + c.getClass().getName(), + (System.currentTimeMillis() - begin)); + } + } + return true; + } catch (Exception e) { + return false; + } + } + })); + + // 等待所有适配器写入完成 + // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 + for (Future f : futures) { + try { + if (!f.get()) { + logger.error("Outer adapter write failed"); + } + } catch (InterruptedException | ExecutionException e) { + logger.error(e.getMessage(), e); + } + } + } + } + + public abstract void start(); + + public abstract void stop(); +} diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java similarity index 62% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterKafkaWorker.java rename to client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java index 2e64f8b8..6b35e452 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterKafkaWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java @@ -1,5 +1,7 @@ -package com.alibaba.otter.canal.client.adapter; +package com.alibaba.otter.canal.client.adapter.loader; +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.loader.AbstractCanalAdapterWorker; import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector; import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors; import com.alibaba.otter.canal.protocol.Message; @@ -13,11 +15,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { + private KafkaCanalConnector connector; - private String topic; + private String topic; - public CanalAdapterKafkaWorker(String zkServers, String bootstrapServers, String topic, String groupId, List> canalOuterAdapters) { + public CanalAdapterKafkaWorker(String zkServers, String bootstrapServers, String topic, String groupId, + List> canalOuterAdapters){ this.canalOuterAdapters = canalOuterAdapters; this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); this.topic = topic; @@ -25,13 +29,14 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { connector = KafkaCanalConnectors.newKafkaConnector(zkServers, bootstrapServers, topic, null, groupId); // connector.setSessionTimeout(5L, TimeUnit.MINUTES); -// super.initSwitcher(topic); + // super.initSwitcher(topic); } @Override public void start() { if (!running) { thread = new Thread(new Runnable() { + @Override public void run() { process(); @@ -53,9 +58,9 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { connector.stopRunning(); running = false; -// if (switcher != null && !switcher.state()) { -// switcher.set(true); -// } + // if (switcher != null && !switcher.state()) { + // switcher.set(true); + // } if (thread != null) { try { @@ -78,7 +83,8 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { } private void process() { - while (!running) ; + while (!running) + ; ExecutorService executor = Executors.newFixedThreadPool(1); final AtomicBoolean executing = new AtomicBoolean(true); while (running) { @@ -90,37 +96,45 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); while (running) { try { -// switcher.get(); //等待开关开启 + // switcher.get(); //等待开关开启 final Message message = connector.getWithoutAck(); executing.set(true); if (message != null) { - executor.submit( - new Runnable() { - @Override - public void run() { - try { - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId: {} batchSize: {} ", topic, message.getId(), message.getEntries().size()); - } - long begin = System.currentTimeMillis(); - convertAndWrite(message.getEntries()); - long now = System.currentTimeMillis(); - if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { - logger.error("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); - } - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - executing.compareAndSet(true, false); - } + executor.submit(new Runnable() { + + @Override + public void run() { + try { + if (logger.isDebugEnabled()) { + logger.debug("topic: {} batchId: {} batchSize: {} ", + topic, + message.getId(), + message.getEntries().size()); } + long begin = System.currentTimeMillis(); + writeOut(message); + long now = System.currentTimeMillis(); + if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { + logger.error("topic: {} batchId {} elapsed time: {} ms", + topic, + message.getId(), + now - begin); + } + if (logger.isDebugEnabled()) { + logger.debug("topic: {} batchId {} elapsed time: {} ms", + topic, + message.getId(), + now - begin); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } finally { + executing.compareAndSet(true, false); } - ); + } + }); while (executing.get()) { // keeping kafka client active connector.ack(); diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java similarity index 82% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/CanalAdapterLoader.java rename to client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java index f60a830e..b0fe35c5 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/CanalAdapterLoader.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java @@ -1,11 +1,9 @@ -package com.alibaba.otter.canal.client; +package com.alibaba.otter.canal.client.adapter.loader; -import com.alibaba.otter.canal.client.adapter.CanalAdapterKafkaWorker; -import com.alibaba.otter.canal.client.adapter.CanalAdapterWorker; import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapterConfiguration; -import com.alibaba.otter.canal.client.support.CanalClientConfig; -import com.alibaba.otter.canal.client.support.ExtensionLoader; +import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; +import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,17 +23,18 @@ import java.util.concurrent.Executors; * @version 1.0.0 */ public class CanalAdapterLoader { - private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); + + private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); private CanalClientConfig canalClientConfig; - private Map canalWorkers = new HashMap<>(); + private Map canalWorkers = new HashMap<>(); private Map canalKafkaWorkers = new HashMap<>(); private ExtensionLoader loader; - public CanalAdapterLoader(CanalClientConfig canalClientConfig) { + public CanalAdapterLoader(CanalClientConfig canalClientConfig){ this.canalClientConfig = canalClientConfig; } @@ -48,7 +47,8 @@ public class CanalAdapterLoader { throw new RuntimeException("Blank config property: canalInstances or canalKafkaTopics"); } - loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class, "" /*TODO canalClientConfig.getClassloaderPolicy()*/); + loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class, + "" /* TODO canalClientConfig.getClassloaderPolicy() */); String canalServerHost = this.canalClientConfig.getCanalServerHost(); SocketAddress sa = null; @@ -91,20 +91,23 @@ public class CanalAdapterLoader { List canalOuterAdapters = new ArrayList<>(); - for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { -// for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) { - loadConnector(config, canalOuterAdapters); -// } + for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { + // for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) { + loadConnector(config, canalOuterAdapters); + // } } canalOuterAdapterGroups.add(canalOuterAdapters); String zkServers = canalClientConfig.getZookeeperHosts(); CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkServers, - canalClientConfig.getBootstrapServers(), - kafkaTopic.getTopic(), group.getGroupId(), canalOuterAdapterGroups); + canalClientConfig.getBootstrapServers(), + kafkaTopic.getTopic(), + group.getGroupId(), + canalOuterAdapterGroups); canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker); canalKafkaWorker.start(); - logger.info("Start adapter for canal-client kafka topic: {} succeed", kafkaTopic.getTopic() + "-" + group.getGroupId()); + logger.info("Start adapter for canal-client kafka topic: {} succeed", + kafkaTopic.getTopic() + "-" + group.getGroupId()); } } } @@ -125,8 +128,7 @@ public class CanalAdapterLoader { } /** - * 销毁所有适配器 - * 为防止canal实例太多造成销毁阻塞, 并行销毁 + * 销毁所有适配器 为防止canal实例太多造成销毁阻塞, 并行销毁 */ public void destroy() { if (canalWorkers.size() > 0) { @@ -134,6 +136,7 @@ public class CanalAdapterLoader { for (CanalAdapterWorker v : canalWorkers.values()) { final CanalAdapterWorker caw = v; stopExecutorService.submit(new Runnable() { + @Override public void run() { caw.stop(); @@ -147,6 +150,7 @@ public class CanalAdapterLoader { for (CanalAdapterKafkaWorker v : canalKafkaWorkers.values()) { final CanalAdapterKafkaWorker cakw = v; stopKafkaExecutorService.submit(new Runnable() { + @Override public void run() { cakw.stop(); diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java similarity index 74% rename from client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterWorker.java rename to client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java index 2af88798..bca40041 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/CanalAdapterWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java @@ -1,7 +1,9 @@ -package com.alibaba.otter.canal.client.adapter; +package com.alibaba.otter.canal.client.adapter.loader; import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.loader.AbstractCanalAdapterWorker; import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; import com.alibaba.otter.canal.protocol.Message; @@ -16,19 +18,21 @@ import java.util.concurrent.Executors; * @version 1.0.0 */ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { + private static final int BATCH_SIZE = 50; private static final int SO_TIMEOUT = 0; - private CanalConnector connector; + private CanalConnector connector; /** * 单台client适配器worker的构造方法 * - * @param canalDestination canal实例名 - * @param address canal-server地址 + * @param canalDestination canal实例名 + * @param address canal-server地址 * @param canalOuterAdapters 外部适配器组 */ - public CanalAdapterWorker(String canalDestination, SocketAddress address, List> canalOuterAdapters) { + public CanalAdapterWorker(String canalDestination, SocketAddress address, + List> canalOuterAdapters){ this.canalOuterAdapters = canalOuterAdapters; this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); @@ -38,24 +42,26 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { /** * HA模式下client适配器worker的构造方法 * - * @param canalDestination canal实例名 - * @param zookeeperHosts zookeeper地址 + * @param canalDestination canal实例名 + * @param zookeeperHosts zookeeper地址 * @param canalOuterAdapters 外部适配器组 */ - public CanalAdapterWorker(String canalDestination, String zookeeperHosts, List> canalOuterAdapters) { + public CanalAdapterWorker(String canalDestination, String zookeeperHosts, + List> canalOuterAdapters){ this.canalOuterAdapters = canalOuterAdapters; this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", ""); ((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT); -// super.initSwitcher(canalDestination); + // super.initSwitcher(canalDestination); } @Override public void start() { if (!running) { thread = new Thread(new Runnable() { + @Override public void run() { process(); @@ -74,9 +80,9 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { return; } -// if (switcher != null && !switcher.state()) { -// switcher.set(true); -// } + // if (switcher != null && !switcher.state()) { + // switcher.set(true); + // } connector.stopRunning(); running = false; @@ -103,27 +109,29 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { } private void process() { - while (!running) ; //waiting until running == true + while (!running) + ; // waiting until running == true while (running) { try { -// if (switcher != null) { -// switcher.get(); -// } + // if (switcher != null) { + // switcher.get(); + // } logger.info("=============> Start to connect destination: {} <=============", this.canalDestination); connector.connect(); logger.info("=============> Start to subscribe destination: {} <=============", this.canalDestination); connector.subscribe(); logger.info("=============> Subscribe destination: {} succeed <=============", this.canalDestination); while (running) { -// try { -// if (switcher != null) { -// switcher.get(); -// } -// } catch (TimeoutException e) { -// break; -// } + // try { + // if (switcher != null) { + // switcher.get(); + // } + // } catch (TimeoutException e) { + // break; + // } - // server配置canal.instance.network.soTimeout(默认: 30s) 范围内未与server交互,server将关闭本次socket连接 + // server配置canal.instance.network.soTimeout(默认: 30s) + // 范围内未与server交互,server将关闭本次socket连接 Message message = connector.getWithoutAck(BATCH_SIZE); // 获取指定数量的数据 long batchId = message.getId(); try { @@ -133,17 +141,23 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { try { Thread.sleep(1000); } catch (InterruptedException e) { - //ignore + // ignore } } else { if (logger.isDebugEnabled()) { - logger.debug("destination: {} batchId: {} batchSize: {} ", this.canalDestination, batchId, size); + logger.debug("destination: {} batchId: {} batchSize: {} ", + this.canalDestination, + batchId, + size); } long begin = System.currentTimeMillis(); - convertAndWrite(message.getEntries()); + writeOut(message); long now = System.currentTimeMillis(); if (logger.isDebugEnabled()) { - logger.debug("destination: {} batchId: {} elapsed time: {} ms", this.canalDestination, batchId, now - begin); + logger.debug("destination: {} batchId: {} elapsed time: {} ms", + this.canalDestination, + batchId, + now - begin); } } connector.ack(batchId); // 提交确认 @@ -153,7 +167,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { } } - } catch (Exception e) { logger.error("process error!", e); } finally { @@ -161,11 +174,11 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { logger.info("=============> Disconnect destination: {} <=============", this.canalDestination); } - if (running) { //is reconnect + if (running) { // is reconnect try { Thread.sleep(1000); } catch (InterruptedException e) { - //ignore + // ignore } } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java deleted file mode 100644 index 0f2b4612..00000000 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/example/LoggerAdapterExample.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.alibaba.otter.canal.client.example; - -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapterConfiguration; -import com.alibaba.otter.canal.client.support.Dml; -import com.alibaba.otter.canal.client.support.SPI; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 外部适配器示例 - * - * @author machengyuan 2018-8-19 下午11:45:38 - * @version 1.0.0 - */ -@SPI("logger") // logger参数对应CanalOuterAdapterConfiguration配置中的name -public class LoggerAdapterExample implements CanalOuterAdapter { - private Logger logger = LoggerFactory.getLogger(this.getClass()); - - @Override - public Boolean writeOut(Dml dml) { - // 直接输出一个日志信息 - logger.info(dml.toString()); - return true; - } - - @Override - public void init(CanalOuterAdapterConfiguration configuration) { - - } - - @Override - public void destroy() { - - } -} diff --git a/client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter b/client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter deleted file mode 100644 index 787f15d7..00000000 --- a/client-launcher/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter +++ /dev/null @@ -1 +0,0 @@ -logger=com.alibaba.otter.canal.client.example.LoggerAdapterExample \ No newline at end of file diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml index 80a912a2..2412e38d 100644 --- a/client-launcher/src/main/resources/canal-client.yml +++ b/client-launcher/src/main/resources/canal-client.yml @@ -1,5 +1,5 @@ -#canalServerHost: 127.0.0.1:11111 -zookeeperHosts: 127.0.0.1:2181 +canalServerHost: 127.0.0.1:11111 +#zookeeperHosts: 127.0.0.1:2181 #bootstrapServers: kafka1.mytest.com:9092,kafka2.mytest.com:9092 canalInstances: @@ -7,9 +7,9 @@ canalInstances: adapterGroups: - outAdapters: - name: logger -# - name: hbase -# hosts: -# properties: {znodeParent: /hbase} + - name: hbase + hosts: slave1:2181 + properties: {znodeParent: "/hbase-unsecure"} #kafkaTopics: #- topic: devmysql4308 diff --git a/client-launcher/src/main/resources/logback.xml b/client-launcher/src/main/resources/logback.xml index 41979ba0..665954f7 100644 --- a/client-launcher/src/main/resources/logback.xml +++ b/client-launcher/src/main/resources/logback.xml @@ -36,12 +36,12 @@ - + - + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 19b07a06..e7bb158e 100644 --- a/pom.xml +++ b/pom.xml @@ -120,6 +120,7 @@ kafka kafka-client prometheus + client-adapter client-launcher @@ -333,7 +334,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.0 + 3.7.0 ${java_source_version} ${java_target_version} From 6e07021223d305b8fe6dec97d54eba83d93548ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 23 Aug 2018 14:27:16 +0800 Subject: [PATCH 106/360] fixed client version 1.1.1-SNAPSHOT --- client-adapter/common/pom.xml | 4 ++-- client-adapter/hbase/pom.xml | 4 ++-- client-adapter/logger/pom.xml | 4 ++-- client-adapter/pom.xml | 4 ++-- client-launcher/pom.xml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml index a3a78073..93793d09 100644 --- a/client-adapter/common/pom.xml +++ b/client-adapter/common/pom.xml @@ -5,7 +5,7 @@ canal.client-adapter com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.1-SNAPSHOT 4.0.0 com.alibaba.otter @@ -30,4 +30,4 @@ - \ No newline at end of file + diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml index a8ec67a0..1437ed5d 100644 --- a/client-adapter/hbase/pom.xml +++ b/client-adapter/hbase/pom.xml @@ -5,7 +5,7 @@ canal.client-adapter com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.1-SNAPSHOT 4.0.0 com.alibaba.otter @@ -98,4 +98,4 @@ - \ No newline at end of file + diff --git a/client-adapter/logger/pom.xml b/client-adapter/logger/pom.xml index e6c5d218..8f2b4edb 100644 --- a/client-adapter/logger/pom.xml +++ b/client-adapter/logger/pom.xml @@ -5,7 +5,7 @@ canal.client-adapter com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.1-SNAPSHOT 4.0.0 com.alibaba.otter @@ -44,4 +44,4 @@ - \ No newline at end of file + diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml index 5398a9c5..792ce283 100644 --- a/client-adapter/pom.xml +++ b/client-adapter/pom.xml @@ -5,7 +5,7 @@ canal com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.1-SNAPSHOT 4.0.0 com.alibaba.otter @@ -18,4 +18,4 @@ hbase - \ No newline at end of file + diff --git a/client-launcher/pom.xml b/client-launcher/pom.xml index 952aeb8a..3bd7955d 100644 --- a/client-launcher/pom.xml +++ b/client-launcher/pom.xml @@ -5,7 +5,7 @@ canal com.alibaba.otter - 1.1.0-SNAPSHOT + 1.1.1-SNAPSHOT 4.0.0 com.alibaba.otter @@ -147,4 +147,4 @@ - \ No newline at end of file + From e7f07d7d3e8cae9f1cd9ae6829a8677e2543b008 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 23 Aug 2018 17:33:15 +0800 Subject: [PATCH 107/360] =?UTF-8?q?=E6=95=B4=E5=90=88canal.server=E5=92=8C?= =?UTF-8?q?canal.kafka?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/pom.xml | 1 + .../otter/canal/deployer/CanalConstants.java | 1 + .../otter/canal/deployer/CanalLauncher.java | 13 ++ deployer/src/main/resources/canal.properties | 2 + .../src/main/resources/kafka.yml | 6 +- kafka/pom.xml | 145 ------------------ kafka/src/main/assembly/dev.xml | 64 -------- kafka/src/main/assembly/release.xml | 64 -------- kafka/src/main/bin/startup.bat | 25 --- kafka/src/main/bin/startup.sh | 104 ------------- kafka/src/main/bin/stop.sh | 65 -------- .../otter/canal/kafka/CanalLauncher.java | 17 -- .../otter/canal/kafka/CanalServerStarter.java | 78 ---------- kafka/src/main/resources/logback.xml | 85 ---------- pom.xml | 1 - server/pom.xml | 23 +++ .../canal/kafka}/CanalKafkaProducer.java | 7 +- .../otter/canal/kafka}/CanalKafkaStarter.java | 31 ++-- .../otter/canal/kafka}/KafkaProperties.java | 2 +- .../otter/canal/kafka}/MessageSerializer.java | 14 +- .../canal/server/CanalServerStarter.java | 12 ++ 21 files changed, 81 insertions(+), 679 deletions(-) rename {kafka => deployer}/src/main/resources/kafka.yml (85%) delete mode 100644 kafka/pom.xml delete mode 100644 kafka/src/main/assembly/dev.xml delete mode 100644 kafka/src/main/assembly/release.xml delete mode 100755 kafka/src/main/bin/startup.bat delete mode 100644 kafka/src/main/bin/startup.sh delete mode 100644 kafka/src/main/bin/stop.sh delete mode 100644 kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalLauncher.java delete mode 100644 kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java delete mode 100644 kafka/src/main/resources/logback.xml rename {kafka/src/main/java/com/alibaba/otter/canal/kafka/producer => server/src/main/java/com/alibaba/otter/canal/kafka}/CanalKafkaProducer.java (91%) rename {kafka/src/main/java/com/alibaba/otter/canal/kafka/producer => server/src/main/java/com/alibaba/otter/canal/kafka}/CanalKafkaStarter.java (83%) rename {kafka/src/main/java/com/alibaba/otter/canal/kafka/producer => server/src/main/java/com/alibaba/otter/canal/kafka}/KafkaProperties.java (98%) rename {kafka/src/main/java/com/alibaba/otter/canal/kafka/producer => server/src/main/java/com/alibaba/otter/canal/kafka}/MessageSerializer.java (83%) create mode 100644 server/src/main/java/com/alibaba/otter/canal/server/CanalServerStarter.java diff --git a/deployer/pom.xml b/deployer/pom.xml index 2de1ce1e..76777f03 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -40,6 +40,7 @@ **/canal.properties **/spring/** **/example/** + **/kafka.yml diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index b328feb7..1e51990e 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -22,6 +22,7 @@ public class CanalConstants { public static final String CANAL_AUTO_SCAN = ROOT + "." + "auto.scan"; public static final String CANAL_AUTO_SCAN_INTERVAL = ROOT + "." + "auto.scan.interval"; public static final String CANAL_CONF_DIR = ROOT + "." + "conf.dir"; + public static final String CANAL_SERVER_MODE = ROOT + "." + "serverMode"; public static final String CANAL_DESTINATION_SPLIT = ","; public static final String GLOBAL_NAME = "global"; diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 828f49e2..2524186a 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -3,6 +3,8 @@ package com.alibaba.otter.canal.deployer; import java.io.FileInputStream; import java.util.Properties; +import com.alibaba.otter.canal.kafka.CanalKafkaStarter; +import com.alibaba.otter.canal.server.CanalServerStarter; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,6 +53,17 @@ public class CanalLauncher { } }); + + CanalServerStarter canalServerStarter = null; + String serverMode = properties.getProperty(CanalConstants.CANAL_SERVER_MODE, "tcp"); + if (serverMode.equalsIgnoreCase("kafka")) { + canalServerStarter = new CanalKafkaStarter(); + } else if (serverMode.equalsIgnoreCase("rocketMQ")) { + // 预留rocketMQ启动 + } + if (canalServerStarter != null) { + canalServerStarter.init(); + } } catch (Throwable e) { logger.error("## Something goes wrong when starting up the canal Server:", e); System.exit(0); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 9083477d..56c35564 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -8,6 +8,8 @@ canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 canal.withoutNetty = false +# tcp, kafka, rocketMQ +canal.serverMode = tcp # flush meta cursor/parse position to file canal.file.data.dir = ${canal.conf.dir} canal.file.flush.period = 1000 diff --git a/kafka/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml similarity index 85% rename from kafka/src/main/resources/kafka.yml rename to deployer/src/main/resources/kafka.yml index 2729fc27..93d4d557 100644 --- a/kafka/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/kafka.yml @@ -12,8 +12,8 @@ canalDestinations: topic: example partition: # 一个destination可以对应多个topic -# topics: -# - topic: example -# partition: + #topics: + # - topic: example + # partition: diff --git a/kafka/pom.xml b/kafka/pom.xml deleted file mode 100644 index b2587c02..00000000 --- a/kafka/pom.xml +++ /dev/null @@ -1,145 +0,0 @@ - - - 4.0.0 - - canal - com.alibaba.otter - 1.1.0-SNAPSHOT - ../pom.xml - - com.alibaba.otter - canal.kafka - jar - canal kafka module for otter ${project.version} - - - com.alibaba.otter - canal.deployer - ${project.version} - - - - org.yaml - snakeyaml - 1.17 - - - org.apache.kafka - kafka_2.11 - 1.1.1 - - - org.slf4j - slf4j-log4j12 - - - - - - org.jboss.netty - netty - 3.2.2.Final - - - - - - - - maven-jar-plugin - - - true - - - **/logback.xml - **/canal.properties - **/spring/** - **/example/** - **/kafka.yml - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - 2.2.1 - - - assemble - - single - - package - - - - false - false - - - - - - - - dev - - true - - env - !release - - - - - - - maven-assembly-plugin - - - - ${basedir}/src/main/assembly/dev.xml - - canal - ${project.build.directory} - - - - - - - - - release - - - env - release - - - - - - - maven-assembly-plugin - - - - ${basedir}/src/main/assembly/release.xml - - - ${project.artifactId}-${project.version} - - ${project.parent.build.directory} - - - - - - - diff --git a/kafka/src/main/assembly/dev.xml b/kafka/src/main/assembly/dev.xml deleted file mode 100644 index 2c3e3fff..00000000 --- a/kafka/src/main/assembly/dev.xml +++ /dev/null @@ -1,64 +0,0 @@ - - dist - - dir - - false - - - . - / - - README* - - - - ./src/main/bin - bin - - **/* - - 0755 - - - ../deployer/src/main/conf - /conf - - **/* - - - - ../deployer/src/main/resources - /conf - - **/* - - - logback.xml - - - - ./src/main/resources - /conf - - **/* - - - - target - logs - - **/* - - - - - - lib - - junit:junit - - - - diff --git a/kafka/src/main/assembly/release.xml b/kafka/src/main/assembly/release.xml deleted file mode 100644 index 0b103620..00000000 --- a/kafka/src/main/assembly/release.xml +++ /dev/null @@ -1,64 +0,0 @@ - - dist - - tar.gz - - false - - - . - / - - README* - - - - ./src/main/bin - bin - - **/* - - 0755 - - - ../deployer/src/main/conf - /conf - - **/* - - - - ../deployer/src/main/resources - /conf - - **/* - - - logback.xml - - - - ./src/main/resources - /conf - - **/* - - - - target - logs - - **/* - - - - - - lib - - junit:junit - - - - diff --git a/kafka/src/main/bin/startup.bat b/kafka/src/main/bin/startup.bat deleted file mode 100755 index 429cc653..00000000 --- a/kafka/src/main/bin/startup.bat +++ /dev/null @@ -1,25 +0,0 @@ -@echo off -@if not "%ECHO%" == "" echo %ECHO% -@if "%OS%" == "Windows_NT" setlocal - -set ENV_PATH=.\ -if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% - -set conf_dir=%ENV_PATH%\..\conf -set canal_conf=%conf_dir%\canal.properties -set logback_configurationFile=%conf_dir%\logback.xml - -set CLASSPATH=%conf_dir% -set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% - -set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m -set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 -set JAVA_DEBUG_OPT= -server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9099,server=y,suspend=n -set CANAL_OPTS= -DappName=otter-canal -Dlogback.configurationFile="%logback_configurationFile%" -Dcanal.conf="%canal_conf%" - -set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS% - -set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.kafka.CanalLauncher -echo start cmd : %CMD_STR% - -java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.kafka.CanalLauncher \ No newline at end of file diff --git a/kafka/src/main/bin/startup.sh b/kafka/src/main/bin/startup.sh deleted file mode 100644 index 184c4b3f..00000000 --- a/kafka/src/main/bin/startup.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash - -current_path=`pwd` -case "`uname`" in - Linux) - bin_abs_path=$(readlink -f $(dirname $0)) - ;; - *) - bin_abs_path=`cd $(dirname $0); pwd` - ;; -esac -base=${bin_abs_path}/.. -canal_conf=$base/conf/canal.properties -logback_configurationFile=$base/conf/logback.xml -export LANG=en_US.UTF-8 -export BASE=$base - -if [ -f $base/bin/canal.pid ] ; then - echo "found canal.pid , Please run stop.sh first ,then startup.sh" 2>&2 - exit 1 -fi - -if [ ! -d $base/logs/canal ] ; then - mkdir -p $base/logs/canal -fi - -## set java path -if [ -z "$JAVA" ] ; then - JAVA=$(which java) -fi - -ALIBABA_JAVA="/usr/alibaba/java/bin/java" -TAOBAO_JAVA="/opt/taobao/java/bin/java" -if [ -z "$JAVA" ]; then - if [ -f $ALIBABA_JAVA ] ; then - JAVA=$ALIBABA_JAVA - elif [ -f $TAOBAO_JAVA ] ; then - JAVA=$TAOBAO_JAVA - else - echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2 - exit 1 - fi -fi - -case "$#" -in -0 ) - ;; -1 ) - var=$* - if [ -f $var ] ; then - canal_conf=$var - else - echo "THE PARAMETER IS NOT CORRECT.PLEASE CHECK AGAIN." - exit - fi;; -2 ) - var=$1 - if [ -f $var ] ; then - canal_conf=$var - else - if [ "$1" = "debug" ]; then - DEBUG_PORT=$2 - DEBUG_SUSPEND="n" - JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND" - fi - fi;; -* ) - echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN." - exit;; -esac - -str=`file -L $JAVA | grep 64-bit` -if [ -n "$str" ]; then - JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError" -else - JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m " -fi - -JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" -CANAL_OPTS="-DappName=otter-canal -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf" - -if [ -e $canal_conf -a -e $logback_configurationFile ] -then - - for i in $base/lib/*; - do CLASSPATH=$i:"$CLASSPATH"; - done - CLASSPATH="$base/conf:$CLASSPATH"; - - echo "cd to $bin_abs_path for workaround relative path" - cd $bin_abs_path - - echo LOG CONFIGURATION : $logback_configurationFile - echo canal conf : $canal_conf - echo CLASSPATH :$CLASSPATH - $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.kafka.CanalLauncher 1>>$base/logs/canal/canal.log 2>&1 & - echo $! > $base/bin/canal.pid - - echo "cd to $current_path for continue" - cd $current_path -else - echo "canal conf("$canal_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!" -fi diff --git a/kafka/src/main/bin/stop.sh b/kafka/src/main/bin/stop.sh deleted file mode 100644 index f398749c..00000000 --- a/kafka/src/main/bin/stop.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -cygwin=false; -linux=false; -case "`uname`" in - CYGWIN*) - cygwin=true - ;; - Linux*) - linux=true - ;; -esac - -get_pid() { - STR=$1 - PID=$2 - if $cygwin; then - JAVA_CMD="$JAVA_HOME\bin\java" - JAVA_CMD=`cygpath --path --unix $JAVA_CMD` - JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'` - else - if $linux; then - if [ ! -z "$PID" ]; then - JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` - else - JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'` - fi - else - if [ ! -z "$PID" ]; then - JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` - else - JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'` - fi - fi - fi - echo $JAVA_PID; -} - -base=`dirname $0`/.. -pidfile=$base/bin/canal.pid -if [ ! -f "$pidfile" ];then - echo "canal is not running. exists" - exit -fi - -pid=`cat $pidfile` -if [ "$pid" == "" ] ; then - pid=`get_pid "appName=otter-canal"` -fi - -echo -e "`hostname`: stopping canal $pid ... " -kill $pid - -LOOPS=0 -while (true); -do - gpid=`get_pid "appName=otter-canal" "$pid"` - if [ "$gpid" == "" ] ; then - echo "Oook! cost:$LOOPS" - `rm $pidfile` - break; - fi - let LOOPS=LOOPS+1 - sleep 1 -done \ No newline at end of file diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalLauncher.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalLauncher.java deleted file mode 100644 index 3212733c..00000000 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalLauncher.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.alibaba.otter.canal.kafka; - -import com.alibaba.otter.canal.kafka.producer.CanalKafkaStarter; - -/** - * canal-kafka独立版本启动的入口类 - * - * @author machengyuan 2018-6-11 下午05:30:49 - * @version 1.0.0 - */ -public class CanalLauncher { - - public static void main(String[] args) { - CanalServerStarter.init(); - CanalKafkaStarter.init(); - } -} diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java b/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java deleted file mode 100644 index 573f0eac..00000000 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/CanalServerStarter.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.alibaba.otter.canal.kafka; - -import java.io.FileInputStream; -import java.util.Properties; - -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.alibaba.otter.canal.deployer.CanalController; - -/** - * canal server 启动类 - * - * @author machengyuan 2018-6-11 下午05:30:49 - * @version 1.0.0 - */ -public class CanalServerStarter { - - private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(CanalServerStarter.class); - private volatile static boolean running = false; - - public static void init() { - try { - logger.info("## set default uncaught exception handler"); - setGlobalUncaughtExceptionHandler(); - - logger.info("## load canal configurations"); - String conf = System.getProperty("canal.conf", "classpath:canal.properties"); - Properties properties = new Properties(); - if (conf.startsWith(CLASSPATH_URL_PREFIX)) { - conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX); - properties.load(CanalLauncher.class.getClassLoader().getResourceAsStream(conf)); - } else { - properties.load(new FileInputStream(conf)); - } - - logger.info("## start the canal server."); - final CanalController controller = new CanalController(properties); - controller.start(); - running = true; - logger.info("## the canal server is running now ......"); - Runtime.getRuntime().addShutdownHook(new Thread() { - - public void run() { - try { - logger.info("## stop the canal server"); - running = false; - controller.stop(); - } catch (Throwable e) { - logger.warn("##something goes wrong when stopping canal Server:", e); - } finally { - logger.info("## canal server is down."); - } - } - - }); - } catch (Throwable e) { - logger.error("## Something goes wrong when starting up the canal Server:", e); - System.exit(0); - } - } - - public static boolean isRunning() { - return running; - } - - private static void setGlobalUncaughtExceptionHandler() { - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - logger.error("UnCaughtException", e); - } - }); - } -} diff --git a/kafka/src/main/resources/logback.xml b/kafka/src/main/resources/logback.xml deleted file mode 100644 index 34e7b1e5..00000000 --- a/kafka/src/main/resources/logback.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n - - - - - - - destination - canal - - - - ../logs/${destination}/${destination}.log - - - ../logs/${destination}/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz - - - 512MB - - 60 - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n - - - - - - - - - destination - canal - - - - ../logs/${destination}/meta.log - - - ../logs/${destination}/%d{yyyy-MM-dd}/meta-%d{yyyy-MM-dd}-%i.log.gz - - - 32MB - - 60 - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} - %msg%n - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index e7bb158e..64588a28 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,6 @@ client deployer example - kafka kafka-client prometheus client-adapter diff --git a/server/pom.xml b/server/pom.xml index b7f83d56..3b8e3ddb 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -25,6 +25,29 @@ canal.instance.manager ${project.version} + + + org.yaml + snakeyaml + 1.17 + + + org.apache.kafka + kafka_2.11 + 1.1.1 + + + org.slf4j + slf4j-log4j12 + + + + + + org.jboss.netty + netty + 3.2.2.Final + diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java similarity index 91% rename from kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java rename to server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index 0bb15308..b6dba051 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.producer; +package com.alibaba.otter.canal.kafka; import java.io.IOException; import java.util.Properties; @@ -10,7 +10,6 @@ import org.apache.kafka.common.serialization.StringSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.kafka.producer.KafkaProperties.Topic; import com.alibaba.otter.canal.protocol.Message; /** @@ -21,7 +20,7 @@ import com.alibaba.otter.canal.protocol.Message; */ public class CanalKafkaProducer { - private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); + private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); private Producer producer; @@ -49,7 +48,7 @@ public class CanalKafkaProducer { } } - public void send(Topic topic, Message message) throws IOException { + public void send(KafkaProperties.Topic topic, Message message) throws IOException { // set canal.instance.filter.transaction.entry = true // boolean valid = false; diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java similarity index 83% rename from kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java rename to server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index 0eedfa7a..d244ce31 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.producer; +package com.alibaba.otter.canal.kafka; import java.io.FileInputStream; import java.util.List; @@ -10,11 +10,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; -import com.alibaba.otter.canal.kafka.CanalServerStarter; -import com.alibaba.otter.canal.kafka.producer.KafkaProperties.CanalDestination; -import com.alibaba.otter.canal.kafka.producer.KafkaProperties.Topic; +import com.alibaba.otter.canal.kafka.KafkaProperties.CanalDestination; +import com.alibaba.otter.canal.kafka.KafkaProperties.Topic; import com.alibaba.otter.canal.protocol.ClientIdentity; import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.server.CanalServerStarter; import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; /** @@ -23,20 +23,21 @@ import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; * @author machengyuan 2018-6-11 下午05:30:49 * @version 1.0.0 */ -public class CanalKafkaStarter { +public class CanalKafkaStarter implements CanalServerStarter { - private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(CanalKafkaStarter.class); + private static final Logger logger = LoggerFactory.getLogger(CanalKafkaStarter.class); - private volatile static boolean running = false; + private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static ExecutorService executorService; + private volatile boolean running = false; - private static CanalKafkaProducer canalKafkaProducer; + private ExecutorService executorService; - private static KafkaProperties kafkaProperties; + private CanalKafkaProducer canalKafkaProducer; - public static void init() { + private KafkaProperties kafkaProperties; + + public void init() { try { logger.info("## load kafka configurations"); String conf = System.getProperty("kafka.conf", "classpath:kafka.yml"); @@ -96,11 +97,9 @@ public class CanalKafkaStarter { } } - private static void worker(CanalDestination destination) { + private void worker(CanalDestination destination) { while (!running) ; - while (!CanalServerStarter.isRunning()) - ; // 等待server启动完成 logger.info("## start the canal consumer: {}.", destination.getCanalDestination()); CanalServerWithEmbedded server = CanalServerWithEmbedded.instance(); ClientIdentity clientIdentity = new ClientIdentity(destination.getCanalDestination(), (short) 1001, ""); @@ -121,7 +120,7 @@ public class CanalKafkaStarter { Message message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); // 获取指定数量的数据 long batchId = message.getId(); try { - int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); + int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); if (batchId != -1 && size != 0) { if (!StringUtils.isEmpty(destination.getTopic())) { Topic topic = new Topic(); diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java similarity index 98% rename from kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java rename to server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java index 8e193aac..140ce965 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/KafkaProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.producer; +package com.alibaba.otter.canal.kafka; import java.util.ArrayList; import java.util.HashSet; diff --git a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java similarity index 83% rename from kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java rename to server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java index 02336004..49db5aa1 100644 --- a/kafka/src/main/java/com/alibaba/otter/canal/kafka/producer/MessageSerializer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.producer; +package com.alibaba.otter.canal.kafka; import java.util.List; import java.util.Map; @@ -37,20 +37,20 @@ public class MessageSerializer implements Serializer { List rowEntries = data.getRawEntries(); // message size int messageSize = 0; - messageSize += com.google.protobuf.CodedOutputStream.computeInt64Size(1, data.getId()); + messageSize += CodedOutputStream.computeInt64Size(1, data.getId()); int dataSize = 0; for (int i = 0; i < rowEntries.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + dataSize += CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); } messageSize += dataSize; messageSize += 1 * rowEntries.size(); // packet size int size = 0; - size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, + size += CodedOutputStream.computeEnumSize(3, PacketType.MESSAGES.getNumber()); - size += com.google.protobuf.CodedOutputStream.computeTagSize(5) - + com.google.protobuf.CodedOutputStream.computeRawVarint32Size(messageSize) + size += CodedOutputStream.computeTagSize(5) + + CodedOutputStream.computeRawVarint32Size(messageSize) + messageSize; // build data byte[] body = new byte[size]; @@ -73,7 +73,7 @@ public class MessageSerializer implements Serializer { } CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); - packetBuilder.setType(CanalPacket.PacketType.MESSAGES); + packetBuilder.setType(PacketType.MESSAGES); packetBuilder.setBody(messageBuilder.build().toByteString()); return packetBuilder.build().toByteArray(); } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalServerStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalServerStarter.java new file mode 100644 index 00000000..18f3f7d4 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalServerStarter.java @@ -0,0 +1,12 @@ +package com.alibaba.otter.canal.server; + +/** + * 外部服务如Kafka, RocketMQ启动接口 + * + * @author machengyuan 2018-8-23 下午05:20:29 + * @version 1.0.0 + */ +public interface CanalServerStarter { + + void init(); +} From fc3de4b7fdf5ce74f0f82fbc0ae2be3d855baf73 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 23 Aug 2018 17:38:17 +0800 Subject: [PATCH 108/360] =?UTF-8?q?=E6=95=B4=E5=90=88canal.client=E5=92=8C?= =?UTF-8?q?canal.kafka-client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-launcher/pom.xml | 9 +- .../loader/CanalAdapterKafkaWorker.java | 22 ++-- client/pom.xml | 7 ++ .../client/kafka}/KafkaCanalConnector.java | 25 ++-- .../client/kafka}/KafkaCanalConnectors.java | 2 +- .../client/kafka}/MessageDeserializer.java | 2 +- .../kafka}/running/ClientRunningData.java | 2 +- .../kafka}/running/ClientRunningListener.java | 2 +- .../kafka}/running/ClientRunningMonitor.java | 10 +- .../running/kafka}/AbstractKafkaTest.java | 2 +- .../kafka}/CanalKafkaClientExample.java | 6 +- .../kafka}/KafkaClientRunningTest.java | 6 +- kafka-client/pom.xml | 111 ------------------ kafka-client/src/test/resources/logback.xml | 19 --- pom.xml | 1 - 15 files changed, 54 insertions(+), 172 deletions(-) rename {kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client => client/src/main/java/com/alibaba/otter/canal/client/kafka}/KafkaCanalConnector.java (96%) rename {kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client => client/src/main/java/com/alibaba/otter/canal/client/kafka}/KafkaCanalConnectors.java (97%) rename {kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client => client/src/main/java/com/alibaba/otter/canal/client/kafka}/MessageDeserializer.java (97%) rename {kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client => client/src/main/java/com/alibaba/otter/canal/client/kafka}/running/ClientRunningData.java (92%) rename {kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client => client/src/main/java/com/alibaba/otter/canal/client/kafka}/running/ClientRunningListener.java (86%) rename {kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client => client/src/main/java/com/alibaba/otter/canal/client/kafka}/running/ClientRunningMonitor.java (97%) rename {kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running => client/src/test/java/com/alibaba/otter/canal/client/running/kafka}/AbstractKafkaTest.java (92%) rename {kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running => client/src/test/java/com/alibaba/otter/canal/client/running/kafka}/CanalKafkaClientExample.java (96%) rename {kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running => client/src/test/java/com/alibaba/otter/canal/client/running/kafka}/KafkaClientRunningTest.java (90%) delete mode 100644 kafka-client/pom.xml delete mode 100644 kafka-client/src/test/resources/logback.xml diff --git a/client-launcher/pom.xml b/client-launcher/pom.xml index 952aeb8a..1f63fe73 100644 --- a/client-launcher/pom.xml +++ b/client-launcher/pom.xml @@ -18,17 +18,16 @@ client-adapter.common ${project.version} - com.alibaba.otter canal.client ${project.version} - + - com.alibaba.otter - canal.kafka.client - ${project.version} + org.apache.kafka + kafka-clients + 1.1.1 org.yaml diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java index 6b35e452..1842cddb 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java @@ -1,19 +1,25 @@ package com.alibaba.otter.canal.client.adapter.loader; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; -import com.alibaba.otter.canal.client.adapter.loader.AbstractCanalAdapterWorker; -import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector; -import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors; -import com.alibaba.otter.canal.protocol.Message; -import org.apache.kafka.clients.consumer.CommitFailedException; -import org.apache.kafka.common.errors.WakeupException; - import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.kafka.clients.consumer.CommitFailedException; +import org.apache.kafka.common.errors.WakeupException; + +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors; +import com.alibaba.otter.canal.protocol.Message; + +/** + * kafka对应的client适配器工作线程 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { private KafkaCanalConnector connector; diff --git a/client/pom.xml b/client/pom.xml index 038f2a9f..6318f771 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -16,6 +16,13 @@ canal.protocol ${project.version} + + + org.apache.kafka + kafka-clients + 1.1.1 + provided + diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java similarity index 96% rename from kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/KafkaCanalConnector.java rename to client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java index 3e997e27..794f80ab 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/KafkaCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java @@ -1,21 +1,22 @@ -package com.alibaba.otter.canal.kafka.client; +package com.alibaba.otter.canal.client.kafka; + +import java.util.Collections; +import java.util.Properties; +import java.util.concurrent.TimeUnit; -import com.alibaba.otter.canal.common.utils.AddressUtils; -import com.alibaba.otter.canal.common.utils.BooleanMutex; -import com.alibaba.otter.canal.common.zookeeper.ZkClientx; -import com.alibaba.otter.canal.kafka.client.running.ClientRunningData; -import com.alibaba.otter.canal.kafka.client.running.ClientRunningListener; -import com.alibaba.otter.canal.kafka.client.running.ClientRunningMonitor; -import com.alibaba.otter.canal.protocol.Message; -import com.alibaba.otter.canal.protocol.exception.CanalClientException; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.StringDeserializer; -import java.util.Collections; -import java.util.Properties; -import java.util.concurrent.TimeUnit; +import com.alibaba.otter.canal.client.kafka.running.ClientRunningData; +import com.alibaba.otter.canal.client.kafka.running.ClientRunningListener; +import com.alibaba.otter.canal.client.kafka.running.ClientRunningMonitor; +import com.alibaba.otter.canal.common.utils.AddressUtils; +import com.alibaba.otter.canal.common.utils.BooleanMutex; +import com.alibaba.otter.canal.common.zookeeper.ZkClientx; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; /** * canal kafka 数据操作客户端 diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/KafkaCanalConnectors.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnectors.java similarity index 97% rename from kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/KafkaCanalConnectors.java rename to client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnectors.java index 1a2942ab..0634a747 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/KafkaCanalConnectors.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnectors.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client; +package com.alibaba.otter.canal.client.kafka; /** * canal kafka connectors创建工具类 diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java similarity index 97% rename from kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java rename to client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java index 70f2fc06..e716d2d6 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/MessageDeserializer.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client; +package com.alibaba.otter.canal.client.kafka; import java.util.Map; diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningData.java similarity index 92% rename from kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java rename to client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningData.java index 7bbbabd7..e528f9ee 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningData.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningData.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client.running; +package com.alibaba.otter.canal.client.kafka.running; /** * client running状态信息 diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningListener.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningListener.java similarity index 86% rename from kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningListener.java rename to client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningListener.java index d49cf79f..016f7f10 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningListener.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningListener.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client.running; +package com.alibaba.otter.canal.client.kafka.running; /** * client running状态信息 diff --git a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningMonitor.java similarity index 97% rename from kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java rename to client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningMonitor.java index 0f61211e..9a84d81e 100644 --- a/kafka-client/src/main/java/com/alibaba/otter/canal/kafka/client/running/ClientRunningMonitor.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningMonitor.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client.running; +package com.alibaba.otter.canal.client.kafka.running; import java.text.MessageFormat; import java.util.Random; @@ -55,15 +55,15 @@ public class ClientRunningMonitor extends AbstractCanalLifeCycle { } private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class); - private ZkClientx zkClient; + private ZkClientx zkClient; private String topic; - private ClientRunningData clientData; + private ClientRunningData clientData; private IZkDataListener dataListener; - private BooleanMutex mutex = new BooleanMutex(false); + private BooleanMutex mutex = new BooleanMutex(false); private volatile boolean release = false; private volatile ClientRunningData activeData; private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1); - private ClientRunningListener listener; + private ClientRunningListener listener; private int delayTime = 5; private static Integer virtualPort; diff --git a/kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/AbstractKafkaTest.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/AbstractKafkaTest.java similarity index 92% rename from kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/AbstractKafkaTest.java rename to client/src/test/java/com/alibaba/otter/canal/client/running/kafka/AbstractKafkaTest.java index 22dda072..473baf67 100644 --- a/kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/AbstractKafkaTest.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/AbstractKafkaTest.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client.running; +package com.alibaba.otter.canal.client.running.kafka; import org.junit.Assert; diff --git a/kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/CanalKafkaClientExample.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java similarity index 96% rename from kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/CanalKafkaClientExample.java rename to client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java index f4a0afc5..0a53a1df 100644 --- a/kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/CanalKafkaClientExample.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client.running; +package com.alibaba.otter.canal.client.running.kafka; import java.util.concurrent.TimeUnit; @@ -7,8 +7,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; -import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector; -import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors; import com.alibaba.otter.canal.protocol.Message; /** diff --git a/kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/KafkaClientRunningTest.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java similarity index 90% rename from kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/KafkaClientRunningTest.java rename to client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java index d8ff6d30..ebc4d549 100644 --- a/kafka-client/src/test/java/com/alibaba/otter/canal/kafka/client/running/KafkaClientRunningTest.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka.client.running; +package com.alibaba.otter.canal.client.running.kafka; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -9,8 +9,8 @@ import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.kafka.client.KafkaCanalConnector; -import com.alibaba.otter.canal.kafka.client.KafkaCanalConnectors; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors; import com.alibaba.otter.canal.protocol.Message; /** diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml deleted file mode 100644 index 42424ddd..00000000 --- a/kafka-client/pom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - 4.0.0 - - canal - com.alibaba.otter - 1.1.0-SNAPSHOT - ../pom.xml - - com.alibaba.otter - canal.kafka.client - jar - canal kafka client module for otter ${project.version} - - - - - com.alibaba.otter - canal.protocol - ${project.version} - - - org.apache.kafka - kafka-clients - 1.1.1 - - - - - junit - junit - - - - - - dev - - true - - env - !javadoc - - - - - - javadoc - - - env - javadoc - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - package - - jar - - - - - true - public - true -
      ${project.artifactId}-${project.version}
      -
      ${project.artifactId}-${project.version}
      - ${project.artifactId}-${project.version} - - https://github.com/alibaba/canal - - ${project.build.directory}/apidocs/apidocs/${project.version} -
      -
      - - org.apache.maven.plugins - maven-scm-publish-plugin - 1.0-beta-2 - - - attach-javadocs - package - - publish-scm - - - - - ${project.build.directory}/scmpublish - Publishing javadoc for ${project.artifactId}:${project.version} - ${project.build.directory}/apidocs - true - scm:git:git@github.com:alibaba/canal.git - gh-pages - - -
      -
      -
      -
      -
      diff --git a/kafka-client/src/test/resources/logback.xml b/kafka-client/src/test/resources/logback.xml deleted file mode 100644 index 81fa0712..00000000 --- a/kafka-client/src/test/resources/logback.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n - - - - - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index 64588a28..71ff9fd5 100644 --- a/pom.xml +++ b/pom.xml @@ -117,7 +117,6 @@ client deployer example - kafka-client prometheus client-adapter client-launcher From 9e3d4a7ffe5371557d79de1899fb68152a1a1d83 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 23 Aug 2018 17:43:53 +0800 Subject: [PATCH 109/360] =?UTF-8?q?HBase=E8=AF=BB=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E7=A9=BA=E8=A1=8C=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/hbase/config/MappingConfigLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index f00e6282..929e9d6a 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -45,7 +45,7 @@ public class MappingConfigLoader { continue; } c = c.trim(); - if (c.startsWith("#")) { + if (c.equals("") || c.startsWith("#")) { continue; } From 1aa2e1a4091c9c36e40baca2cdd633b4e7f13aa1 Mon Sep 17 00:00:00 2001 From: Chuanyi Li L Date: Thu, 23 Aug 2018 17:51:25 +0800 Subject: [PATCH 110/360] Ensure at least 1 thread for thread pool. --- .../canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 63b0f0eb..722450dd 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -80,8 +80,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement this.disruptorMsgBuffer = RingBuffer.createSingleProducer(new MessageEventFactory(), ringBufferSize, new BlockingWaitStrategy()); - - this.parserExecutor = Executors.newFixedThreadPool(parserThreadCount, + int tc = parserThreadCount > 0 ? parserThreadCount : 1; + this.parserExecutor = Executors.newFixedThreadPool(tc, new NamedThreadFactory("MultiStageCoprocessor-Parser-" + destination)); this.stageExecutor = Executors.newFixedThreadPool(2, new NamedThreadFactory("MultiStageCoprocessor-other-" From b7de1e50705fc196d2261a8d983f0228bd86d960 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 24 Aug 2018 09:14:52 +0800 Subject: [PATCH 111/360] =?UTF-8?q?=E5=88=A0=E9=99=A4kafka=20kafka-client?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kafka-client/pom.xml | 0 kafka/pom.xml | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 kafka-client/pom.xml delete mode 100644 kafka/pom.xml diff --git a/kafka-client/pom.xml b/kafka-client/pom.xml deleted file mode 100644 index e69de29b..00000000 diff --git a/kafka/pom.xml b/kafka/pom.xml deleted file mode 100644 index e69de29b..00000000 From 6b94f396ffaca3dfcaef5fb6af4c152982054399 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 24 Aug 2018 10:52:04 +0800 Subject: [PATCH 112/360] =?UTF-8?q?=E7=A7=BB=E9=99=A4kafka=20client?= =?UTF-8?q?=E5=9F=BA=E4=BA=8Ezk=E7=9A=84HA=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/CanalClientConfig.java | 4 +- .../loader/CanalAdapterKafkaWorker.java | 5 +- .../adapter/loader/CanalAdapterLoader.java | 89 ++++++++++--------- .../src/main/resources/canal-client.yml | 19 ++-- .../client/kafka/KafkaCanalConnector.java | 78 ++++++++-------- 5 files changed, 99 insertions(+), 96 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 4f8ff396..f6fa7707 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -20,9 +20,9 @@ public class CanalClientConfig { private String bootstrapServers; - private List kafkaTopics = new ArrayList<>(); + private List kafkaTopics; - private List canalInstances = new ArrayList<>(); + private List canalInstances; public String getCanalServerHost() { return canalServerHost; diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java index 1842cddb..5bccb51a 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java @@ -91,7 +91,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { private void process() { while (!running) ; - ExecutorService executor = Executors.newFixedThreadPool(1); + ExecutorService executor = Executors.newSingleThreadExecutor(); final AtomicBoolean executing = new AtomicBoolean(true); while (running) { try { @@ -142,7 +142,8 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { } }); - while (executing.get()) { // keeping kafka client active + // 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端 + while (executing.get()) { connector.ack(); Thread.sleep(500); } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java index b0fe35c5..98973f11 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java @@ -26,13 +26,13 @@ public class CanalAdapterLoader { private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); - private CanalClientConfig canalClientConfig; + private CanalClientConfig canalClientConfig; private Map canalWorkers = new HashMap<>(); private Map canalKafkaWorkers = new HashMap<>(); - private ExtensionLoader loader; + private ExtensionLoader loader; public CanalAdapterLoader(CanalClientConfig canalClientConfig){ this.canalClientConfig = canalClientConfig; @@ -43,7 +43,7 @@ public class CanalAdapterLoader { */ public void init() { // canal instances 和 kafka topics 配置不能同时为空 - if (canalClientConfig.getCanalInstances().isEmpty() && canalClientConfig.getKafkaTopics().isEmpty()) { + if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getKafkaTopics() == null) { throw new RuntimeException("Blank config property: canalInstances or canalKafkaTopics"); } @@ -58,56 +58,61 @@ public class CanalAdapterLoader { } String zkHosts = this.canalClientConfig.getZookeeperHosts(); - if (zkHosts == null && sa == null) { - throw new RuntimeException("Blank config property: canalServerHost or zookeeperHosts"); - } + // if (zkHosts == null && sa == null) { + // throw new RuntimeException("Blank config property: canalServerHost or + // zookeeperHosts"); + // } // 初始化canal-client的适配器 - for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { - List> canalOuterAdapterGroups = new ArrayList<>(); + if (canalClientConfig.getCanalInstances() != null) { + for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { + List> canalOuterAdapterGroups = new ArrayList<>(); - for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { - List canalOutConnectors = new ArrayList<>(); - for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) { - loadConnector(c, canalOutConnectors); + for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { + List canalOutConnectors = new ArrayList<>(); + for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) { + loadConnector(c, canalOutConnectors); + } + canalOuterAdapterGroups.add(canalOutConnectors); } - canalOuterAdapterGroups.add(canalOutConnectors); + CanalAdapterWorker worker; + if (zkHosts != null) { + worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups); + } else { + worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups); + } + canalWorkers.put(instance.getInstance(), worker); + worker.start(); + logger.info("Start adapter for canal instance: {} succeed", instance.getInstance()); } - CanalAdapterWorker worker; - if (zkHosts != null) { - worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups); - } else { - worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups); - } - canalWorkers.put(instance.getInstance(), worker); - worker.start(); - logger.info("Start adapter for canal instance: {} succeed", instance.getInstance()); } // 初始化canal-client-kafka的适配器 - for (CanalClientConfig.KafkaTopic kafkaTopic : canalClientConfig.getKafkaTopics()) { - for (CanalClientConfig.Group group : kafkaTopic.getGroups()) { - List> canalOuterAdapterGroups = new ArrayList<>(); + if (canalClientConfig.getKafkaTopics() != null) { + for (CanalClientConfig.KafkaTopic kafkaTopic : canalClientConfig.getKafkaTopics()) { + for (CanalClientConfig.Group group : kafkaTopic.getGroups()) { + List> canalOuterAdapterGroups = new ArrayList<>(); - List canalOuterAdapters = new ArrayList<>(); + List canalOuterAdapters = new ArrayList<>(); - for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { - // for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) { - loadConnector(config, canalOuterAdapters); - // } + for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { + // for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) { + loadConnector(config, canalOuterAdapters); + // } + } + canalOuterAdapterGroups.add(canalOuterAdapters); + + // String zkServers = canalClientConfig.getZookeeperHosts(); + CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts, + canalClientConfig.getBootstrapServers(), + kafkaTopic.getTopic(), + group.getGroupId(), + canalOuterAdapterGroups); + canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker); + canalKafkaWorker.start(); + logger.info("Start adapter for canal-client kafka topic: {} succeed", + kafkaTopic.getTopic() + "-" + group.getGroupId()); } - canalOuterAdapterGroups.add(canalOuterAdapters); - - String zkServers = canalClientConfig.getZookeeperHosts(); - CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkServers, - canalClientConfig.getBootstrapServers(), - kafkaTopic.getTopic(), - group.getGroupId(), - canalOuterAdapterGroups); - canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker); - canalKafkaWorker.start(); - logger.info("Start adapter for canal-client kafka topic: {} succeed", - kafkaTopic.getTopic() + "-" + group.getGroupId()); } } } diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml index 2412e38d..12e016a7 100644 --- a/client-launcher/src/main/resources/canal-client.yml +++ b/client-launcher/src/main/resources/canal-client.yml @@ -1,6 +1,6 @@ canalServerHost: 127.0.0.1:11111 -#zookeeperHosts: 127.0.0.1:2181 -#bootstrapServers: kafka1.mytest.com:9092,kafka2.mytest.com:9092 +#zookeeperHosts: slave1:2181 +#bootstrapServers: slave1:6667,slave2:6667 canalInstances: - instance: example @@ -10,13 +10,12 @@ canalInstances: - name: hbase hosts: slave1:2181 properties: {znodeParent: "/hbase-unsecure"} - #kafkaTopics: -#- topic: devmysql4308 +#- topic: example # groups: -# - groupId: devmysql4308_es -# adapters: -# - name: es -# hosts: -# zkHosts: -# properties: {clusterName: es-service-test} +# - groupId: example_g1 +# outAdapters: +# - name: logger +# - name: hbase +# hosts: slave1:2181 +# properties: {znodeParent: "/hbase-unsecure"} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java index 794f80ab..c5b2f8fc 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java @@ -10,10 +10,7 @@ import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.StringDeserializer; import com.alibaba.otter.canal.client.kafka.running.ClientRunningData; -import com.alibaba.otter.canal.client.kafka.running.ClientRunningListener; -import com.alibaba.otter.canal.client.kafka.running.ClientRunningMonitor; import com.alibaba.otter.canal.common.utils.AddressUtils; -import com.alibaba.otter.canal.common.utils.BooleanMutex; import com.alibaba.otter.canal.common.zookeeper.ZkClientx; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.exception.CanalClientException; @@ -27,16 +24,16 @@ import com.alibaba.otter.canal.protocol.exception.CanalClientException; public class KafkaCanalConnector { private KafkaConsumer kafkaConsumer; - private String topic; - private Integer partition; - private Properties properties; - private ClientRunningMonitor runningMonitor; // 运行控制 - private ZkClientx zkClientx; - private BooleanMutex mutex = new BooleanMutex(false); - private volatile boolean connected = false; - private volatile boolean running = false; + private String topic; + private Integer partition; + private Properties properties; + // private ClientRunningMonitor runningMonitor; // 运行控制 + // private BooleanMutex mutex = new BooleanMutex(false); + private ZkClientx zkClientx; + private volatile boolean connected = false; + private volatile boolean running = false; - public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId) { + public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId){ this.topic = topic; this.partition = partition; @@ -45,7 +42,7 @@ public class KafkaCanalConnector { properties.put("group.id", groupId); properties.put("enable.auto.commit", false); properties.put("auto.commit.interval.ms", "1000"); - properties.put("auto.offset.reset", "latest"); //如果没有offset则从最后的offset开始读 + properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读 properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置 properties.put("session.timeout.ms", "30000"); // 默认为30秒 properties.put("max.poll.records", "1"); // 所以一次只取一条数据 @@ -59,19 +56,19 @@ public class KafkaCanalConnector { clientData.setGroupId(groupId); clientData.setAddress(AddressUtils.getHostIp()); - runningMonitor = new ClientRunningMonitor(); - runningMonitor.setTopic(topic); - runningMonitor.setZkClient(zkClientx); - runningMonitor.setClientData(clientData); - runningMonitor.setListener(new ClientRunningListener() { - public void processActiveEnter() { - mutex.set(true); - } - - public void processActiveExit() { - mutex.set(false); - } - }); + // runningMonitor = new ClientRunningMonitor(); + // runningMonitor.setTopic(topic); + // runningMonitor.setZkClient(zkClientx); + // runningMonitor.setClientData(clientData); + // runningMonitor.setListener(new ClientRunningListener() { + // public void processActiveEnter() { + // mutex.set(true); + // } + // + // public void processActiveExit() { + // mutex.set(false); + // } + // }); } } @@ -96,11 +93,11 @@ public class KafkaCanalConnector { return; } - if (runningMonitor != null) { - if (!runningMonitor.isStart()) { - runningMonitor.start(); - } - } + // if (runningMonitor != null) { + // if (!runningMonitor.isStart()) { + // runningMonitor.start(); + // } + // } connected = true; @@ -116,9 +113,9 @@ public class KafkaCanalConnector { kafkaConsumer.close(); connected = false; - if (runningMonitor.isStart()) { - runningMonitor.stop(); - } + // if (runningMonitor.isStart()) { + // runningMonitor.stop(); + // } } private void waitClientRunning() { @@ -129,12 +126,12 @@ public class KafkaCanalConnector { } running = true; - mutex.get();// 阻塞等待 + // mutex.get();// 阻塞等待 } else { // 单机模式直接设置为running running = true; } - } catch (InterruptedException e) { + } catch (Exception e) { Thread.currentThread().interrupt(); throw new CanalClientException(e); } @@ -142,7 +139,8 @@ public class KafkaCanalConnector { public boolean checkValid() { if (zkClientx != null) { - return mutex.state(); + // return mutex.state(); + return true; } else { return true;// 默认都放过 } @@ -235,9 +233,9 @@ public class KafkaCanalConnector { public void stopRunning() { if (running) { running = false; // 设置为非running状态 - if (!mutex.state()) { - mutex.set(true); // 中断阻塞 - } + // if (!mutex.state()) { + // mutex.set(true); // 中断阻塞 + // } } } } From c1f92858a27f201504bcd86eea69f0cee34071c5 Mon Sep 17 00:00:00 2001 From: winger Date: Sat, 25 Aug 2018 03:54:38 +0800 Subject: [PATCH 113/360] =?UTF-8?q?fix=20bug:=20=E5=B9=B6=E8=A1=8C?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=A8=A1=E5=BC=8F=E4=B8=8B=EF=BC=8C=E5=BC=80?= =?UTF-8?q?=E5=90=AFgtid=20=E4=BC=9A=E5=AF=BC=E8=87=B4=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbsync/pom.xml | 5 +++++ .../taobao/tddl/dbsync/binlog/LogContext.java | 20 +++++++++++++++++++ .../taobao/tddl/dbsync/binlog/LogDecoder.java | 14 ++++++++++++- .../tddl/dbsync/binlog/event/LogHeader.java | 13 ++++++++++++ .../inbound/mysql/dbsync/LogEventConvert.java | 11 ++++------ 5 files changed, 55 insertions(+), 8 deletions(-) diff --git a/dbsync/pom.xml b/dbsync/pom.xml index f4d20a10..ac606af4 100644 --- a/dbsync/pom.xml +++ b/dbsync/pom.xml @@ -11,6 +11,11 @@ jar canal dbsync module for otter ${project.version} + + com.alibaba.otter + canal.parse.driver + ${project.version} + ch.qos.logback diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java index 8d88d00d..80b2c254 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java @@ -3,7 +3,10 @@ package com.taobao.tddl.dbsync.binlog; import java.util.HashMap; import java.util.Map; +import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; +import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent; +import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent; import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; /** @@ -20,6 +23,8 @@ public final class LogContext { private LogPosition logPosition; + private GTIDSet gtidSet; + public LogContext(){ this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x; } @@ -60,4 +65,19 @@ public final class LogContext { formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x; mapOfTable.clear(); } + + public final void putGtid(GtidLogEvent logEvent) { + if (logEvent != null) { + String gtid = logEvent.getSid().toString() + ":" + logEvent.getGno(); + if (gtidSet == null) { + gtid = logEvent.getSid().toString() + ":1-" + logEvent.getGno(); + gtidSet = MysqlGTIDSet.parse(gtid); + } + gtidSet.update(gtid); + } + } + + public GTIDSet getGtidSet() { + return gtidSet; + } } diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java index bbb358b7..d094b2fd 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java @@ -3,6 +3,7 @@ package com.taobao.tddl.dbsync.binlog; import java.io.IOException; import java.util.BitSet; +import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -159,18 +160,19 @@ public final class LogDecoder { // remove checksum bytes buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN); } - switch (header.getType()) { case LogEvent.QUERY_EVENT: { QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.XID_EVENT: { XidLogEvent event = new XidLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.TABLE_MAP_EVENT: { @@ -268,12 +270,14 @@ public final class LogDecoder { RandLogEvent event = new RandLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.USER_VAR_EVENT: { UserVarLogEvent event = new UserVarLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.FORMAT_DESCRIPTION_EVENT: { @@ -324,6 +328,7 @@ public final class LogDecoder { HeartbeatLogEvent event = new HeartbeatLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.IGNORABLE_LOG_EVENT: { @@ -336,6 +341,7 @@ public final class LogDecoder { RowsQueryLogEvent event = new RowsQueryLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.WRITE_ROWS_EVENT: { @@ -343,6 +349,7 @@ public final class LogDecoder { /* updating position in context */ logPosition.position = header.getLogPos(); event.fillTable(context); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.UPDATE_ROWS_EVENT: { @@ -350,6 +357,7 @@ public final class LogDecoder { /* updating position in context */ logPosition.position = header.getLogPos(); event.fillTable(context); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.DELETE_ROWS_EVENT: { @@ -357,6 +365,7 @@ public final class LogDecoder { /* updating position in context */ logPosition.position = header.getLogPos(); event.fillTable(context); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.GTID_LOG_EVENT: @@ -364,6 +373,8 @@ public final class LogDecoder { GtidLogEvent event = new GtidLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + // update latest gtid + context.putGtid(event); return event; } case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: { @@ -394,6 +405,7 @@ public final class LogDecoder { AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); + header.putGtidStr(context.getGtidSet()); return event; } case LogEvent.BINLOG_CHECKPOINT_EVENT: { diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java index 229693a2..77e48ddb 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java @@ -1,5 +1,6 @@ package com.taobao.tddl.dbsync.binlog.event; +import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; import com.taobao.tddl.dbsync.binlog.LogBuffer; import com.taobao.tddl.dbsync.binlog.LogEvent; @@ -120,6 +121,8 @@ public final class LogHeader { */ protected String logFileName; + protected String gtidStr; + /* for Start_event_v3 */ public LogHeader(final int type){ this.type = type; @@ -288,4 +291,14 @@ public final class LogHeader { crc = buffer.getUint32(eventLen - LogEvent.BINLOG_CHECKSUM_LEN); } } + + public String getGtidStr() { + return gtidStr; + } + + public void putGtidStr(GTIDSet gtidSet) { + if (gtidSet != null) { + this.gtidStr = gtidSet.toString(); + } + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index e161e34e..5e679639 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -170,15 +170,13 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar return entryBuilder.build(); } - private Entry parseGTIDLogEvent(GtidLogEvent logEvent) { + public Entry parseGTIDLogEvent(GtidLogEvent logEvent) { LogHeader logHeader = logEvent.getHeader(); String value = logEvent.getSid().toString() + ":" + logEvent.getGno(); Pair.Builder builder = Pair.newBuilder(); builder.setKey("gtid"); builder.setValue(value); - if (gtidSet != null) { - gtidSet.update(value); - } + if (logEvent.getLastCommitted() != null) { builder.setKey("lastCommitted"); builder.setValue(String.valueOf(logEvent.getLastCommitted())); @@ -845,9 +843,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar } headerBuilder.setEventLength(logHeader.getEventLen()); // enable gtid position - if (gtidSet != null) { - String gtid = gtidSet.toString(); - headerBuilder.setGtid(gtid); + if (StringUtils.isNotEmpty(logHeader.getGtidStr())) { + headerBuilder.setGtid(logHeader.getGtidStr()); } // add rowsCount suppport From 6fe3ef43d86f77718ea701136c705b3cbc08e3cf Mon Sep 17 00:00:00 2001 From: winger Date: Sat, 25 Aug 2018 09:17:13 +0800 Subject: [PATCH 114/360] =?UTF-8?q?fix=20bug:=20=E5=B9=B6=E8=A1=8C?= =?UTF-8?q?=E8=A7=A3=E6=9E=90=E6=A8=A1=E5=BC=8F=E4=B8=8B=EF=BC=8C=E5=BC=80?= =?UTF-8?q?=E5=90=AFgtid=20=E4=BC=9A=E5=AF=BC=E8=87=B4=E8=A7=A3=E6=9E=90?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 5e679639..cb579961 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -170,7 +170,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar return entryBuilder.build(); } - public Entry parseGTIDLogEvent(GtidLogEvent logEvent) { + private Entry parseGTIDLogEvent(GtidLogEvent logEvent) { LogHeader logHeader = logEvent.getHeader(); String value = logEvent.getSid().toString() + ":" + logEvent.getGno(); Pair.Builder builder = Pair.newBuilder(); From b5e05c8c77fd7091901500360e28f568b20ca89b Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 28 Aug 2018 13:51:23 +0800 Subject: [PATCH 115/360] =?UTF-8?q?kafka=20client=20keep=20alive=E9=97=B4?= =?UTF-8?q?=E9=9A=94=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/adapter/loader/CanalAdapterKafkaWorker.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java index 5bccb51a..aecaceb2 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java @@ -143,9 +143,13 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { }); // 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端 + long currentTS = System.currentTimeMillis(); while (executing.get()) { - connector.ack(); - Thread.sleep(500); + // 大于1分钟未消费完ack一次keep alive + if (System.currentTimeMillis() - currentTS > 60000) { + connector.ack(); + currentTS = System.currentTimeMillis(); + } } } else { connector.ack(); From bcf30a9a05934e30220b9f8e6aec41f2d0246b6f Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 28 Aug 2018 15:52:36 +0800 Subject: [PATCH 116/360] =?UTF-8?q?=E5=8E=BB=E6=8E=89kafka=20canalBatchSiz?= =?UTF-8?q?e=E4=BD=BF=E7=94=A8=E9=BB=98=E8=AE=A450?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/kafka.yml | 2 -- .../java/com/alibaba/otter/canal/kafka/KafkaProperties.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml index 93d4d557..8abf4934 100644 --- a/deployer/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/kafka.yml @@ -3,8 +3,6 @@ retries: 0 batchSize: 16384 lingerMs: 1 bufferMemory: 33554432 -# canal的批次大小,单位 k,量大建议改为1M -canalBatchSize: 50 filterTransactionEntry: true canalDestinations: diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java index 140ce965..9194c0ab 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java @@ -19,7 +19,7 @@ public class KafkaProperties { private int lingerMs = 1; private long bufferMemory = 33554432L; private boolean filterTransactionEntry = true; - private int canalBatchSize = 5; + private int canalBatchSize = 50; private List canalDestinations = new ArrayList(); From ea81b1dc92cdfd0ddadd6db36c25ca4989257109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E9=91=AB?= Date: Wed, 29 Aug 2018 14:37:27 +0800 Subject: [PATCH 117/360] fix #893 index name contains "on" keyword --- .../parse/inbound/mysql/ddl/SimpleDdlParser.java | 4 ++-- .../parse/inbound/mysql/SimpleDdlParserTest.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/SimpleDdlParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/SimpleDdlParser.java index 53f7de82..57d95dcc 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/SimpleDdlParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/SimpleDdlParser.java @@ -44,8 +44,8 @@ public class SimpleDdlParser { * http://dev.mysql.com/doc/refman/5.6/en/create-index.html * */ - public static final String CREATE_INDEX_PATTERN = "^\\s*CREATE\\s*(UNIQUE)?(FULLTEXT)?(SPATIAL)?\\s*INDEX\\s*(.*?)\\s*ON\\s*(.*?)$"; - public static final String DROP_INDEX_PATTERN = "^\\s*DROP\\s*INDEX\\s*(.*?)\\s*ON\\s*(.*?)$"; + public static final String CREATE_INDEX_PATTERN = "^\\s*CREATE\\s*(UNIQUE)?(FULLTEXT)?(SPATIAL)?\\s*INDEX\\s*(.*?)\\s+ON\\s+(.*?)$"; + public static final String DROP_INDEX_PATTERN = "^\\s*DROP\\s*INDEX\\s*(.*?)\\s+ON\\s+(.*?)$"; public static DdlResult parse(String queryString, String schmeaName) { queryString = removeComment(queryString); // 去除/* */的sql注释内容 diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java index f2bb1ce4..f0aaf67a 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java @@ -225,5 +225,19 @@ public class SimpleDdlParserTest { Assert.assertNotNull(result); Assert.assertEquals("retl", result.getSchemaName()); Assert.assertEquals("retl_mark", result.getTableName()); + + //test index name contains 'on' -- version + queryString = "create index schema_new_index_version_s_idx on q_contract_account (contract_id,main_contract_id)"; + result = SimpleDdlParser.parse(queryString, "retl"); + Assert.assertNotNull(result); + Assert.assertEquals("retl", result.getSchemaName()); + Assert.assertEquals("q_contract_account", result.getTableName()); + + queryString = "drop index schema_new_index_version_s_idx on q_contract_account"; + result = SimpleDdlParser.parse(queryString, "retl"); + Assert.assertNotNull(result); + Assert.assertEquals("retl", result.getSchemaName()); + Assert.assertEquals("q_contract_account", result.getTableName()); + } } From 06275a46ed0a0dfbf8a1a9aa2e4699d005084e7a Mon Sep 17 00:00:00 2001 From: Chuanyi Li Date: Wed, 29 Aug 2018 19:52:09 +0800 Subject: [PATCH 118/360] Fix no data points while execTime exceeding current time of canal server. --- .../canal/prometheus/impl/EntryCollector.java | 6 +++--- .../canal/prometheus/impl/StoreCollector.java | 16 +++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java index 9242bd11..bbd51720 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/EntryCollector.java @@ -53,9 +53,9 @@ public class EntryCollector extends Collector implements InstanceRegistry { for (EntryMetricsHolder emh : instances.values()) { long now = System.currentTimeMillis(); long latest = emh.latestExecTime.get(); - if (now >= latest) { - delay.addMetric(emh.destLabelValues, (now - latest)); - } + // execTime > now,delay显示为0 + long d = (now >= latest) ? (now - latest) : 0; + delay.addMetric(emh.destLabelValues, d); transactions.addMetric(emh.destLabelValues, emh.transactionCounter.doubleValue()); } mfs.add(delay); diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java index 40186dc0..cb26f228 100644 --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/StoreCollector.java @@ -99,15 +99,13 @@ public class StoreCollector extends Collector implements InstanceRegistry { long get = Math.min(smh.getExecTime.get(), pet); long aet = Math.min(smh.ackExecTime.get(), get); long now = System.currentTimeMillis(); - if (now >= pet) { - putDelay.addMetric(smh.destLabelValues, (now - pet)); - } - if (now >= get) { - getDelay.addMetric(smh.destLabelValues, (now - get)); - } - if (now >= aet) { - ackDelay.addMetric(smh.destLabelValues, (now - aet)); - } + // execTime > now,delay显示为0 + long pd = (now >= pet) ? (now - pet) : 0; + putDelay.addMetric(smh.destLabelValues, pd); + long gd = (now >= get) ? (now - get) : 0; + getDelay.addMetric(smh.destLabelValues, gd); + long ad = (now >= aet) ? (now - aet) : 0; + ackDelay.addMetric(smh.destLabelValues, ad); putRows.addMetric(smh.destLabelValues, smh.putTableRows.doubleValue()); getRows.addMetric(smh.destLabelValues, smh.getTableRows.doubleValue()); ackRows.addMetric(smh.destLabelValues, smh.ackTableRows.doubleValue()); From ee5775ce05acfc053842a69f24ef745c76c74171 Mon Sep 17 00:00:00 2001 From: machey Date: Sun, 2 Sep 2018 20:21:07 +0800 Subject: [PATCH 119/360] =?UTF-8?q?=E5=8E=BB=E6=8E=89kafka=20server?= =?UTF-8?q?=E7=AB=AFTRANSACTIONBEGIN=E5=92=8CTRANSACTIONEND=E7=9A=84?= =?UTF-8?q?=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/kafka/KafkaCanalConnector.java | 2 +- .../otter/canal/kafka/CanalKafkaProducer.java | 28 +------------------ .../otter/canal/kafka/CanalKafkaStarter.java | 6 ++-- 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java index c5b2f8fc..7afb9c09 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java @@ -45,7 +45,7 @@ public class KafkaCanalConnector { properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读 properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置 properties.put("session.timeout.ms", "30000"); // 默认为30秒 - properties.put("max.poll.records", "1"); // 所以一次只取一条数据 + properties.put("max.poll.records", "1"); // 一次只取一条message数据 properties.put("key.deserializer", StringDeserializer.class.getName()); properties.put("value.deserializer", MessageDeserializer.class.getName()); diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index b6dba051..a6d084e4 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -48,33 +48,7 @@ public class CanalKafkaProducer { } } - public void send(KafkaProperties.Topic topic, Message message) throws IOException { - // set canal.instance.filter.transaction.entry = true - - // boolean valid = false; - // if (message != null) { - // if (message.isRaw() && !message.getRawEntries().isEmpty()) { - // for (ByteString byteString : message.getRawEntries()) { - // CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString); - // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - // valid = true; - // break; - // } - // } - // } else if (!message.getEntries().isEmpty()){ - // for (CanalEntry.Entry entry : message.getEntries()) { - // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - // valid = true; - // break; - // } - // } - // } - // } - // if (!valid) { - // return; - // } + public void send(KafkaProperties.Topic topic, Message message) { ProducerRecord record; if (topic.getPartition() != null) { record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index d244ce31..feef8038 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -54,9 +54,9 @@ public class CanalKafkaStarter implements CanalServerStarter { canalKafkaProducer = new CanalKafkaProducer(); canalKafkaProducer.init(kafkaProperties); // set filterTransactionEntry - if (kafkaProperties.isFilterTransactionEntry()) { - System.setProperty("canal.instance.filter.transaction.entry", "true"); - } + // if (kafkaProperties.isFilterTransactionEntry()) { + // System.setProperty("canal.instance.filter.transaction.entry", "true"); + // } // 对应每个instance启动一个worker线程 List destinations = kafkaProperties.getCanalDestinations(); From 7de40091a34aaef868fa1244dc29a46602f625e9 Mon Sep 17 00:00:00 2001 From: winger Date: Mon, 3 Sep 2018 15:28:15 +0800 Subject: [PATCH 120/360] fix bug #890 --- .../taobao/tddl/dbsync/binlog/LogContext.java | 15 ++++--------- .../taobao/tddl/dbsync/binlog/LogDecoder.java | 21 ++++++++++++------- .../dbsync/binlog/event/GtidLogEvent.java | 7 +++++++ .../parse/inbound/AbstractEventParser.java | 10 ++++++--- .../mysql/AbstractMysqlEventParser.java | 7 ------- .../parse/inbound/mysql/MysqlConnection.java | 2 ++ .../mysql/MysqlMultiStageCoprocessor.java | 8 +++++++ .../inbound/mysql/dbsync/LogEventConvert.java | 14 +------------ 8 files changed, 42 insertions(+), 42 deletions(-) diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java index 80b2c254..11005197 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java @@ -66,18 +66,11 @@ public final class LogContext { mapOfTable.clear(); } - public final void putGtid(GtidLogEvent logEvent) { - if (logEvent != null) { - String gtid = logEvent.getSid().toString() + ":" + logEvent.getGno(); - if (gtidSet == null) { - gtid = logEvent.getSid().toString() + ":1-" + logEvent.getGno(); - gtidSet = MysqlGTIDSet.parse(gtid); - } - gtidSet.update(gtid); - } - } - public GTIDSet getGtidSet() { return gtidSet; } + + public void setGtidSet(GTIDSet gtidSet) { + this.gtidSet = gtidSet; + } } diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java index d094b2fd..c4a99852 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.BitSet; import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; +import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -47,22 +48,22 @@ import com.taobao.tddl.dbsync.binlog.event.mariadb.StartEncryptionLogEvent; /** * Implements a binary-log decoder. - * + * *
        * LogDecoder decoder = new LogDecoder();
        * decoder.handle(...);
      - * 
      + *
        * LogEvent event;
        * do
        * {
        *     event = decoder.decode(buffer, context);
      - * 
      + *
        *     // process log event.
        * }
        * while (event != null);
        * // no more events in buffer.
        * 
      - * + * * @author Changyuan.lh * @version 1.0 */ @@ -89,7 +90,7 @@ public final class LogDecoder { /** * Decoding an event from binary-log buffer. - * + * * @return UknownLogEvent if event type is unknown or skipped, * null if buffer is not including a full event. */ @@ -141,7 +142,7 @@ public final class LogDecoder { /** * Deserialize an event from buffer. - * + * * @return UknownLogEvent if event type is unknown or skipped. */ public static LogEvent decode(LogBuffer buffer, LogHeader header, LogContext context) throws IOException { @@ -160,6 +161,7 @@ public final class LogDecoder { // remove checksum bytes buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN); } + GTIDSet gtidSet = context.getGtidSet(); switch (header.getType()) { case LogEvent.QUERY_EVENT: { QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent); @@ -373,8 +375,11 @@ public final class LogDecoder { GtidLogEvent event = new GtidLogEvent(header, buffer, descriptionEvent); /* updating position in context */ logPosition.position = header.getLogPos(); - // update latest gtid - context.putGtid(event); + if (gtidSet != null) { + gtidSet.update(event.getGtidStr()); + // update latest gtid + header.putGtidStr(gtidSet); + } return event; } case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: { diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java index 5c2678b5..b2897d58 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java @@ -82,4 +82,11 @@ public class GtidLogEvent extends LogEvent { public Long getSequenceNumber() { return sequenceNumber; } + + public String getGtidStr() { + StringBuilder sb = new StringBuilder(); + sb.append(sid.toString()).append(":"); + sb.append(gno); + return sb.toString(); + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index fcae1317..be2354dd 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -8,6 +8,8 @@ import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlMultiStageCoprocessor; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.lang.math.RandomUtils; @@ -235,12 +237,14 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle if (parallel) { // build stage processor multiStageCoprocessor = buildMultiStageCoprocessor(); - multiStageCoprocessor.start(); - if (isGTIDMode()) { // 判断所属instance是否启用GTID模式,是的话调用ErosaConnection中GTID对应方法dump数据 - erosaConnection.dump(MysqlGTIDSet.parse(startPosition.getGtid()), multiStageCoprocessor); + GTIDSet gtidSet = MysqlGTIDSet.parse(startPosition.getGtid()); + ((MysqlMultiStageCoprocessor) multiStageCoprocessor).setGtidSet(gtidSet); + multiStageCoprocessor.start(); + erosaConnection.dump(gtidSet, multiStageCoprocessor); } else { + multiStageCoprocessor.start(); if (StringUtils.isEmpty(startPosition.getJournalName()) && startPosition.getTimestamp() != null) { erosaConnection.dump(startPosition.getTimestamp(), multiStageCoprocessor); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index 941f5ebc..4883d971 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -76,13 +76,6 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { * @return */ protected boolean processTableMeta(EntryPosition position) { - if (isGTIDMode()) { - if (binlogParser instanceof LogEventConvert) { - // 记录gtid - ((LogEventConvert) binlogParser).setGtidSet(MysqlGTIDSet.parse(position.getGtid())); - } - } - if (tableMetaTSDB != null) { if (position.getTimestamp() == null || position.getTimestamp() <= 0) { throw new CanalParseException("use gtid and TableMeta TSDB should be config timestamp > 0"); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index eb3c0e31..256b1ecc 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -179,6 +179,8 @@ public class MysqlConnection implements ErosaConnection { fetcher.start(connector.getChannel()); LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); LogContext context = new LogContext(); + // fix bug: #890 将gtid传输至context中,供decode使用 + context.setGtidSet(gtidSet); while (fetcher.fetch()) { accumulateReceivedBytes(fetcher.limit()); LogEvent event = null; diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java index 722450dd..89c6e842 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java @@ -7,6 +7,7 @@ import java.util.concurrent.locks.LockSupport; import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; import com.alibaba.otter.canal.common.utils.NamedThreadFactory; +import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; import com.alibaba.otter.canal.parse.exception.CanalParseException; import com.alibaba.otter.canal.parse.inbound.ErosaConnection; import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer; @@ -63,6 +64,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement private String destination; private volatile CanalParseException exception; private AtomicLong eventsPublishBlockingTime; + private GTIDSet gtidSet; public MysqlMultiStageCoprocessor(int ringBufferSize, int parserThreadCount, LogEventConvert logEventConvert, EventTransactionBuffer transactionBuffer, String destination){ @@ -227,6 +229,9 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement public SimpleParserStage(){ decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); context = new LogContext(); + if (gtidSet != null) { + context.setGtidSet(gtidSet); + } } public void onEvent(MessageEvent event, long sequence, boolean endOfBatch) throws Exception { @@ -447,4 +452,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement this.eventsPublishBlockingTime = eventsPublishBlockingTime; } + public void setGtidSet(GTIDSet gtidSet) { + this.gtidSet = gtidSet; + } } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index cb579961..17c55e8d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -99,13 +99,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private boolean filterRows = false; private boolean useDruidDdlFilter = true; - // latest gtid - private GTIDSet gtidSet; - - public LogEventConvert(GTIDSet gtidSet){ - this.gtidSet = gtidSet; - } - public LogEventConvert(){ } @@ -172,10 +165,9 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar private Entry parseGTIDLogEvent(GtidLogEvent logEvent) { LogHeader logHeader = logEvent.getHeader(); - String value = logEvent.getSid().toString() + ":" + logEvent.getGno(); Pair.Builder builder = Pair.newBuilder(); builder.setKey("gtid"); - builder.setValue(value); + builder.setValue(logEvent.getGtidStr()); if (logEvent.getLastCommitted() != null) { builder.setKey("lastCommitted"); @@ -974,8 +966,4 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar public void setFilterRows(boolean filterRows) { this.filterRows = filterRows; } - - public void setGtidSet(GTIDSet gtidSet) { - this.gtidSet = gtidSet; - } } From ebf91397627e22cfbf07263663e40bdb67dbdc4a Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 3 Sep 2018 17:40:30 +0800 Subject: [PATCH 121/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0kafka=20producer=20se?= =?UTF-8?q?nder=E7=9A=84callback=EF=BC=8Ccanal=E5=92=8Ckafka=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E6=8F=90=E4=BA=A4=E5=9B=9E=E6=BB=9A=20=E5=8E=BB?= =?UTF-8?q?=E9=99=A4=E4=B8=80=E4=B8=AAcanal=E5=AE=9E=E4=BE=8B=E5=AF=B9?= =?UTF-8?q?=E5=BA=94=E5=A4=9A=E4=B8=AAkafka=20topic=E7=9A=84=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=EF=BC=8C=E4=B8=80=E4=B8=AA=E5=AE=9E=E4=BE=8B=E5=8F=AA?= =?UTF-8?q?=E5=AF=B9=E5=BA=94=E4=B8=80=E4=B8=AAtopic=20kafka=E6=A8=A1?= =?UTF-8?q?=E5=BC=8F=E4=B8=8Bserver=E7=AB=AF=E7=9A=84=E6=89=B9=E6=AC=A1?= =?UTF-8?q?=E5=A4=A7=E5=B0=8F=E4=B8=8D=E8=83=BD=E8=B6=85=E8=BF=871M?= =?UTF-8?q?=EF=BC=8C=E6=9C=80=E5=A4=A7=E4=B8=BA900K?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loader/CanalAdapterKafkaWorker.java | 6 +-- .../adapter/loader/CanalAdapterLoader.java | 15 +++---- .../adapter/loader/CanalAdapterWorker.java | 13 +++--- deployer/src/main/resources/kafka.yml | 6 +-- .../otter/canal/kafka/CanalKafkaProducer.java | 38 +++++++++++++----- .../otter/canal/kafka/CanalKafkaStarter.java | 40 ++++++++++--------- 6 files changed, 66 insertions(+), 52 deletions(-) diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java index aecaceb2..5cf86611 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java @@ -97,9 +97,9 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { try { logger.info("=============> Start to connect topic: {} <=============", this.topic); connector.connect(); - logger.info("=============> Start to subscribe topic: {}<=============", this.topic); + logger.info("=============> Start to subscribe topic: {} <=============", this.topic); connector.subscribe(); - logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); + logger.info("=============> Subscribe topic: {} succeed <=============", this.topic); while (running) { try { // switcher.get(); //等待开关开启 @@ -146,7 +146,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { long currentTS = System.currentTimeMillis(); while (executing.get()) { // 大于1分钟未消费完ack一次keep alive - if (System.currentTimeMillis() - currentTS > 60000) { + if (System.currentTimeMillis() - currentTS > 60000) { connector.ack(); currentTS = System.currentTimeMillis(); } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java index 98973f11..14efe493 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java @@ -1,12 +1,5 @@ package com.alibaba.otter.canal.client.adapter.loader; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; -import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; -import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; -import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; @@ -16,6 +9,14 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; +import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; + /** * 外部适配器的加载器 * diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java index bca40041..e341c6a3 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java @@ -1,16 +1,15 @@ package com.alibaba.otter.canal.client.adapter.loader; -import com.alibaba.otter.canal.client.CanalConnector; -import com.alibaba.otter.canal.client.CanalConnectors; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; -import com.alibaba.otter.canal.client.adapter.loader.AbstractCanalAdapterWorker; -import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; -import com.alibaba.otter.canal.protocol.Message; - import java.net.SocketAddress; import java.util.List; import java.util.concurrent.Executors; +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.CanalConnectors; +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; +import com.alibaba.otter.canal.protocol.Message; + /** * 原生canal-server对应的client适配器工作线程 * diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml index 8abf4934..8f897e6d 100644 --- a/deployer/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/kafka.yml @@ -3,15 +3,11 @@ retries: 0 batchSize: 16384 lingerMs: 1 bufferMemory: 33554432 -filterTransactionEntry: true +canalBatchSize: 50 # Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下) canalDestinations: - canalDestination: example topic: example partition: - # 一个destination可以对应多个topic - #topics: - # - topic: example - # partition: diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index a6d084e4..ae63f415 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -1,11 +1,11 @@ package com.alibaba.otter.canal.kafka; -import java.io.IOException; import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; +import org.apache.kafka.common.KafkaException; import org.apache.kafka.common.serialization.StringSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -48,16 +48,32 @@ public class CanalKafkaProducer { } } - public void send(KafkaProperties.Topic topic, Message message) { - ProducerRecord record; - if (topic.getPartition() != null) { - record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); - } else { - record = new ProducerRecord(topic.getTopic(), message); - } - producer.send(record); - if (logger.isDebugEnabled()) { - logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); + public void send(KafkaProperties.Topic topic, Message message, Callback callback) { + producer.initTransactions(); + try { + producer.beginTransaction(); + ProducerRecord record; + if (topic.getPartition() != null) { + record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); + } else { + record = new ProducerRecord(topic.getTopic(), message); + } + producer.send(record); + producer.commitTransaction(); + callback.commit(); + if (logger.isDebugEnabled()) { + logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); + } + } catch (KafkaException e) { + producer.abortTransaction(); + callback.rollback(); } } + + public interface Callback { + + void commit(); + + void rollback(); + } } diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index feef8038..4ad17e25 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -33,9 +33,9 @@ public class CanalKafkaStarter implements CanalServerStarter { private ExecutorService executorService; - private CanalKafkaProducer canalKafkaProducer; + private CanalKafkaProducer canalKafkaProducer; - private KafkaProperties kafkaProperties; + private KafkaProperties kafkaProperties; public void init() { try { @@ -55,7 +55,7 @@ public class CanalKafkaStarter implements CanalServerStarter { canalKafkaProducer.init(kafkaProperties); // set filterTransactionEntry // if (kafkaProperties.isFilterTransactionEntry()) { - // System.setProperty("canal.instance.filter.transaction.entry", "true"); + // System.setProperty("canal.instance.filter.transaction.entry", "true"); // } // 对应每个instance启动一个worker线程 List destinations = kafkaProperties.getCanalDestinations(); @@ -101,8 +101,8 @@ public class CanalKafkaStarter implements CanalServerStarter { while (!running) ; logger.info("## start the canal consumer: {}.", destination.getCanalDestination()); - CanalServerWithEmbedded server = CanalServerWithEmbedded.instance(); - ClientIdentity clientIdentity = new ClientIdentity(destination.getCanalDestination(), (short) 1001, ""); + final CanalServerWithEmbedded server = CanalServerWithEmbedded.instance(); + final ClientIdentity clientIdentity = new ClientIdentity(destination.getCanalDestination(), (short) 1001, ""); while (running) { try { if (!server.getCanalInstances().containsKey(clientIdentity.getDestination())) { @@ -118,26 +118,28 @@ public class CanalKafkaStarter implements CanalServerStarter { while (running) { Message message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); // 获取指定数量的数据 - long batchId = message.getId(); + final long batchId = message.getId(); try { int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); if (batchId != -1 && size != 0) { - if (!StringUtils.isEmpty(destination.getTopic())) { - Topic topic = new Topic(); - topic.setTopic(destination.getTopic()); - topic.setPartition(destination.getPartition()); - destination.getTopics().add(topic); - } - for (Topic topic : destination.getTopics()) { - canalKafkaProducer.send(topic, message); // 发送message到所有topic - } + Topic topic = new Topic(); + topic.setTopic(destination.getTopic()); + topic.setPartition(destination.getPartition()); + canalKafkaProducer.send(topic, message, new CanalKafkaProducer.Callback() { + + @Override + public void commit() { + server.ack(clientIdentity, batchId); // 提交确认 + } + + @Override + public void rollback() { + server.rollback(clientIdentity, batchId); + } + }); // 发送message到topic } - if (batchId != -1) { - server.ack(clientIdentity, batchId); // 提交确认 - } } catch (Exception e) { - server.rollback(clientIdentity); logger.error(e.getMessage(), e); } } From 17f345bfe5bdf7d7eb00ffe6667573d7ec5bab84 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 3 Sep 2018 18:07:43 +0800 Subject: [PATCH 122/360] =?UTF-8?q?0.11=E7=9A=84=E7=8E=AF=E5=A2=83?= =?UTF-8?q?=E6=9C=AA=E6=B5=8B=E8=BF=87=EF=BC=8C=E5=85=88=E5=8E=BB=E9=99=A4?= =?UTF-8?q?kafka=E7=9A=84=E4=BA=8B=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/alibaba/otter/canal/kafka/CanalKafkaProducer.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index ae63f415..ec1a5926 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -35,6 +35,7 @@ public class CanalKafkaProducer { properties.put("key.serializer", StringSerializer.class.getName()); properties.put("value.serializer", MessageSerializer.class.getName()); producer = new KafkaProducer(properties); + // producer.initTransactions(); } public void stop() { @@ -49,9 +50,8 @@ public class CanalKafkaProducer { } public void send(KafkaProperties.Topic topic, Message message, Callback callback) { - producer.initTransactions(); try { - producer.beginTransaction(); + // producer.beginTransaction(); ProducerRecord record; if (topic.getPartition() != null) { record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); @@ -59,13 +59,13 @@ public class CanalKafkaProducer { record = new ProducerRecord(topic.getTopic(), message); } producer.send(record); - producer.commitTransaction(); + // producer.commitTransaction(); callback.commit(); if (logger.isDebugEnabled()) { logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); } } catch (KafkaException e) { - producer.abortTransaction(); + // producer.abortTransaction(); callback.rollback(); } } From 81c216f1c16a113047202b188a158074c0797612 Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 4 Sep 2018 09:39:02 +0800 Subject: [PATCH 123/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0kafka=20get=E7=9A=84?= =?UTF-8?q?=E8=B6=85=E6=97=B6=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/kafka.yml | 6 +++++- .../alibaba/otter/canal/kafka/CanalKafkaStarter.java | 12 +++++++++++- .../alibaba/otter/canal/kafka/KafkaProperties.java | 9 +++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml index 8f897e6d..edd6b464 100644 --- a/deployer/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/kafka.yml @@ -3,7 +3,11 @@ retries: 0 batchSize: 16384 lingerMs: 1 bufferMemory: 33554432 -canalBatchSize: 50 # Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下) + +# Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下) +canalBatchSize: 50 +# Canal get数据的超时时间, 单位: 毫秒, 空为不限超时 +canalGetTimeout: 100 canalDestinations: - canalDestination: example diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index 4ad17e25..717a4525 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -4,6 +4,7 @@ import java.io.FileInputStream; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -117,7 +118,16 @@ public class CanalKafkaStarter implements CanalServerStarter { logger.info("## the canal consumer {} is running now ......", destination.getCanalDestination()); while (running) { - Message message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); // 获取指定数量的数据 + Message message; + if (kafkaProperties.getCanalGetTimeout() != null) { + message = server.getWithoutAck(clientIdentity, + kafkaProperties.getCanalBatchSize(), + kafkaProperties.getCanalGetTimeout(), + TimeUnit.MILLISECONDS); + } else { + message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); + } + final long batchId = message.getId(); try { int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java index 9194c0ab..40f11bf5 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java @@ -20,6 +20,7 @@ public class KafkaProperties { private long bufferMemory = 33554432L; private boolean filterTransactionEntry = true; private int canalBatchSize = 50; + private Long canalGetTimeout; private List canalDestinations = new ArrayList(); @@ -151,6 +152,14 @@ public class KafkaProperties { this.canalBatchSize = canalBatchSize; } + public Long getCanalGetTimeout() { + return canalGetTimeout; + } + + public void setCanalGetTimeout(Long canalGetTimeout) { + this.canalGetTimeout = canalGetTimeout; + } + public List getCanalDestinations() { return canalDestinations; } From 728550cac3dd9a400f2d0077c363a25b4a3a399b Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 4 Sep 2018 09:56:51 +0800 Subject: [PATCH 124/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0kafka=20producer=20se?= =?UTF-8?q?nd=E7=BB=93=E6=9E=9C=E5=90=8C=E6=AD=A5=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alibaba/otter/canal/kafka/CanalKafkaProducer.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index ec1a5926..0685f76e 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -1,11 +1,12 @@ package com.alibaba.otter.canal.kafka; import java.util.Properties; +import java.util.concurrent.Future; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.common.KafkaException; +import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.common.serialization.StringSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,13 +59,15 @@ public class CanalKafkaProducer { } else { record = new ProducerRecord(topic.getTopic(), message); } - producer.send(record); + Future future = producer.send(record); + future.get(); // producer.commitTransaction(); callback.commit(); if (logger.isDebugEnabled()) { - logger.debug("send message to kafka topic: {} \n {}", topic.getTopic(), message.toString()); + logger.debug("send message to kafka topic: {}", topic.getTopic()); } - } catch (KafkaException e) { + } catch (Exception e) { + logger.error(e.getMessage(), e); // producer.abortTransaction(); callback.rollback(); } From 47ac10ca185ef128ccdd055cb83fea5def7428c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 4 Sep 2018 11:28:56 +0800 Subject: [PATCH 125/360] fixed issue #868 , NPE fixed --- .../otter/canal/store/memory/MemoryEventStoreWithBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java index fb74d5f8..6c6d08ff 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java @@ -435,7 +435,7 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple if (batchMode.isMemSize()) { ackMemSize.addAndGet(memsize); // 尝试清空buffer中的内存,将ack之前的内存全部释放掉 - for (long index = sequence + 1; index <= next; index++) { + for (long index = sequence + 1; index < next; index++) { entries[getIndex(index)] = null;// 设置为null } } From a9cccb3017709f335c57d74354eef72ac97e9df8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Tue, 4 Sep 2018 11:39:23 +0800 Subject: [PATCH 126/360] fixed format --- .../com/taobao/tddl/dbsync/binlog/LogContext.java | 2 -- .../com/taobao/tddl/dbsync/binlog/LogDecoder.java | 7 +++---- .../tddl/dbsync/binlog/event/GtidLogEvent.java | 8 +++----- .../taobao/tddl/dbsync/FetcherPerformanceTest.java | 4 +--- .../parse/exception/PositionNotFoundException.java | 10 +++++----- .../parse/exception/ServerIdNotMatchException.java | 12 ++++++------ .../canal/parse/inbound/AbstractEventParser.java | 4 ++-- .../canal/parse/inbound/ParserExceptionHandler.java | 1 + .../inbound/mysql/AbstractMysqlEventParser.java | 1 - .../inbound/mysql/MysqlMultiStageCoprocessor.java | 4 ++-- .../parse/inbound/mysql/dbsync/LogEventConvert.java | 1 - .../parse/inbound/mysql/SimpleDdlParserTest.java | 2 +- 12 files changed, 24 insertions(+), 32 deletions(-) diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java index 11005197..d8699629 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java @@ -4,9 +4,7 @@ import java.util.HashMap; import java.util.Map; import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; -import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent; -import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent; import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent; /** diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java index c4a99852..cfe51a1c 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java @@ -3,11 +3,10 @@ package com.taobao.tddl.dbsync.binlog; import java.io.IOException; import java.util.BitSet; -import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; -import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet; import com.taobao.tddl.dbsync.binlog.event.AppendBlockLogEvent; import com.taobao.tddl.dbsync.binlog.event.BeginLoadQueryLogEvent; import com.taobao.tddl.dbsync.binlog.event.CreateFileLogEvent; @@ -52,12 +51,12 @@ import com.taobao.tddl.dbsync.binlog.event.mariadb.StartEncryptionLogEvent; *
        * LogDecoder decoder = new LogDecoder();
        * decoder.handle(...);
      - *
      + * 
        * LogEvent event;
        * do
        * {
        *     event = decoder.decode(buffer, context);
      - *
      + * 
        *     // process log event.
        * }
        * while (event != null);
      diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java
      index b2897d58..49557fc6 100644
      --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java
      +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/GtidLogEvent.java
      @@ -14,10 +14,10 @@ import com.taobao.tddl.dbsync.binlog.LogEvent;
       public class GtidLogEvent extends LogEvent {
       
           // / Length of the commit_flag in event encoding
      -    public static final int ENCODED_FLAG_LENGTH = 1;
      +    public static final int ENCODED_FLAG_LENGTH         = 1;
           // / Length of SID in event encoding
      -    public static final int ENCODED_SID_LENGTH  = 16;
      -    public static final int LOGICAL_TIMESTAMP_TYPE_CODE  = 2;
      +    public static final int ENCODED_SID_LENGTH          = 16;
      +    public static final int LOGICAL_TIMESTAMP_TYPE_CODE = 2;
       
           private boolean         commitFlag;
           private UUID            sid;
      @@ -50,8 +50,6 @@ public class GtidLogEvent extends LogEvent {
                   sequenceNumber = buffer.getLong64();
               }
       
      -
      -
               // ignore gtid info read
               // sid.copy_from((uchar *)ptr_buffer);
               // ptr_buffer+= ENCODED_SID_LENGTH;
      diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java
      index f5af66e3..d9b6355b 100644
      --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java
      +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/FetcherPerformanceTest.java
      @@ -15,9 +15,7 @@ public class FetcherPerformanceTest {
               DirectLogFetcher fetcher = new DirectLogFetcher();
               try {
                   Class.forName("com.mysql.jdbc.Driver");
      -            Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306",
      -                "root",
      -                "hello");
      +            Connection connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306", "root", "hello");
                   Statement statement = connection.createStatement();
                   statement.execute("SET @master_binlog_checksum='@@global.binlog_checksum'");
                   statement.execute("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'");
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java
      index d5552a9f..67a1d72d 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/PositionNotFoundException.java
      @@ -8,23 +8,23 @@ public class PositionNotFoundException extends CanalParseException {
       
           private static final long serialVersionUID = -7382448928116244017L;
       
      -    public PositionNotFoundException(String errorCode) {
      +    public PositionNotFoundException(String errorCode){
               super(errorCode);
           }
       
      -    public PositionNotFoundException(String errorCode, Throwable cause) {
      +    public PositionNotFoundException(String errorCode, Throwable cause){
               super(errorCode, cause);
           }
       
      -    public PositionNotFoundException(String errorCode, String errorDesc) {
      +    public PositionNotFoundException(String errorCode, String errorDesc){
               super(errorCode, errorDesc);
           }
       
      -    public PositionNotFoundException(String errorCode, String errorDesc, Throwable cause) {
      +    public PositionNotFoundException(String errorCode, String errorDesc, Throwable cause){
               super(errorCode, errorDesc, cause);
           }
       
      -    public PositionNotFoundException(Throwable cause) {
      +    public PositionNotFoundException(Throwable cause){
               super(cause);
           }
       }
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java
      index 05baee02..4186c6ec 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/exception/ServerIdNotMatchException.java
      @@ -6,27 +6,27 @@ import com.alibaba.otter.canal.common.CanalException;
        * @author chengjin.lyf on 2018/8/8 下午1:07
        * @since 1.0.25
        */
      -public class ServerIdNotMatchException extends CanalException{
      +public class ServerIdNotMatchException extends CanalException {
       
           private static final long serialVersionUID = -6124989280379293953L;
       
      -    public ServerIdNotMatchException(String errorCode) {
      +    public ServerIdNotMatchException(String errorCode){
               super(errorCode);
           }
       
      -    public ServerIdNotMatchException(String errorCode, Throwable cause) {
      +    public ServerIdNotMatchException(String errorCode, Throwable cause){
               super(errorCode, cause);
           }
       
      -    public ServerIdNotMatchException(String errorCode, String errorDesc) {
      +    public ServerIdNotMatchException(String errorCode, String errorDesc){
               super(errorCode, errorDesc);
           }
       
      -    public ServerIdNotMatchException(String errorCode, String errorDesc, Throwable cause) {
      +    public ServerIdNotMatchException(String errorCode, String errorDesc, Throwable cause){
               super(errorCode, errorDesc, cause);
           }
       
      -    public ServerIdNotMatchException(Throwable cause) {
      +    public ServerIdNotMatchException(Throwable cause){
               super(cause);
           }
       }
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java
      index be2354dd..e1f80d76 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java
      @@ -8,8 +8,6 @@ import java.util.TimerTask;
       import java.util.concurrent.atomic.AtomicBoolean;
       import java.util.concurrent.atomic.AtomicLong;
       
      -import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
      -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlMultiStageCoprocessor;
       import org.apache.commons.lang.StringUtils;
       import org.apache.commons.lang.exception.ExceptionUtils;
       import org.apache.commons.lang.math.RandomUtils;
      @@ -21,11 +19,13 @@ import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
       import com.alibaba.otter.canal.common.alarm.CanalAlarmHandler;
       import com.alibaba.otter.canal.filter.CanalEventFilter;
       import com.alibaba.otter.canal.parse.CanalEventParser;
      +import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
       import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
       import com.alibaba.otter.canal.parse.exception.CanalParseException;
       import com.alibaba.otter.canal.parse.exception.PositionNotFoundException;
       import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
       import com.alibaba.otter.canal.parse.inbound.EventTransactionBuffer.TransactionFlushCallback;
      +import com.alibaba.otter.canal.parse.inbound.mysql.MysqlMultiStageCoprocessor;
       import com.alibaba.otter.canal.parse.index.CanalLogPositionManager;
       import com.alibaba.otter.canal.parse.support.AuthenticationInfo;
       import com.alibaba.otter.canal.protocol.CanalEntry;
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java
      index d0face01..3a77291d 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ParserExceptionHandler.java
      @@ -5,5 +5,6 @@ package com.alibaba.otter.canal.parse.inbound;
        * @since 1.0.25
        */
       public interface ParserExceptionHandler {
      +
           void handle(Throwable e);
       }
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
      index 4883d971..e36893a3 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
      @@ -9,7 +9,6 @@ import org.slf4j.LoggerFactory;
       import com.alibaba.otter.canal.filter.CanalEventFilter;
       import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
       import com.alibaba.otter.canal.parse.CanalEventParser;
      -import com.alibaba.otter.canal.parse.driver.mysql.packets.MysqlGTIDSet;
       import com.alibaba.otter.canal.parse.exception.CanalParseException;
       import com.alibaba.otter.canal.parse.inbound.AbstractEventParser;
       import com.alibaba.otter.canal.parse.inbound.BinlogParser;
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java
      index 89c6e842..99bb94f5 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java
      @@ -83,8 +83,8 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
                   ringBufferSize,
                   new BlockingWaitStrategy());
               int tc = parserThreadCount > 0 ? parserThreadCount : 1;
      -        this.parserExecutor = Executors.newFixedThreadPool(tc,
      -            new NamedThreadFactory("MultiStageCoprocessor-Parser-" + destination));
      +        this.parserExecutor = Executors.newFixedThreadPool(tc, new NamedThreadFactory("MultiStageCoprocessor-Parser-"
      +                                                                                      + destination));
       
               this.stageExecutor = Executors.newFixedThreadPool(2, new NamedThreadFactory("MultiStageCoprocessor-other-"
                                                                                           + destination));
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      index 17c55e8d..55090f65 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      @@ -17,7 +17,6 @@ import org.slf4j.LoggerFactory;
       
       import com.alibaba.otter.canal.common.AbstractCanalLifeCycle;
       import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter;
      -import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
       import com.alibaba.otter.canal.parse.exception.CanalParseException;
       import com.alibaba.otter.canal.parse.exception.TableIdNotFoundException;
       import com.alibaba.otter.canal.parse.inbound.BinlogParser;
      diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java
      index f0aaf67a..ed528d86 100644
      --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java
      +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/SimpleDdlParserTest.java
      @@ -226,7 +226,7 @@ public class SimpleDdlParserTest {
               Assert.assertEquals("retl", result.getSchemaName());
               Assert.assertEquals("retl_mark", result.getTableName());
       
      -        //test index name contains 'on' -- version
      +        // test index name contains 'on' -- version
               queryString = "create index schema_new_index_version_s_idx on q_contract_account (contract_id,main_contract_id)";
               result = SimpleDdlParser.parse(queryString, "retl");
               Assert.assertNotNull(result);
      
      From f5bf1667a9bdb5559532ea822615628a6dd68bef Mon Sep 17 00:00:00 2001
      From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= 
      Date: Tue, 4 Sep 2018 12:35:20 +0800
      Subject: [PATCH 127/360] fixed mq and client launcher
      
      ---
       client-launcher/src/main/bin/startup.sh                   | 2 +-
       client-launcher/src/main/bin/stop.sh                      | 4 ++--
       .../com/alibaba/otter/canal/deployer/CanalController.java | 2 +-
       .../com/alibaba/otter/canal/deployer/CanalLauncher.java   | 8 +++++---
       deployer/src/main/resources/kafka.yml                     | 4 +---
       5 files changed, 10 insertions(+), 10 deletions(-)
      
      diff --git a/client-launcher/src/main/bin/startup.sh b/client-launcher/src/main/bin/startup.sh
      index d467b4d2..0e09ddff 100644
      --- a/client-launcher/src/main/bin/startup.sh
      +++ b/client-launcher/src/main/bin/startup.sh
      @@ -74,7 +74,7 @@ else
       fi
       
       JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8"
      -CANAL_OPTS="-DappName=otter-canal -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf"
      +CANAL_OPTS="-DappName=otter-canal-client -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf"
       
       if [ -e $canal_conf -a -e $logback_configurationFile ]
       then 
      diff --git a/client-launcher/src/main/bin/stop.sh b/client-launcher/src/main/bin/stop.sh
      index 0cfcc712..c96f2807 100644
      --- a/client-launcher/src/main/bin/stop.sh
      +++ b/client-launcher/src/main/bin/stop.sh
      @@ -45,7 +45,7 @@ fi
       
       pid=`cat $pidfile`
       if [ "$pid" == "" ] ; then
      -	pid=`get_pid "appName=otter-canal"`
      +	pid=`get_pid "appName=otter-canal-client"`
       fi
       
       echo -e "`hostname`: stopping canal $pid ... "
      @@ -54,7 +54,7 @@ kill $pid
       LOOPS=0
       while (true); 
       do 
      -	gpid=`get_pid "appName=otter-canal" "$pid"`
      +	gpid=`get_pid "appName=otter-canal-client" "$pid"`
           if [ "$gpid" == "" ] ; then
           	echo "Oook! cost:$LOOPS"
           	`rm $pidfile`
      diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java
      index aacace84..42821405 100644
      --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java
      +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java
      @@ -387,7 +387,7 @@ public class CanalController {
               return config;
           }
       
      -    private String getProperty(Properties properties, String key) {
      +    public String getProperty(Properties properties, String key) {
               key = StringUtils.trim(key);
               String value = System.getProperty(key);
       
      diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java
      index 2524186a..62b73b1d 100644
      --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java
      +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java
      @@ -3,12 +3,13 @@ package com.alibaba.otter.canal.deployer;
       import java.io.FileInputStream;
       import java.util.Properties;
       
      -import com.alibaba.otter.canal.kafka.CanalKafkaStarter;
      -import com.alibaba.otter.canal.server.CanalServerStarter;
       import org.apache.commons.lang.StringUtils;
       import org.slf4j.Logger;
       import org.slf4j.LoggerFactory;
       
      +import com.alibaba.otter.canal.kafka.CanalKafkaStarter;
      +import com.alibaba.otter.canal.server.CanalServerStarter;
      +
       /**
        * canal独立版本启动的入口类
        * 
      @@ -55,12 +56,13 @@ public class CanalLauncher {
                   });
       
                   CanalServerStarter canalServerStarter = null;
      -            String serverMode = properties.getProperty(CanalConstants.CANAL_SERVER_MODE, "tcp");
      +            String serverMode = controller.getProperty(properties, CanalConstants.CANAL_SERVER_MODE);
                   if (serverMode.equalsIgnoreCase("kafka")) {
                       canalServerStarter = new CanalKafkaStarter();
                   } else if (serverMode.equalsIgnoreCase("rocketMQ")) {
                       // 预留rocketMQ启动
                   }
      +
                   if (canalServerStarter != null) {
                       canalServerStarter.init();
                   }
      diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml
      index edd6b464..db4a9abe 100644
      --- a/deployer/src/main/resources/kafka.yml
      +++ b/deployer/src/main/resources/kafka.yml
      @@ -12,6 +12,4 @@ canalGetTimeout: 100
       canalDestinations:
         - canalDestination: example
           topic: example
      -    partition:
      -
      -
      +    partition:
      \ No newline at end of file
      
      From 82f8a9fc980dd428eb45c8f66789564632723897 Mon Sep 17 00:00:00 2001
      From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= 
      Date: Tue, 4 Sep 2018 12:55:16 +0800
      Subject: [PATCH 128/360] fixed issue #483 , show slave hosts
      
      ---
       .../parse/driver/mysql/socket/BioSocketChannel.java | 10 ++++++++++
       .../driver/mysql/socket/NettySocketChannel.java     |  4 ++++
       .../parse/driver/mysql/socket/SocketChannel.java    |  2 ++
       .../canal/parse/inbound/mysql/MysqlConnection.java  | 13 +++++++++++--
       .../canal/parse/MysqlBinlogDumpPerformanceTest.java |  4 ++--
       5 files changed, 29 insertions(+), 4 deletions(-)
      
      diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java
      index f746189e..8de2acfd 100644
      --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java
      +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/BioSocketChannel.java
      @@ -138,6 +138,16 @@ public class BioSocketChannel implements SocketChannel {
               if (socket != null) {
                   return socket.getRemoteSocketAddress();
               }
      +
      +        return null;
      +    }
      +
      +    public SocketAddress getLocalSocketAddress() {
      +        Socket socket = this.socket;
      +        if (socket != null) {
      +            return socket.getLocalSocketAddress();
      +        }
      +
               return null;
           }
       
      diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/NettySocketChannel.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/NettySocketChannel.java
      index 782af21e..878dc605 100644
      --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/NettySocketChannel.java
      +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/NettySocketChannel.java
      @@ -216,6 +216,10 @@ public class NettySocketChannel implements SocketChannel {
               return channel != null ? channel.remoteAddress() : null;
           }
       
      +    public SocketAddress getLocalSocketAddress() {
      +        return channel != null ? channel.localAddress() : null;
      +    }
      +
           public void close() {
               if (channel != null) {
                   channel.close();
      diff --git a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/SocketChannel.java b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/SocketChannel.java
      index 67f967ab..b67702ae 100644
      --- a/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/SocketChannel.java
      +++ b/driver/src/main/java/com/alibaba/otter/canal/parse/driver/mysql/socket/SocketChannel.java
      @@ -21,5 +21,7 @@ public interface SocketChannel {
       
           public SocketAddress getRemoteSocketAddress();
       
      +    public SocketAddress getLocalSocketAddress();
      +
           public void close();
       }
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java
      index 256b1ecc..4348dc29 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java
      @@ -4,6 +4,7 @@ import static com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetche
       
       import java.io.IOException;
       import java.net.InetSocketAddress;
      +import java.net.SocketAddress;
       import java.nio.charset.Charset;
       import java.util.List;
       import java.util.concurrent.TimeUnit;
      @@ -254,10 +255,18 @@ public class MysqlConnection implements ErosaConnection {
       
           private void sendRegisterSlave() throws IOException {
               RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket();
      -        cmd.reportHost = authInfo.getAddress().getAddress().getHostAddress();
      +        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.reportPort = authInfo.getAddress().getPort(); // 暂时先用master节点的port
               cmd.serverId = this.slaveId;
               byte[] cmdBody = cmd.toBytes();
       
      diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java
      index 8ca9b044..25c88764 100644
      --- a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java
      +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogDumpPerformanceTest.java
      @@ -20,12 +20,12 @@ public class MysqlBinlogDumpPerformanceTest {
       
           public static void main(String args[]) {
               final MysqlEventParser controller = new MysqlEventParser();
      -        final EntryPosition startPosition = new EntryPosition("mysql-bin.001699", 120L, 100L);
      +        final EntryPosition startPosition = new EntryPosition("mysql-bin.000007", 89796293L, 100L);
               controller.setConnectionCharset(Charset.forName("UTF-8"));
               controller.setSlaveId(3344L);
               controller.setDetectingEnable(false);
               controller.setFilterQueryDml(true);
      -        controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3328), "root", "hello"));
      +        controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("100.81.154.142", 3306), "canal", "canal"));
               controller.setMasterPosition(startPosition);
               controller.setEnableTsdb(false);
               controller.setDestination("example");
      
      From a49796215390b8e74c201ead54e4db95093f5acb Mon Sep 17 00:00:00 2001
      From: WithLin 
      Date: Fri, 7 Sep 2018 14:25:46 +0800
      Subject: [PATCH 129/360] Fix upgrade protobuf version
      
      ---
       pom.xml                                       |     2 +-
       .../otter/canal/protocol/CanalEntry.java      |  7191 +++---
       .../otter/canal/protocol/CanalPacket.java     | 20567 +++++++++-------
       .../otter/canal/protocol/CanalProtocol.proto  |     1 +
       .../otter/canal/protocol/EntryProtocol.proto  |     1 +
       .../listener/ChannelFutureAggregator.java     |    18 +-
       6 files changed, 15212 insertions(+), 12568 deletions(-)
      
      diff --git a/pom.xml b/pom.xml
      index 505e987e..ea1e393c 100644
      --- a/pom.xml
      +++ b/pom.xml
      @@ -237,7 +237,7 @@
                   
                       com.google.protobuf
                       protobuf-java
      -                2.6.1
      +                3.6.1
                   
                   
                       org.apache.ibatis
      diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java
      index 051d8bf6..8807b1fd 100644
      --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java
      +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java
      @@ -5,42 +5,48 @@ package com.alibaba.otter.canal.protocol;
       
       public final class CanalEntry {
         private CanalEntry() {}
      +  public static void registerAllExtensions(
      +      com.google.protobuf.ExtensionRegistryLite registry) {
      +  }
      +
         public static void registerAllExtensions(
             com.google.protobuf.ExtensionRegistry registry) {
      +    registerAllExtensions(
      +        (com.google.protobuf.ExtensionRegistryLite) registry);
         }
         /**
      -   * Protobuf enum {@code com.alibaba.otter.canal.protocol.EntryType}
      -   *
          * 
          **打散后的事件类型,主要用于标识事务的开始,变更数据,结束*
          * 
      + * + * Protobuf enum {@code com.alibaba.otter.canal.protocol.EntryType} */ public enum EntryType implements com.google.protobuf.ProtocolMessageEnum { /** * TRANSACTIONBEGIN = 1; */ - TRANSACTIONBEGIN(0, 1), + TRANSACTIONBEGIN(1), /** * ROWDATA = 2; */ - ROWDATA(1, 2), + ROWDATA(2), /** * TRANSACTIONEND = 3; */ - TRANSACTIONEND(2, 3), + TRANSACTIONEND(3), /** - * HEARTBEAT = 4; - * *
            ** 心跳类型,内部使用,外部暂不可见,可忽略 *
            * 
      + * + * HEARTBEAT = 4; */ - HEARTBEAT(3, 4), + HEARTBEAT(4), /** * GTIDLOG = 5; */ - GTIDLOG(4, 5), + GTIDLOG(5), ; /** @@ -56,11 +62,11 @@ public final class CanalEntry { */ public static final int TRANSACTIONEND_VALUE = 3; /** - * HEARTBEAT = 4; - * *
            ** 心跳类型,内部使用,外部暂不可见,可忽略 *
            * 
      + * + * HEARTBEAT = 4; */ public static final int HEARTBEAT_VALUE = 4; /** @@ -69,9 +75,19 @@ public final class CanalEntry { public static final int GTIDLOG_VALUE = 5; - public final int getNumber() { return value; } + public final int getNumber() { + return value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated public static EntryType valueOf(int value) { + return forNumber(value); + } + + public static EntryType forNumber(int value) { switch (value) { case 1: return TRANSACTIONBEGIN; case 2: return ROWDATA; @@ -86,17 +102,17 @@ public final class CanalEntry { internalGetValueMap() { return internalValueMap; } - private static com.google.protobuf.Internal.EnumLiteMap - internalValueMap = + private static final com.google.protobuf.Internal.EnumLiteMap< + EntryType> internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { public EntryType findValueByNumber(int number) { - return EntryType.valueOf(number); + return EntryType.forNumber(number); } }; public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); + return getDescriptor().getValues().get(ordinal()); } public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { @@ -104,7 +120,7 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return CanalEntry.getDescriptor().getEnumTypes().get(0); + return com.alibaba.otter.canal.protocol.CanalEntry.getDescriptor().getEnumTypes().get(0); } private static final EntryType[] VALUES = values(); @@ -112,17 +128,15 @@ public final class CanalEntry { public static EntryType valueOf( com.google.protobuf.Descriptors.EnumValueDescriptor desc) { if (desc.getType() != getDescriptor()) { - throw new IllegalArgumentException( + throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } return VALUES[desc.getIndex()]; } - private final int index; private final int value; - private EntryType(int index, int value) { - this.index = index; + private EntryType(int value) { this.value = value; } @@ -130,86 +144,86 @@ public final class CanalEntry { } /** - * Protobuf enum {@code com.alibaba.otter.canal.protocol.EventType} - * *
          ** 事件类型 *
          * 
      + * + * Protobuf enum {@code com.alibaba.otter.canal.protocol.EventType} */ public enum EventType implements com.google.protobuf.ProtocolMessageEnum { /** * INSERT = 1; */ - INSERT(0, 1), + INSERT(1), /** * UPDATE = 2; */ - UPDATE(1, 2), + UPDATE(2), /** * DELETE = 3; */ - DELETE(2, 3), + DELETE(3), /** * CREATE = 4; */ - CREATE(3, 4), + CREATE(4), /** * ALTER = 5; */ - ALTER(4, 5), + ALTER(5), /** * ERASE = 6; */ - ERASE(5, 6), + ERASE(6), /** * QUERY = 7; */ - QUERY(6, 7), + QUERY(7), /** * TRUNCATE = 8; */ - TRUNCATE(7, 8), + TRUNCATE(8), /** * RENAME = 9; */ - RENAME(8, 9), + RENAME(9), /** - * CINDEX = 10; - * *
            **CREATE INDEX*
            * 
      + * + * CINDEX = 10; */ - CINDEX(9, 10), + CINDEX(10), /** * DINDEX = 11; */ - DINDEX(10, 11), + DINDEX(11), /** * GTID = 12; */ - GTID(11, 12), + GTID(12), /** - * XACOMMIT = 13; - * *
            ** XA *
            * 
      + * + * XACOMMIT = 13; */ - XACOMMIT(12, 13), + XACOMMIT(13), /** * XAROLLBACK = 14; */ - XAROLLBACK(13, 14), + XAROLLBACK(14), /** - * MHEARTBEAT = 15; - * *
            ** MASTER HEARTBEAT *
            * 
      + * + * MHEARTBEAT = 15; */ - MHEARTBEAT(14, 15), + MHEARTBEAT(15), ; /** @@ -249,11 +263,11 @@ public final class CanalEntry { */ public static final int RENAME_VALUE = 9; /** - * CINDEX = 10; - * *
            **CREATE INDEX*
            * 
      + * + * CINDEX = 10; */ public static final int CINDEX_VALUE = 10; /** @@ -265,11 +279,11 @@ public final class CanalEntry { */ public static final int GTID_VALUE = 12; /** - * XACOMMIT = 13; - * *
            ** XA *
            * 
      + * + * XACOMMIT = 13; */ public static final int XACOMMIT_VALUE = 13; /** @@ -277,18 +291,28 @@ public final class CanalEntry { */ public static final int XAROLLBACK_VALUE = 14; /** - * MHEARTBEAT = 15; - * *
            ** MASTER HEARTBEAT *
            * 
      + * + * MHEARTBEAT = 15; */ public static final int MHEARTBEAT_VALUE = 15; - public final int getNumber() { return value; } + public final int getNumber() { + return value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated public static EventType valueOf(int value) { + return forNumber(value); + } + + public static EventType forNumber(int value) { switch (value) { case 1: return INSERT; case 2: return UPDATE; @@ -313,17 +337,17 @@ public final class CanalEntry { internalGetValueMap() { return internalValueMap; } - private static com.google.protobuf.Internal.EnumLiteMap - internalValueMap = + private static final com.google.protobuf.Internal.EnumLiteMap< + EventType> internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { public EventType findValueByNumber(int number) { - return EventType.valueOf(number); + return EventType.forNumber(number); } }; public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); + return getDescriptor().getValues().get(ordinal()); } public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { @@ -331,7 +355,7 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return CanalEntry.getDescriptor().getEnumTypes().get(1); + return com.alibaba.otter.canal.protocol.CanalEntry.getDescriptor().getEnumTypes().get(1); } private static final EventType[] VALUES = values(); @@ -339,17 +363,15 @@ public final class CanalEntry { public static EventType valueOf( com.google.protobuf.Descriptors.EnumValueDescriptor desc) { if (desc.getType() != getDescriptor()) { - throw new IllegalArgumentException( + throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } return VALUES[desc.getIndex()]; } - private final int index; private final int value; - private EventType(int index, int value) { - this.index = index; + private EventType(int value) { this.value = value; } @@ -357,26 +379,26 @@ public final class CanalEntry { } /** - * Protobuf enum {@code com.alibaba.otter.canal.protocol.Type} - * *
          **数据库类型*
          * 
      + * + * Protobuf enum {@code com.alibaba.otter.canal.protocol.Type} */ public enum Type implements com.google.protobuf.ProtocolMessageEnum { /** * ORACLE = 1; */ - ORACLE(0, 1), + ORACLE(1), /** * MYSQL = 2; */ - MYSQL(1, 2), + MYSQL(2), /** * PGSQL = 3; */ - PGSQL(2, 3), + PGSQL(3), ; /** @@ -393,9 +415,19 @@ public final class CanalEntry { public static final int PGSQL_VALUE = 3; - public final int getNumber() { return value; } + public final int getNumber() { + return value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated public static Type valueOf(int value) { + return forNumber(value); + } + + public static Type forNumber(int value) { switch (value) { case 1: return ORACLE; case 2: return MYSQL; @@ -408,17 +440,17 @@ public final class CanalEntry { internalGetValueMap() { return internalValueMap; } - private static com.google.protobuf.Internal.EnumLiteMap - internalValueMap = + private static final com.google.protobuf.Internal.EnumLiteMap< + Type> internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { public Type findValueByNumber(int number) { - return Type.valueOf(number); + return Type.forNumber(number); } }; public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); + return getDescriptor().getValues().get(ordinal()); } public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { @@ -426,7 +458,7 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return CanalEntry.getDescriptor().getEnumTypes().get(2); + return com.alibaba.otter.canal.protocol.CanalEntry.getDescriptor().getEnumTypes().get(2); } private static final Type[] VALUES = values(); @@ -434,17 +466,15 @@ public final class CanalEntry { public static Type valueOf( com.google.protobuf.Descriptors.EnumValueDescriptor desc) { if (desc.getType() != getDescriptor()) { - throw new IllegalArgumentException( + throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } return VALUES[desc.getIndex()]; } - private final int index; private final int value; - private Type(int index, int value) { - this.index = index; + private Type(int value) { this.value = value; } @@ -456,105 +486,101 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
            **协议头部信息*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ boolean hasHeader(); /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
            **协议头部信息*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - Header getHeader(); + com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader(); /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
            **协议头部信息*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - HeaderOrBuilder getHeaderOrBuilder(); + com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder(); /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
            **打散后的事件类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ boolean hasEntryType(); /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
            **打散后的事件类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ - EntryType getEntryType(); + com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType(); /** - * optional bytes storeValue = 3; - * *
            **传输的二进制数组*
            * 
      + * + * optional bytes storeValue = 3; */ boolean hasStoreValue(); /** - * optional bytes storeValue = 3; - * *
            **传输的二进制数组*
            * 
      + * + * optional bytes storeValue = 3; */ com.google.protobuf.ByteString getStoreValue(); } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Entry} - * *
          ****************************************************************
          * message model
          *如果要在Enum中新增类型,确保以前的类型的下标值不变.
          ***************************************************************
          * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Entry} */ - public static final class Entry extends - com.google.protobuf.GeneratedMessage implements + public static final class Entry extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Entry) EntryOrBuilder { + private static final long serialVersionUID = 0L; // Use Entry.newBuilder() to construct. - private Entry(com.google.protobuf.GeneratedMessage.Builder builder) { + private Entry(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private Entry(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Entry defaultInstance; - public static Entry getDefaultInstance() { - return defaultInstance; + private Entry() { + entryType_ = 2; + storeValue_ = com.google.protobuf.ByteString.EMPTY; } - public Entry getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private Entry( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -566,19 +592,12 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 10: { - Header.Builder subBuilder = null; + com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder subBuilder = null; if (((bitField0_ & 0x00000001) == 0x00000001)) { subBuilder = header_.toBuilder(); } - header_ = input.readMessage(Header.PARSER, extensionRegistry); + header_ = input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Header.PARSER, extensionRegistry); if (subBuilder != null) { subBuilder.mergeFrom(header_); header_ = subBuilder.buildPartial(); @@ -588,12 +607,13 @@ public final class CanalEntry { } case 16: { int rawValue = input.readEnum(); - EntryType value = EntryType.valueOf(rawValue); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EntryType value = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf(rawValue); if (value == null) { unknownFields.mergeVarintField(2, rawValue); } else { bitField0_ |= 0x00000002; - entryType_ = value; + entryType_ = rawValue; } break; } @@ -602,13 +622,20 @@ public final class CanalEntry { storeValue_ = input.readBytes(); break; } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); @@ -616,117 +643,101 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable .ensureFieldAccessorsInitialized( - Entry.class, Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Entry parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Entry(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.Entry.class, com.alibaba.otter.canal.protocol.CanalEntry.Entry.Builder.class); } private int bitField0_; public static final int HEADER_FIELD_NUMBER = 1; - private Header header_; + private com.alibaba.otter.canal.protocol.CanalEntry.Header header_; /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
            **协议头部信息*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ public boolean hasHeader() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
            **协议头部信息*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - public Header getHeader() { - return header_; + public com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader() { + return header_ == null ? com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance() : header_; } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
            **协议头部信息*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - public HeaderOrBuilder getHeaderOrBuilder() { - return header_; + public com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder() { + return header_ == null ? com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance() : header_; } public static final int ENTRYTYPE_FIELD_NUMBER = 2; - private EntryType entryType_; + private int entryType_; /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
            **打散后的事件类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ public boolean hasEntryType() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
            **打散后的事件类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ - public EntryType getEntryType() { - return entryType_; + public com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EntryType result = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf(entryType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA : result; } public static final int STOREVALUE_FIELD_NUMBER = 3; private com.google.protobuf.ByteString storeValue_; /** - * optional bytes storeValue = 3; - * *
            **传输的二进制数组*
            * 
      + * + * optional bytes storeValue = 3; */ public boolean hasStoreValue() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional bytes storeValue = 3; - * *
            **传输的二进制数组*
            * 
      + * + * optional bytes storeValue = 3; */ public com.google.protobuf.ByteString getStoreValue() { return storeValue_; } - private void initFields() { - header_ = Header.getDefaultInstance(); - entryType_ = EntryType.ROWDATA; - storeValue_ = com.google.protobuf.ByteString.EMPTY; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -736,141 +747,212 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeMessage(1, header_); + output.writeMessage(1, getHeader()); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, entryType_.getNumber()); + output.writeEnum(2, entryType_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeBytes(3, storeValue_); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; if (((bitField0_ & 0x00000001) == 0x00000001)) { size += com.google.protobuf.CodedOutputStream - .computeMessageSize(1, header_); + .computeMessageSize(1, getHeader()); } if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(2, entryType_.getNumber()); + .computeEnumSize(2, entryType_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, storeValue_); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.Entry)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.Entry other = (com.alibaba.otter.canal.protocol.CanalEntry.Entry) obj; + + boolean result = true; + result = result && (hasHeader() == other.hasHeader()); + if (hasHeader()) { + result = result && getHeader() + .equals(other.getHeader()); + } + result = result && (hasEntryType() == other.hasEntryType()); + if (hasEntryType()) { + result = result && entryType_ == other.entryType_; + } + result = result && (hasStoreValue() == other.hasStoreValue()); + if (hasStoreValue()) { + result = result && getStoreValue() + .equals(other.getStoreValue()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static Entry parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasHeader()) { + hash = (37 * hash) + HEADER_FIELD_NUMBER; + hash = (53 * hash) + getHeader().hashCode(); + } + if (hasEntryType()) { + hash = (37 * hash) + ENTRYTYPE_FIELD_NUMBER; + hash = (53 * hash) + entryType_; + } + if (hasStoreValue()) { + hash = (37 * hash) + STOREVALUE_FIELD_NUMBER; + hash = (53 * hash) + getStoreValue().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Entry parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Entry parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Entry parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Entry parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Entry parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Entry parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static Entry parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static Entry parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Entry parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(Entry prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Entry prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Entry} - * *
            ****************************************************************
            * message model
            *如果要在Enum中新增类型,确保以前的类型的下标值不变.
            ***************************************************************
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Entry} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Entry) - EntryOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.EntryOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable .ensureFieldAccessorsInitialized( - Entry.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.Entry.class, com.alibaba.otter.canal.protocol.CanalEntry.Entry.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Entry.newBuilder() @@ -879,57 +961,55 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { getHeaderFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); if (headerBuilder_ == null) { - header_ = Header.getDefaultInstance(); + header_ = null; } else { headerBuilder_.clear(); } bitField0_ = (bitField0_ & ~0x00000001); - entryType_ = EntryType.ROWDATA; + entryType_ = 2; bitField0_ = (bitField0_ & ~0x00000002); storeValue_ = com.google.protobuf.ByteString.EMPTY; bitField0_ = (bitField0_ & ~0x00000004); return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; } - public Entry getDefaultInstanceForType() { - return Entry.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Entry getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.Entry.getDefaultInstance(); } - public Entry build() { - Entry result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Entry build() { + com.alibaba.otter.canal.protocol.CanalEntry.Entry result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public Entry buildPartial() { - Entry result = new Entry(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Entry buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.Entry result = new com.alibaba.otter.canal.protocol.CanalEntry.Entry(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -953,17 +1033,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Entry) { - return mergeFrom((Entry)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Entry) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Entry)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(Entry other) { - if (other == Entry.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Entry other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.Entry.getDefaultInstance()) return this; if (other.hasHeader()) { mergeHeader(other.getHeader()); } @@ -973,24 +1086,27 @@ public final class CanalEntry { if (other.hasStoreValue()) { setStoreValue(other.getStoreValue()); } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - Entry parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.Entry parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Entry) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Entry) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -1000,41 +1116,41 @@ public final class CanalEntry { } private int bitField0_; - private Header header_ = Header.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - Header, Header.Builder, HeaderOrBuilder> headerBuilder_; + private com.alibaba.otter.canal.protocol.CanalEntry.Header header_ = null; + private com.google.protobuf.SingleFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Header, com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder, com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder> headerBuilder_; /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ public boolean hasHeader() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - public Header getHeader() { + public com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader() { if (headerBuilder_ == null) { - return header_; + return header_ == null ? com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance() : header_; } else { return headerBuilder_.getMessage(); } } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - public Builder setHeader(Header value) { + public Builder setHeader(com.alibaba.otter.canal.protocol.CanalEntry.Header value) { if (headerBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -1048,14 +1164,14 @@ public final class CanalEntry { return this; } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ public Builder setHeader( - Header.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder builderForValue) { if (headerBuilder_ == null) { header_ = builderForValue.build(); onChanged(); @@ -1066,18 +1182,19 @@ public final class CanalEntry { return this; } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - public Builder mergeHeader(Header value) { + public Builder mergeHeader(com.alibaba.otter.canal.protocol.CanalEntry.Header value) { if (headerBuilder_ == null) { if (((bitField0_ & 0x00000001) == 0x00000001) && - header_ != Header.getDefaultInstance()) { + header_ != null && + header_ != com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance()) { header_ = - Header.newBuilder(header_).mergeFrom(value).buildPartial(); + com.alibaba.otter.canal.protocol.CanalEntry.Header.newBuilder(header_).mergeFrom(value).buildPartial(); } else { header_ = value; } @@ -1089,15 +1206,15 @@ public final class CanalEntry { return this; } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ public Builder clearHeader() { if (headerBuilder_ == null) { - header_ = Header.getDefaultInstance(); + header_ = null; onChanged(); } else { headerBuilder_.clear(); @@ -1106,44 +1223,45 @@ public final class CanalEntry { return this; } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - public Header.Builder getHeaderBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder getHeaderBuilder() { bitField0_ |= 0x00000001; onChanged(); return getHeaderFieldBuilder().getBuilder(); } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - public HeaderOrBuilder getHeaderOrBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder() { if (headerBuilder_ != null) { return headerBuilder_.getMessageOrBuilder(); } else { - return header_; + return header_ == null ? + com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance() : header_; } } /** - * optional .com.alibaba.otter.canal.protocol.Header header = 1; - * *
              **协议头部信息*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Header header = 1; */ - private com.google.protobuf.SingleFieldBuilder< - Header, Header.Builder, HeaderOrBuilder> + private com.google.protobuf.SingleFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Header, com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder, com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder> getHeaderFieldBuilder() { if (headerBuilder_ == null) { - headerBuilder_ = new com.google.protobuf.SingleFieldBuilder< - Header, Header.Builder, HeaderOrBuilder>( + headerBuilder_ = new com.google.protobuf.SingleFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Header, com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder, com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder>( getHeader(), getParentForChildren(), isClean()); @@ -1152,84 +1270,86 @@ public final class CanalEntry { return headerBuilder_; } - private EntryType entryType_ = EntryType.ROWDATA; + private int entryType_ = 2; /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
              **打散后的事件类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ public boolean hasEntryType() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
              **打散后的事件类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ - public EntryType getEntryType() { - return entryType_; + public com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EntryType result = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf(entryType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA : result; } /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
              **打散后的事件类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ - public Builder setEntryType(EntryType value) { + public Builder setEntryType(com.alibaba.otter.canal.protocol.CanalEntry.EntryType value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000002; - entryType_ = value; + entryType_ = value.getNumber(); onChanged(); return this; } /** - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; - * *
              **打散后的事件类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; */ public Builder clearEntryType() { bitField0_ = (bitField0_ & ~0x00000002); - entryType_ = EntryType.ROWDATA; + entryType_ = 2; onChanged(); return this; } private com.google.protobuf.ByteString storeValue_ = com.google.protobuf.ByteString.EMPTY; /** - * optional bytes storeValue = 3; - * *
              **传输的二进制数组*
              * 
      + * + * optional bytes storeValue = 3; */ public boolean hasStoreValue() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional bytes storeValue = 3; - * *
              **传输的二进制数组*
              * 
      + * + * optional bytes storeValue = 3; */ public com.google.protobuf.ByteString getStoreValue() { return storeValue_; } /** - * optional bytes storeValue = 3; - * *
              **传输的二进制数组*
              * 
      + * + * optional bytes storeValue = 3; */ public Builder setStoreValue(com.google.protobuf.ByteString value) { if (value == null) { @@ -1241,11 +1361,11 @@ public final class CanalEntry { return this; } /** - * optional bytes storeValue = 3; - * *
              **传输的二进制数组*
              * 
      + * + * optional bytes storeValue = 3; */ public Builder clearStoreValue() { bitField0_ = (bitField0_ & ~0x00000004); @@ -1253,16 +1373,57 @@ public final class CanalEntry { onChanged(); return this; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Entry) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Entry) + private static final com.alibaba.otter.canal.protocol.CanalEntry.Entry DEFAULT_INSTANCE; static { - defaultInstance = new Entry(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.Entry(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Entry getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Entry parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Entry(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Entry getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Entry) } public interface HeaderOrBuilder extends @@ -1270,335 +1431,343 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * optional int32 version = 1 [default = 1]; - * *
            **协议的版本号*
            * 
      + * + * optional int32 version = 1 [default = 1]; */ boolean hasVersion(); /** - * optional int32 version = 1 [default = 1]; - * *
            **协议的版本号*
            * 
      + * + * optional int32 version = 1 [default = 1]; */ int getVersion(); /** - * optional string logfileName = 2; - * *
            **binlog/redolog 文件名*
            * 
      + * + * optional string logfileName = 2; */ boolean hasLogfileName(); /** - * optional string logfileName = 2; - * *
            **binlog/redolog 文件名*
            * 
      + * + * optional string logfileName = 2; */ - String getLogfileName(); + java.lang.String getLogfileName(); /** - * optional string logfileName = 2; - * *
            **binlog/redolog 文件名*
            * 
      + * + * optional string logfileName = 2; */ com.google.protobuf.ByteString getLogfileNameBytes(); /** - * optional int64 logfileOffset = 3; - * *
            **binlog/redolog 文件的偏移位置*
            * 
      + * + * optional int64 logfileOffset = 3; */ boolean hasLogfileOffset(); /** - * optional int64 logfileOffset = 3; - * *
            **binlog/redolog 文件的偏移位置*
            * 
      + * + * optional int64 logfileOffset = 3; */ long getLogfileOffset(); /** - * optional int64 serverId = 4; - * *
            **服务端serverId*
            * 
      + * + * optional int64 serverId = 4; */ boolean hasServerId(); /** - * optional int64 serverId = 4; - * *
            **服务端serverId*
            * 
      + * + * optional int64 serverId = 4; */ long getServerId(); /** - * optional string serverenCode = 5; - * *
            ** 变更数据的编码 *
            * 
      + * + * optional string serverenCode = 5; */ boolean hasServerenCode(); /** - * optional string serverenCode = 5; - * *
            ** 变更数据的编码 *
            * 
      + * + * optional string serverenCode = 5; */ - String getServerenCode(); + java.lang.String getServerenCode(); /** - * optional string serverenCode = 5; - * *
            ** 变更数据的编码 *
            * 
      + * + * optional string serverenCode = 5; */ com.google.protobuf.ByteString getServerenCodeBytes(); /** - * optional int64 executeTime = 6; - * *
            **变更数据的执行时间 *
            * 
      + * + * optional int64 executeTime = 6; */ boolean hasExecuteTime(); /** - * optional int64 executeTime = 6; - * *
            **变更数据的执行时间 *
            * 
      + * + * optional int64 executeTime = 6; */ long getExecuteTime(); /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
            ** 变更数据的来源*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ boolean hasSourceType(); /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
            ** 变更数据的来源*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ - Type getSourceType(); + com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType(); /** - * optional string schemaName = 8; - * *
            ** 变更数据的schemaname*
            * 
      + * + * optional string schemaName = 8; */ boolean hasSchemaName(); /** - * optional string schemaName = 8; - * *
            ** 变更数据的schemaname*
            * 
      + * + * optional string schemaName = 8; */ - String getSchemaName(); + java.lang.String getSchemaName(); /** - * optional string schemaName = 8; - * *
            ** 变更数据的schemaname*
            * 
      + * + * optional string schemaName = 8; */ com.google.protobuf.ByteString getSchemaNameBytes(); /** - * optional string tableName = 9; - * *
            **变更数据的tablename*
            * 
      + * + * optional string tableName = 9; */ boolean hasTableName(); /** - * optional string tableName = 9; - * *
            **变更数据的tablename*
            * 
      + * + * optional string tableName = 9; */ - String getTableName(); + java.lang.String getTableName(); /** - * optional string tableName = 9; - * *
            **变更数据的tablename*
            * 
      + * + * optional string tableName = 9; */ com.google.protobuf.ByteString getTableNameBytes(); /** - * optional int64 eventLength = 10; - * *
            **每个event的长度*
            * 
      + * + * optional int64 eventLength = 10; */ boolean hasEventLength(); /** - * optional int64 eventLength = 10; - * *
            **每个event的长度*
            * 
      + * + * optional int64 eventLength = 10; */ long getEventLength(); /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ boolean hasEventType(); /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ - EventType getEventType(); + com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - java.util.List + java.util.List getPropsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - Pair getProps(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ int getPropsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - java.util.List + java.util.List getPropsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - PairOrBuilder getPropsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( + int index); /** - * optional string gtid = 13; - * *
            **当前事务的gitd*
            * 
      + * + * optional string gtid = 13; */ boolean hasGtid(); /** - * optional string gtid = 13; - * *
            **当前事务的gitd*
            * 
      + * + * optional string gtid = 13; */ - String getGtid(); + java.lang.String getGtid(); /** - * optional string gtid = 13; - * *
            **当前事务的gitd*
            * 
      + * + * optional string gtid = 13; */ com.google.protobuf.ByteString getGtidBytes(); } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Header} - * *
          **message Header*
          * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Header} */ - public static final class Header extends - com.google.protobuf.GeneratedMessage implements + public static final class Header extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Header) HeaderOrBuilder { + private static final long serialVersionUID = 0L; // Use Header.newBuilder() to construct. - private Header(com.google.protobuf.GeneratedMessage.Builder builder) { + private Header(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private Header(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Header defaultInstance; - public static Header getDefaultInstance() { - return defaultInstance; + private Header() { + version_ = 1; + logfileName_ = ""; + logfileOffset_ = 0L; + serverId_ = 0L; + serverenCode_ = ""; + executeTime_ = 0L; + sourceType_ = 2; + schemaName_ = ""; + tableName_ = ""; + eventLength_ = 0L; + eventType_ = 2; + props_ = java.util.Collections.emptyList(); + gtid_ = ""; } - public Header getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private Header( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -1610,13 +1779,6 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 8: { bitField0_ |= 0x00000001; version_ = input.readInt32(); @@ -1651,12 +1813,13 @@ public final class CanalEntry { } case 56: { int rawValue = input.readEnum(); - Type value = Type.valueOf(rawValue); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.Type value = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf(rawValue); if (value == null) { unknownFields.mergeVarintField(7, rawValue); } else { bitField0_ |= 0x00000040; - sourceType_ = value; + sourceType_ = rawValue; } break; } @@ -1679,21 +1842,23 @@ public final class CanalEntry { } case 88: { int rawValue = input.readEnum(); - EventType value = EventType.valueOf(rawValue); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType value = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(rawValue); if (value == null) { unknownFields.mergeVarintField(11, rawValue); } else { bitField0_ |= 0x00000400; - eventType_ = value; + eventType_ = rawValue; } break; } case 98: { if (!((mutable_bitField0_ & 0x00000800) == 0x00000800)) { - props_ = new java.util.ArrayList(); + props_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000800; } - props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + props_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); break; } case 106: { @@ -1702,13 +1867,20 @@ public final class CanalEntry { gtid_ = bs; break; } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000800) == 0x00000800)) { props_ = java.util.Collections.unmodifiableList(props_); @@ -1719,82 +1891,68 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable .ensureFieldAccessorsInitialized( - Header.class, Builder.class); - } - - public static com.google.protobuf.Parser
      PARSER = - new com.google.protobuf.AbstractParser
      () { - public Header parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Header(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser
      getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.Header.class, com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder.class); } private int bitField0_; public static final int VERSION_FIELD_NUMBER = 1; private int version_; /** - * optional int32 version = 1 [default = 1]; - * *
            **协议的版本号*
            * 
      + * + * optional int32 version = 1 [default = 1]; */ public boolean hasVersion() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int32 version = 1 [default = 1]; - * *
            **协议的版本号*
            * 
      + * + * optional int32 version = 1 [default = 1]; */ public int getVersion() { return version_; } public static final int LOGFILENAME_FIELD_NUMBER = 2; - private Object logfileName_; + private volatile java.lang.Object logfileName_; /** - * optional string logfileName = 2; - * *
            **binlog/redolog 文件名*
            * 
      + * + * optional string logfileName = 2; */ public boolean hasLogfileName() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional string logfileName = 2; - * *
            **binlog/redolog 文件名*
            * 
      + * + * optional string logfileName = 2; */ - public String getLogfileName() { - Object ref = logfileName_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getLogfileName() { + java.lang.Object ref = logfileName_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { logfileName_ = s; } @@ -1802,19 +1960,19 @@ public final class CanalEntry { } } /** - * optional string logfileName = 2; - * *
            **binlog/redolog 文件名*
            * 
      + * + * optional string logfileName = 2; */ public com.google.protobuf.ByteString getLogfileNameBytes() { - Object ref = logfileName_; - if (ref instanceof String) { + java.lang.Object ref = logfileName_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); logfileName_ = b; return b; } else { @@ -1825,21 +1983,21 @@ public final class CanalEntry { public static final int LOGFILEOFFSET_FIELD_NUMBER = 3; private long logfileOffset_; /** - * optional int64 logfileOffset = 3; - * *
            **binlog/redolog 文件的偏移位置*
            * 
      + * + * optional int64 logfileOffset = 3; */ public boolean hasLogfileOffset() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional int64 logfileOffset = 3; - * *
            **binlog/redolog 文件的偏移位置*
            * 
      + * + * optional int64 logfileOffset = 3; */ public long getLogfileOffset() { return logfileOffset_; @@ -1848,53 +2006,53 @@ public final class CanalEntry { public static final int SERVERID_FIELD_NUMBER = 4; private long serverId_; /** - * optional int64 serverId = 4; - * *
            **服务端serverId*
            * 
      + * + * optional int64 serverId = 4; */ public boolean hasServerId() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional int64 serverId = 4; - * *
            **服务端serverId*
            * 
      + * + * optional int64 serverId = 4; */ public long getServerId() { return serverId_; } public static final int SERVERENCODE_FIELD_NUMBER = 5; - private Object serverenCode_; + private volatile java.lang.Object serverenCode_; /** - * optional string serverenCode = 5; - * *
            ** 变更数据的编码 *
            * 
      + * + * optional string serverenCode = 5; */ public boolean hasServerenCode() { return ((bitField0_ & 0x00000010) == 0x00000010); } /** - * optional string serverenCode = 5; - * *
            ** 变更数据的编码 *
            * 
      + * + * optional string serverenCode = 5; */ - public String getServerenCode() { - Object ref = serverenCode_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getServerenCode() { + java.lang.Object ref = serverenCode_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { serverenCode_ = s; } @@ -1902,19 +2060,19 @@ public final class CanalEntry { } } /** - * optional string serverenCode = 5; - * *
            ** 变更数据的编码 *
            * 
      + * + * optional string serverenCode = 5; */ public com.google.protobuf.ByteString getServerenCodeBytes() { - Object ref = serverenCode_; - if (ref instanceof String) { + java.lang.Object ref = serverenCode_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); serverenCode_ = b; return b; } else { @@ -1925,76 +2083,78 @@ public final class CanalEntry { public static final int EXECUTETIME_FIELD_NUMBER = 6; private long executeTime_; /** - * optional int64 executeTime = 6; - * *
            **变更数据的执行时间 *
            * 
      + * + * optional int64 executeTime = 6; */ public boolean hasExecuteTime() { return ((bitField0_ & 0x00000020) == 0x00000020); } /** - * optional int64 executeTime = 6; - * *
            **变更数据的执行时间 *
            * 
      + * + * optional int64 executeTime = 6; */ public long getExecuteTime() { return executeTime_; } public static final int SOURCETYPE_FIELD_NUMBER = 7; - private Type sourceType_; + private int sourceType_; /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
            ** 变更数据的来源*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ public boolean hasSourceType() { return ((bitField0_ & 0x00000040) == 0x00000040); } /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
            ** 变更数据的来源*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ - public Type getSourceType() { - return sourceType_; + public com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.Type result = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf(sourceType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL : result; } public static final int SCHEMANAME_FIELD_NUMBER = 8; - private Object schemaName_; + private volatile java.lang.Object schemaName_; /** - * optional string schemaName = 8; - * *
            ** 变更数据的schemaname*
            * 
      + * + * optional string schemaName = 8; */ public boolean hasSchemaName() { return ((bitField0_ & 0x00000080) == 0x00000080); } /** - * optional string schemaName = 8; - * *
            ** 变更数据的schemaname*
            * 
      + * + * optional string schemaName = 8; */ - public String getSchemaName() { - Object ref = schemaName_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getSchemaName() { + java.lang.Object ref = schemaName_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { schemaName_ = s; } @@ -2002,19 +2162,19 @@ public final class CanalEntry { } } /** - * optional string schemaName = 8; - * *
            ** 变更数据的schemaname*
            * 
      + * + * optional string schemaName = 8; */ public com.google.protobuf.ByteString getSchemaNameBytes() { - Object ref = schemaName_; - if (ref instanceof String) { + java.lang.Object ref = schemaName_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); schemaName_ = b; return b; } else { @@ -2023,32 +2183,32 @@ public final class CanalEntry { } public static final int TABLENAME_FIELD_NUMBER = 9; - private Object tableName_; + private volatile java.lang.Object tableName_; /** - * optional string tableName = 9; - * *
            **变更数据的tablename*
            * 
      + * + * optional string tableName = 9; */ public boolean hasTableName() { return ((bitField0_ & 0x00000100) == 0x00000100); } /** - * optional string tableName = 9; - * *
            **变更数据的tablename*
            * 
      + * + * optional string tableName = 9; */ - public String getTableName() { - Object ref = tableName_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getTableName() { + java.lang.Object ref = tableName_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { tableName_ = s; } @@ -2056,19 +2216,19 @@ public final class CanalEntry { } } /** - * optional string tableName = 9; - * *
            **变更数据的tablename*
            * 
      + * + * optional string tableName = 9; */ public com.google.protobuf.ByteString getTableNameBytes() { - Object ref = tableName_; - if (ref instanceof String) { + java.lang.Object ref = tableName_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); tableName_ = b; return b; } else { @@ -2079,131 +2239,133 @@ public final class CanalEntry { public static final int EVENTLENGTH_FIELD_NUMBER = 10; private long eventLength_; /** - * optional int64 eventLength = 10; - * *
            **每个event的长度*
            * 
      + * + * optional int64 eventLength = 10; */ public boolean hasEventLength() { return ((bitField0_ & 0x00000200) == 0x00000200); } /** - * optional int64 eventLength = 10; - * *
            **每个event的长度*
            * 
      + * + * optional int64 eventLength = 10; */ public long getEventLength() { return eventLength_; } public static final int EVENTTYPE_FIELD_NUMBER = 11; - private EventType eventType_; + private int eventType_; /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ public boolean hasEventType() { return ((bitField0_ & 0x00000400) == 0x00000400); } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ - public EventType getEventType() { - return eventType_; + public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; } public static final int PROPS_FIELD_NUMBER = 12; - private java.util.List props_; + private java.util.List props_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public int getPropsCount() { return props_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { return props_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { return props_.get(index); } public static final int GTID_FIELD_NUMBER = 13; - private Object gtid_; + private volatile java.lang.Object gtid_; /** - * optional string gtid = 13; - * *
            **当前事务的gitd*
            * 
      + * + * optional string gtid = 13; */ public boolean hasGtid() { return ((bitField0_ & 0x00000800) == 0x00000800); } /** - * optional string gtid = 13; - * *
            **当前事务的gitd*
            * 
      + * + * optional string gtid = 13; */ - public String getGtid() { - Object ref = gtid_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getGtid() { + java.lang.Object ref = gtid_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { gtid_ = s; } @@ -2211,19 +2373,19 @@ public final class CanalEntry { } } /** - * optional string gtid = 13; - * *
            **当前事务的gitd*
            * 
      + * + * optional string gtid = 13; */ public com.google.protobuf.ByteString getGtidBytes() { - Object ref = gtid_; - if (ref instanceof String) { + java.lang.Object ref = gtid_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); gtid_ = b; return b; } else { @@ -2231,22 +2393,8 @@ public final class CanalEntry { } } - private void initFields() { - version_ = 1; - logfileName_ = ""; - logfileOffset_ = 0L; - serverId_ = 0L; - serverenCode_ = ""; - executeTime_ = 0L; - sourceType_ = Type.MYSQL; - schemaName_ = ""; - tableName_ = ""; - eventLength_ = 0L; - eventType_ = EventType.UPDATE; - props_ = java.util.Collections.emptyList(); - gtid_ = ""; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -2256,14 +2404,14 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeInt32(1, version_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getLogfileNameBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, logfileName_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeInt64(3, logfileOffset_); @@ -2272,38 +2420,38 @@ public final class CanalEntry { output.writeInt64(4, serverId_); } if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBytes(5, getServerenCodeBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 5, serverenCode_); } if (((bitField0_ & 0x00000020) == 0x00000020)) { output.writeInt64(6, executeTime_); } if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeEnum(7, sourceType_.getNumber()); + output.writeEnum(7, sourceType_); } if (((bitField0_ & 0x00000080) == 0x00000080)) { - output.writeBytes(8, getSchemaNameBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 8, schemaName_); } if (((bitField0_ & 0x00000100) == 0x00000100)) { - output.writeBytes(9, getTableNameBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 9, tableName_); } if (((bitField0_ & 0x00000200) == 0x00000200)) { output.writeInt64(10, eventLength_); } if (((bitField0_ & 0x00000400) == 0x00000400)) { - output.writeEnum(11, eventType_.getNumber()); + output.writeEnum(11, eventType_); } for (int i = 0; i < props_.size(); i++) { output.writeMessage(12, props_.get(i)); } if (((bitField0_ & 0x00000800) == 0x00000800)) { - output.writeBytes(13, getGtidBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 13, gtid_); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; @@ -2312,8 +2460,7 @@ public final class CanalEntry { .computeInt32Size(1, version_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getLogfileNameBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, logfileName_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream @@ -2324,8 +2471,7 @@ public final class CanalEntry { .computeInt64Size(4, serverId_); } if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(5, getServerenCodeBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, serverenCode_); } if (((bitField0_ & 0x00000020) == 0x00000020)) { size += com.google.protobuf.CodedOutputStream @@ -2333,15 +2479,13 @@ public final class CanalEntry { } if (((bitField0_ & 0x00000040) == 0x00000040)) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(7, sourceType_.getNumber()); + .computeEnumSize(7, sourceType_); } if (((bitField0_ & 0x00000080) == 0x00000080)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(8, getSchemaNameBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, schemaName_); } if (((bitField0_ & 0x00000100) == 0x00000100)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(9, getTableNameBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(9, tableName_); } if (((bitField0_ & 0x00000200) == 0x00000200)) { size += com.google.protobuf.CodedOutputStream @@ -2349,115 +2493,275 @@ public final class CanalEntry { } if (((bitField0_ & 0x00000400) == 0x00000400)) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(11, eventType_.getNumber()); + .computeEnumSize(11, eventType_); } for (int i = 0; i < props_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(12, props_.get(i)); } if (((bitField0_ & 0x00000800) == 0x00000800)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(13, getGtidBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(13, gtid_); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.Header)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.Header other = (com.alibaba.otter.canal.protocol.CanalEntry.Header) obj; + + boolean result = true; + result = result && (hasVersion() == other.hasVersion()); + if (hasVersion()) { + result = result && (getVersion() + == other.getVersion()); + } + result = result && (hasLogfileName() == other.hasLogfileName()); + if (hasLogfileName()) { + result = result && getLogfileName() + .equals(other.getLogfileName()); + } + result = result && (hasLogfileOffset() == other.hasLogfileOffset()); + if (hasLogfileOffset()) { + result = result && (getLogfileOffset() + == other.getLogfileOffset()); + } + result = result && (hasServerId() == other.hasServerId()); + if (hasServerId()) { + result = result && (getServerId() + == other.getServerId()); + } + result = result && (hasServerenCode() == other.hasServerenCode()); + if (hasServerenCode()) { + result = result && getServerenCode() + .equals(other.getServerenCode()); + } + result = result && (hasExecuteTime() == other.hasExecuteTime()); + if (hasExecuteTime()) { + result = result && (getExecuteTime() + == other.getExecuteTime()); + } + result = result && (hasSourceType() == other.hasSourceType()); + if (hasSourceType()) { + result = result && sourceType_ == other.sourceType_; + } + result = result && (hasSchemaName() == other.hasSchemaName()); + if (hasSchemaName()) { + result = result && getSchemaName() + .equals(other.getSchemaName()); + } + result = result && (hasTableName() == other.hasTableName()); + if (hasTableName()) { + result = result && getTableName() + .equals(other.getTableName()); + } + result = result && (hasEventLength() == other.hasEventLength()); + if (hasEventLength()) { + result = result && (getEventLength() + == other.getEventLength()); + } + result = result && (hasEventType() == other.hasEventType()); + if (hasEventType()) { + result = result && eventType_ == other.eventType_; + } + result = result && getPropsList() + .equals(other.getPropsList()); + result = result && (hasGtid() == other.hasGtid()); + if (hasGtid()) { + result = result && getGtid() + .equals(other.getGtid()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static Header parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasVersion()) { + hash = (37 * hash) + VERSION_FIELD_NUMBER; + hash = (53 * hash) + getVersion(); + } + if (hasLogfileName()) { + hash = (37 * hash) + LOGFILENAME_FIELD_NUMBER; + hash = (53 * hash) + getLogfileName().hashCode(); + } + if (hasLogfileOffset()) { + hash = (37 * hash) + LOGFILEOFFSET_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getLogfileOffset()); + } + if (hasServerId()) { + hash = (37 * hash) + SERVERID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getServerId()); + } + if (hasServerenCode()) { + hash = (37 * hash) + SERVERENCODE_FIELD_NUMBER; + hash = (53 * hash) + getServerenCode().hashCode(); + } + if (hasExecuteTime()) { + hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getExecuteTime()); + } + if (hasSourceType()) { + hash = (37 * hash) + SOURCETYPE_FIELD_NUMBER; + hash = (53 * hash) + sourceType_; + } + if (hasSchemaName()) { + hash = (37 * hash) + SCHEMANAME_FIELD_NUMBER; + hash = (53 * hash) + getSchemaName().hashCode(); + } + if (hasTableName()) { + hash = (37 * hash) + TABLENAME_FIELD_NUMBER; + hash = (53 * hash) + getTableName().hashCode(); + } + if (hasEventLength()) { + hash = (37 * hash) + EVENTLENGTH_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getEventLength()); + } + if (hasEventType()) { + hash = (37 * hash) + EVENTTYPE_FIELD_NUMBER; + hash = (53 * hash) + eventType_; + } + if (getPropsCount() > 0) { + hash = (37 * hash) + PROPS_FIELD_NUMBER; + hash = (53 * hash) + getPropsList().hashCode(); + } + if (hasGtid()) { + hash = (37 * hash) + GTID_FIELD_NUMBER; + hash = (53 * hash) + getGtid().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Header parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Header parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Header parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Header parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Header parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Header parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static Header parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static Header parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Header parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Header parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(Header prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Header prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Header} - * *
            **message Header*
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Header} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Header) - HeaderOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable .ensureFieldAccessorsInitialized( - Header.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.Header.class, com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Header.newBuilder() @@ -2466,19 +2770,17 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { getPropsFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); version_ = 1; @@ -2493,7 +2795,7 @@ public final class CanalEntry { bitField0_ = (bitField0_ & ~0x00000010); executeTime_ = 0L; bitField0_ = (bitField0_ & ~0x00000020); - sourceType_ = Type.MYSQL; + sourceType_ = 2; bitField0_ = (bitField0_ & ~0x00000040); schemaName_ = ""; bitField0_ = (bitField0_ & ~0x00000080); @@ -2501,7 +2803,7 @@ public final class CanalEntry { bitField0_ = (bitField0_ & ~0x00000100); eventLength_ = 0L; bitField0_ = (bitField0_ & ~0x00000200); - eventType_ = EventType.UPDATE; + eventType_ = 2; bitField0_ = (bitField0_ & ~0x00000400); if (propsBuilder_ == null) { props_ = java.util.Collections.emptyList(); @@ -2514,29 +2816,29 @@ public final class CanalEntry { return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; } - public Header getDefaultInstanceForType() { - return Header.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Header getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance(); } - public Header build() { - Header result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Header build() { + com.alibaba.otter.canal.protocol.CanalEntry.Header result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public Header buildPartial() { - Header result = new Header(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Header buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.Header result = new com.alibaba.otter.canal.protocol.CanalEntry.Header(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -2601,17 +2903,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Header) { - return mergeFrom((Header)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Header) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Header)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(Header other) { - if (other == Header.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Header other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance()) return this; if (other.hasVersion()) { setVersion(other.getVersion()); } @@ -2672,7 +3007,7 @@ public final class CanalEntry { props_ = other.props_; bitField0_ = (bitField0_ & ~0x00000800); propsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; } else { propsBuilder_.addAllMessages(other.props_); @@ -2684,24 +3019,27 @@ public final class CanalEntry { gtid_ = other.gtid_; onChanged(); } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - Header parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.Header parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Header) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Header) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -2713,31 +3051,31 @@ public final class CanalEntry { private int version_ = 1; /** - * optional int32 version = 1 [default = 1]; - * *
              **协议的版本号*
              * 
      + * + * optional int32 version = 1 [default = 1]; */ public boolean hasVersion() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int32 version = 1 [default = 1]; - * *
              **协议的版本号*
              * 
      + * + * optional int32 version = 1 [default = 1]; */ public int getVersion() { return version_; } /** - * optional int32 version = 1 [default = 1]; - * *
              **协议的版本号*
              * 
      + * + * optional int32 version = 1 [default = 1]; */ public Builder setVersion(int value) { bitField0_ |= 0x00000001; @@ -2746,11 +3084,11 @@ public final class CanalEntry { return this; } /** - * optional int32 version = 1 [default = 1]; - * *
              **协议的版本号*
              * 
      + * + * optional int32 version = 1 [default = 1]; */ public Builder clearVersion() { bitField0_ = (bitField0_ & ~0x00000001); @@ -2759,52 +3097,52 @@ public final class CanalEntry { return this; } - private Object logfileName_ = ""; + private java.lang.Object logfileName_ = ""; /** - * optional string logfileName = 2; - * *
              **binlog/redolog 文件名*
              * 
      + * + * optional string logfileName = 2; */ public boolean hasLogfileName() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional string logfileName = 2; - * *
              **binlog/redolog 文件名*
              * 
      + * + * optional string logfileName = 2; */ - public String getLogfileName() { - Object ref = logfileName_; - if (!(ref instanceof String)) { + public java.lang.String getLogfileName() { + java.lang.Object ref = logfileName_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { logfileName_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string logfileName = 2; - * *
              **binlog/redolog 文件名*
              * 
      + * + * optional string logfileName = 2; */ public com.google.protobuf.ByteString getLogfileNameBytes() { - Object ref = logfileName_; + java.lang.Object ref = logfileName_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); logfileName_ = b; return b; } else { @@ -2812,14 +3150,14 @@ public final class CanalEntry { } } /** - * optional string logfileName = 2; - * *
              **binlog/redolog 文件名*
              * 
      + * + * optional string logfileName = 2; */ public Builder setLogfileName( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -2829,11 +3167,11 @@ public final class CanalEntry { return this; } /** - * optional string logfileName = 2; - * *
              **binlog/redolog 文件名*
              * 
      + * + * optional string logfileName = 2; */ public Builder clearLogfileName() { bitField0_ = (bitField0_ & ~0x00000002); @@ -2842,11 +3180,11 @@ public final class CanalEntry { return this; } /** - * optional string logfileName = 2; - * *
              **binlog/redolog 文件名*
              * 
      + * + * optional string logfileName = 2; */ public Builder setLogfileNameBytes( com.google.protobuf.ByteString value) { @@ -2861,31 +3199,31 @@ public final class CanalEntry { private long logfileOffset_ ; /** - * optional int64 logfileOffset = 3; - * *
              **binlog/redolog 文件的偏移位置*
              * 
      + * + * optional int64 logfileOffset = 3; */ public boolean hasLogfileOffset() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional int64 logfileOffset = 3; - * *
              **binlog/redolog 文件的偏移位置*
              * 
      + * + * optional int64 logfileOffset = 3; */ public long getLogfileOffset() { return logfileOffset_; } /** - * optional int64 logfileOffset = 3; - * *
              **binlog/redolog 文件的偏移位置*
              * 
      + * + * optional int64 logfileOffset = 3; */ public Builder setLogfileOffset(long value) { bitField0_ |= 0x00000004; @@ -2894,11 +3232,11 @@ public final class CanalEntry { return this; } /** - * optional int64 logfileOffset = 3; - * *
              **binlog/redolog 文件的偏移位置*
              * 
      + * + * optional int64 logfileOffset = 3; */ public Builder clearLogfileOffset() { bitField0_ = (bitField0_ & ~0x00000004); @@ -2909,31 +3247,31 @@ public final class CanalEntry { private long serverId_ ; /** - * optional int64 serverId = 4; - * *
              **服务端serverId*
              * 
      + * + * optional int64 serverId = 4; */ public boolean hasServerId() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional int64 serverId = 4; - * *
              **服务端serverId*
              * 
      + * + * optional int64 serverId = 4; */ public long getServerId() { return serverId_; } /** - * optional int64 serverId = 4; - * *
              **服务端serverId*
              * 
      + * + * optional int64 serverId = 4; */ public Builder setServerId(long value) { bitField0_ |= 0x00000008; @@ -2942,11 +3280,11 @@ public final class CanalEntry { return this; } /** - * optional int64 serverId = 4; - * *
              **服务端serverId*
              * 
      + * + * optional int64 serverId = 4; */ public Builder clearServerId() { bitField0_ = (bitField0_ & ~0x00000008); @@ -2955,52 +3293,52 @@ public final class CanalEntry { return this; } - private Object serverenCode_ = ""; + private java.lang.Object serverenCode_ = ""; /** - * optional string serverenCode = 5; - * *
              ** 变更数据的编码 *
              * 
      + * + * optional string serverenCode = 5; */ public boolean hasServerenCode() { return ((bitField0_ & 0x00000010) == 0x00000010); } /** - * optional string serverenCode = 5; - * *
              ** 变更数据的编码 *
              * 
      + * + * optional string serverenCode = 5; */ - public String getServerenCode() { - Object ref = serverenCode_; - if (!(ref instanceof String)) { + public java.lang.String getServerenCode() { + java.lang.Object ref = serverenCode_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { serverenCode_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string serverenCode = 5; - * *
              ** 变更数据的编码 *
              * 
      + * + * optional string serverenCode = 5; */ public com.google.protobuf.ByteString getServerenCodeBytes() { - Object ref = serverenCode_; + java.lang.Object ref = serverenCode_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); serverenCode_ = b; return b; } else { @@ -3008,14 +3346,14 @@ public final class CanalEntry { } } /** - * optional string serverenCode = 5; - * *
              ** 变更数据的编码 *
              * 
      + * + * optional string serverenCode = 5; */ public Builder setServerenCode( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -3025,11 +3363,11 @@ public final class CanalEntry { return this; } /** - * optional string serverenCode = 5; - * *
              ** 变更数据的编码 *
              * 
      + * + * optional string serverenCode = 5; */ public Builder clearServerenCode() { bitField0_ = (bitField0_ & ~0x00000010); @@ -3038,11 +3376,11 @@ public final class CanalEntry { return this; } /** - * optional string serverenCode = 5; - * *
              ** 变更数据的编码 *
              * 
      + * + * optional string serverenCode = 5; */ public Builder setServerenCodeBytes( com.google.protobuf.ByteString value) { @@ -3057,31 +3395,31 @@ public final class CanalEntry { private long executeTime_ ; /** - * optional int64 executeTime = 6; - * *
              **变更数据的执行时间 *
              * 
      + * + * optional int64 executeTime = 6; */ public boolean hasExecuteTime() { return ((bitField0_ & 0x00000020) == 0x00000020); } /** - * optional int64 executeTime = 6; - * *
              **变更数据的执行时间 *
              * 
      + * + * optional int64 executeTime = 6; */ public long getExecuteTime() { return executeTime_; } /** - * optional int64 executeTime = 6; - * *
              **变更数据的执行时间 *
              * 
      + * + * optional int64 executeTime = 6; */ public Builder setExecuteTime(long value) { bitField0_ |= 0x00000020; @@ -3090,11 +3428,11 @@ public final class CanalEntry { return this; } /** - * optional int64 executeTime = 6; - * *
              **变更数据的执行时间 *
              * 
      + * + * optional int64 executeTime = 6; */ public Builder clearExecuteTime() { bitField0_ = (bitField0_ & ~0x00000020); @@ -3103,103 +3441,105 @@ public final class CanalEntry { return this; } - private Type sourceType_ = Type.MYSQL; + private int sourceType_ = 2; /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
              ** 变更数据的来源*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ public boolean hasSourceType() { return ((bitField0_ & 0x00000040) == 0x00000040); } /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
              ** 变更数据的来源*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ - public Type getSourceType() { - return sourceType_; + public com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.Type result = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf(sourceType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL : result; } /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
              ** 变更数据的来源*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ - public Builder setSourceType(Type value) { + public Builder setSourceType(com.alibaba.otter.canal.protocol.CanalEntry.Type value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000040; - sourceType_ = value; + sourceType_ = value.getNumber(); onChanged(); return this; } /** - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; - * *
              ** 变更数据的来源*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; */ public Builder clearSourceType() { bitField0_ = (bitField0_ & ~0x00000040); - sourceType_ = Type.MYSQL; + sourceType_ = 2; onChanged(); return this; } - private Object schemaName_ = ""; + private java.lang.Object schemaName_ = ""; /** - * optional string schemaName = 8; - * *
              ** 变更数据的schemaname*
              * 
      + * + * optional string schemaName = 8; */ public boolean hasSchemaName() { return ((bitField0_ & 0x00000080) == 0x00000080); } /** - * optional string schemaName = 8; - * *
              ** 变更数据的schemaname*
              * 
      + * + * optional string schemaName = 8; */ - public String getSchemaName() { - Object ref = schemaName_; - if (!(ref instanceof String)) { + public java.lang.String getSchemaName() { + java.lang.Object ref = schemaName_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { schemaName_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string schemaName = 8; - * *
              ** 变更数据的schemaname*
              * 
      + * + * optional string schemaName = 8; */ public com.google.protobuf.ByteString getSchemaNameBytes() { - Object ref = schemaName_; + java.lang.Object ref = schemaName_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); schemaName_ = b; return b; } else { @@ -3207,14 +3547,14 @@ public final class CanalEntry { } } /** - * optional string schemaName = 8; - * *
              ** 变更数据的schemaname*
              * 
      + * + * optional string schemaName = 8; */ public Builder setSchemaName( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -3224,11 +3564,11 @@ public final class CanalEntry { return this; } /** - * optional string schemaName = 8; - * *
              ** 变更数据的schemaname*
              * 
      + * + * optional string schemaName = 8; */ public Builder clearSchemaName() { bitField0_ = (bitField0_ & ~0x00000080); @@ -3237,11 +3577,11 @@ public final class CanalEntry { return this; } /** - * optional string schemaName = 8; - * *
              ** 变更数据的schemaname*
              * 
      + * + * optional string schemaName = 8; */ public Builder setSchemaNameBytes( com.google.protobuf.ByteString value) { @@ -3254,52 +3594,52 @@ public final class CanalEntry { return this; } - private Object tableName_ = ""; + private java.lang.Object tableName_ = ""; /** - * optional string tableName = 9; - * *
              **变更数据的tablename*
              * 
      + * + * optional string tableName = 9; */ public boolean hasTableName() { return ((bitField0_ & 0x00000100) == 0x00000100); } /** - * optional string tableName = 9; - * *
              **变更数据的tablename*
              * 
      + * + * optional string tableName = 9; */ - public String getTableName() { - Object ref = tableName_; - if (!(ref instanceof String)) { + public java.lang.String getTableName() { + java.lang.Object ref = tableName_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { tableName_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string tableName = 9; - * *
              **变更数据的tablename*
              * 
      + * + * optional string tableName = 9; */ public com.google.protobuf.ByteString getTableNameBytes() { - Object ref = tableName_; + java.lang.Object ref = tableName_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); tableName_ = b; return b; } else { @@ -3307,14 +3647,14 @@ public final class CanalEntry { } } /** - * optional string tableName = 9; - * *
              **变更数据的tablename*
              * 
      + * + * optional string tableName = 9; */ public Builder setTableName( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -3324,11 +3664,11 @@ public final class CanalEntry { return this; } /** - * optional string tableName = 9; - * *
              **变更数据的tablename*
              * 
      + * + * optional string tableName = 9; */ public Builder clearTableName() { bitField0_ = (bitField0_ & ~0x00000100); @@ -3337,11 +3677,11 @@ public final class CanalEntry { return this; } /** - * optional string tableName = 9; - * *
              **变更数据的tablename*
              * 
      + * + * optional string tableName = 9; */ public Builder setTableNameBytes( com.google.protobuf.ByteString value) { @@ -3356,31 +3696,31 @@ public final class CanalEntry { private long eventLength_ ; /** - * optional int64 eventLength = 10; - * *
              **每个event的长度*
              * 
      + * + * optional int64 eventLength = 10; */ public boolean hasEventLength() { return ((bitField0_ & 0x00000200) == 0x00000200); } /** - * optional int64 eventLength = 10; - * *
              **每个event的长度*
              * 
      + * + * optional int64 eventLength = 10; */ public long getEventLength() { return eventLength_; } /** - * optional int64 eventLength = 10; - * *
              **每个event的长度*
              * 
      + * + * optional int64 eventLength = 10; */ public Builder setEventLength(long value) { bitField0_ |= 0x00000200; @@ -3389,11 +3729,11 @@ public final class CanalEntry { return this; } /** - * optional int64 eventLength = 10; - * *
              **每个event的长度*
              * 
      + * + * optional int64 eventLength = 10; */ public Builder clearEventLength() { bitField0_ = (bitField0_ & ~0x00000200); @@ -3402,77 +3742,79 @@ public final class CanalEntry { return this; } - private EventType eventType_ = EventType.UPDATE; + private int eventType_ = 2; /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ public boolean hasEventType() { return ((bitField0_ & 0x00000400) == 0x00000400); } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ - public EventType getEventType() { - return eventType_; + public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ - public Builder setEventType(EventType value) { + public Builder setEventType(com.alibaba.otter.canal.protocol.CanalEntry.EventType value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000400; - eventType_ = value; + eventType_ = value.getNumber(); onChanged(); return this; } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; */ public Builder clearEventType() { bitField0_ = (bitField0_ & ~0x00000400); - eventType_ = EventType.UPDATE; + eventType_ = 2; onChanged(); return this; } - private java.util.List props_ = + private java.util.List props_ = java.util.Collections.emptyList(); private void ensurePropsIsMutable() { if (!((bitField0_ & 0x00000800) == 0x00000800)) { - props_ = new java.util.ArrayList(props_); + props_ = new java.util.ArrayList(props_); bitField0_ |= 0x00000800; } } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> propsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { if (propsBuilder_ == null) { return java.util.Collections.unmodifiableList(props_); } else { @@ -3480,11 +3822,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public int getPropsCount() { if (propsBuilder_ == null) { @@ -3494,13 +3836,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -3508,14 +3850,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder setProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -3529,14 +3871,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder setProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.set(index, builderForValue.build()); @@ -3547,13 +3889,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public Builder addProps(Pair value) { + public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -3567,14 +3909,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder addProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -3588,14 +3930,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder addProps( - Pair.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(builderForValue.build()); @@ -3606,14 +3948,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder addProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(index, builderForValue.build()); @@ -3624,14 +3966,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder addAllProps( - Iterable values) { + java.lang.Iterable values) { if (propsBuilder_ == null) { ensurePropsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -3643,11 +3985,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder clearProps() { if (propsBuilder_ == null) { @@ -3660,11 +4002,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ public Builder removeProps(int index) { if (propsBuilder_ == null) { @@ -3677,24 +4019,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public Pair.Builder getPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder( int index) { return getPropsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -3702,13 +4044,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { if (propsBuilder_ != null) { return propsBuilder_.getMessageOrBuilderList(); @@ -3717,45 +4059,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public Pair.Builder addPropsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { return getPropsFieldBuilder().addBuilder( - Pair.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public Pair.Builder addPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder( int index) { return getPropsFieldBuilder().addBuilder( - index, Pair.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 12; */ - public java.util.List + public java.util.List getPropsBuilderList() { return getPropsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> getPropsFieldBuilder() { if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder>( + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder>( props_, ((bitField0_ & 0x00000800) == 0x00000800), getParentForChildren(), @@ -3765,52 +4107,52 @@ public final class CanalEntry { return propsBuilder_; } - private Object gtid_ = ""; + private java.lang.Object gtid_ = ""; /** - * optional string gtid = 13; - * *
              **当前事务的gitd*
              * 
      + * + * optional string gtid = 13; */ public boolean hasGtid() { return ((bitField0_ & 0x00001000) == 0x00001000); } /** - * optional string gtid = 13; - * *
              **当前事务的gitd*
              * 
      + * + * optional string gtid = 13; */ - public String getGtid() { - Object ref = gtid_; - if (!(ref instanceof String)) { + public java.lang.String getGtid() { + java.lang.Object ref = gtid_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { gtid_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string gtid = 13; - * *
              **当前事务的gitd*
              * 
      + * + * optional string gtid = 13; */ public com.google.protobuf.ByteString getGtidBytes() { - Object ref = gtid_; + java.lang.Object ref = gtid_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); gtid_ = b; return b; } else { @@ -3818,14 +4160,14 @@ public final class CanalEntry { } } /** - * optional string gtid = 13; - * *
              **当前事务的gitd*
              * 
      + * + * optional string gtid = 13; */ public Builder setGtid( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -3835,11 +4177,11 @@ public final class CanalEntry { return this; } /** - * optional string gtid = 13; - * *
              **当前事务的gitd*
              * 
      + * + * optional string gtid = 13; */ public Builder clearGtid() { bitField0_ = (bitField0_ & ~0x00001000); @@ -3848,11 +4190,11 @@ public final class CanalEntry { return this; } /** - * optional string gtid = 13; - * *
              **当前事务的gitd*
              * 
      + * + * optional string gtid = 13; */ public Builder setGtidBytes( com.google.protobuf.ByteString value) { @@ -3864,16 +4206,57 @@ public final class CanalEntry { onChanged(); return this; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Header) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Header) + private static final com.alibaba.otter.canal.protocol.CanalEntry.Header DEFAULT_INSTANCE; static { - defaultInstance = new Header(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.Header(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Header getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser
      + PARSER = new com.google.protobuf.AbstractParser
      () { + @java.lang.Override + public Header parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Header(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser
      parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser
      getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Header getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Header) } public interface ColumnOrBuilder extends @@ -3881,266 +4264,271 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * optional int32 index = 1; - * *
            **字段下标*
            * 
      + * + * optional int32 index = 1; */ boolean hasIndex(); /** - * optional int32 index = 1; - * *
            **字段下标*
            * 
      + * + * optional int32 index = 1; */ int getIndex(); /** - * optional int32 sqlType = 2; - * *
            **字段java中类型*
            * 
      + * + * optional int32 sqlType = 2; */ boolean hasSqlType(); /** - * optional int32 sqlType = 2; - * *
            **字段java中类型*
            * 
      + * + * optional int32 sqlType = 2; */ int getSqlType(); /** - * optional string name = 3; - * *
            **字段名称(忽略大小写),在mysql中是没有的*
            * 
      + * + * optional string name = 3; */ boolean hasName(); /** - * optional string name = 3; - * *
            **字段名称(忽略大小写),在mysql中是没有的*
            * 
      + * + * optional string name = 3; */ - String getName(); + java.lang.String getName(); /** - * optional string name = 3; - * *
            **字段名称(忽略大小写),在mysql中是没有的*
            * 
      + * + * optional string name = 3; */ com.google.protobuf.ByteString getNameBytes(); /** - * optional bool isKey = 4; - * *
            **是否是主键*
            * 
      + * + * optional bool isKey = 4; */ boolean hasIsKey(); /** - * optional bool isKey = 4; - * *
            **是否是主键*
            * 
      + * + * optional bool isKey = 4; */ boolean getIsKey(); /** - * optional bool updated = 5; - * *
            **如果EventType=UPDATE,用于标识这个字段值是否有修改*
            * 
      + * + * optional bool updated = 5; */ boolean hasUpdated(); /** - * optional bool updated = 5; - * *
            **如果EventType=UPDATE,用于标识这个字段值是否有修改*
            * 
      + * + * optional bool updated = 5; */ boolean getUpdated(); /** - * optional bool isNull = 6 [default = false]; - * *
            ** 标识是否为空  *
            * 
      + * + * optional bool isNull = 6 [default = false]; */ boolean hasIsNull(); /** - * optional bool isNull = 6 [default = false]; - * *
            ** 标识是否为空  *
            * 
      + * + * optional bool isNull = 6 [default = false]; */ boolean getIsNull(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - java.util.List + java.util.List getPropsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - Pair getProps(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ int getPropsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - java.util.List + java.util.List getPropsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - PairOrBuilder getPropsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( + int index); /** - * optional string value = 8; - * *
            ** 字段值,timestamp,Datetime是一个时间格式的文本 *
            * 
      + * + * optional string value = 8; */ boolean hasValue(); /** - * optional string value = 8; - * *
            ** 字段值,timestamp,Datetime是一个时间格式的文本 *
            * 
      + * + * optional string value = 8; */ - String getValue(); + java.lang.String getValue(); /** - * optional string value = 8; - * *
            ** 字段值,timestamp,Datetime是一个时间格式的文本 *
            * 
      + * + * optional string value = 8; */ com.google.protobuf.ByteString getValueBytes(); /** - * optional int32 length = 9; - * *
            ** 对应数据对象原始长度 *
            * 
      + * + * optional int32 length = 9; */ boolean hasLength(); /** - * optional int32 length = 9; - * *
            ** 对应数据对象原始长度 *
            * 
      + * + * optional int32 length = 9; */ int getLength(); /** - * optional string mysqlType = 10; - * *
            **字段mysql类型*
            * 
      + * + * optional string mysqlType = 10; */ boolean hasMysqlType(); /** - * optional string mysqlType = 10; - * *
            **字段mysql类型*
            * 
      + * + * optional string mysqlType = 10; */ - String getMysqlType(); + java.lang.String getMysqlType(); /** - * optional string mysqlType = 10; - * *
            **字段mysql类型*
            * 
      + * + * optional string mysqlType = 10; */ com.google.protobuf.ByteString getMysqlTypeBytes(); } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Column} - * *
          **每个字段的数据结构*
          * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Column} */ - public static final class Column extends - com.google.protobuf.GeneratedMessage implements + public static final class Column extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Column) ColumnOrBuilder { + private static final long serialVersionUID = 0L; // Use Column.newBuilder() to construct. - private Column(com.google.protobuf.GeneratedMessage.Builder builder) { + private Column(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private Column(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Column defaultInstance; - public static Column getDefaultInstance() { - return defaultInstance; + private Column() { + index_ = 0; + sqlType_ = 0; + name_ = ""; + isKey_ = false; + updated_ = false; + isNull_ = false; + props_ = java.util.Collections.emptyList(); + value_ = ""; + length_ = 0; + mysqlType_ = ""; } - public Column getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private Column( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -4152,13 +4540,6 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 8: { bitField0_ |= 0x00000001; index_ = input.readInt32(); @@ -4192,10 +4573,11 @@ public final class CanalEntry { } case 58: { if (!((mutable_bitField0_ & 0x00000040) == 0x00000040)) { - props_ = new java.util.ArrayList(); + props_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000040; } - props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + props_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); break; } case 66: { @@ -4215,13 +4597,20 @@ public final class CanalEntry { mysqlType_ = bs; break; } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000040) == 0x00000040)) { props_ = java.util.Collections.unmodifiableList(props_); @@ -4232,50 +4621,36 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable .ensureFieldAccessorsInitialized( - Column.class, Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Column parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Column(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.Column.class, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder.class); } private int bitField0_; public static final int INDEX_FIELD_NUMBER = 1; private int index_; /** - * optional int32 index = 1; - * *
            **字段下标*
            * 
      + * + * optional int32 index = 1; */ public boolean hasIndex() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int32 index = 1; - * *
            **字段下标*
            * 
      + * + * optional int32 index = 1; */ public int getIndex() { return index_; @@ -4284,53 +4659,53 @@ public final class CanalEntry { public static final int SQLTYPE_FIELD_NUMBER = 2; private int sqlType_; /** - * optional int32 sqlType = 2; - * *
            **字段java中类型*
            * 
      + * + * optional int32 sqlType = 2; */ public boolean hasSqlType() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional int32 sqlType = 2; - * *
            **字段java中类型*
            * 
      + * + * optional int32 sqlType = 2; */ public int getSqlType() { return sqlType_; } public static final int NAME_FIELD_NUMBER = 3; - private Object name_; + private volatile java.lang.Object name_; /** - * optional string name = 3; - * *
            **字段名称(忽略大小写),在mysql中是没有的*
            * 
      + * + * optional string name = 3; */ public boolean hasName() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional string name = 3; - * *
            **字段名称(忽略大小写),在mysql中是没有的*
            * 
      + * + * optional string name = 3; */ - public String getName() { - Object ref = name_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { name_ = s; } @@ -4338,19 +4713,19 @@ public final class CanalEntry { } } /** - * optional string name = 3; - * *
            **字段名称(忽略大小写),在mysql中是没有的*
            * 
      + * + * optional string name = 3; */ public com.google.protobuf.ByteString getNameBytes() { - Object ref = name_; - if (ref instanceof String) { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); name_ = b; return b; } else { @@ -4361,21 +4736,21 @@ public final class CanalEntry { public static final int ISKEY_FIELD_NUMBER = 4; private boolean isKey_; /** - * optional bool isKey = 4; - * *
            **是否是主键*
            * 
      + * + * optional bool isKey = 4; */ public boolean hasIsKey() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional bool isKey = 4; - * *
            **是否是主键*
            * 
      + * + * optional bool isKey = 4; */ public boolean getIsKey() { return isKey_; @@ -4384,21 +4759,21 @@ public final class CanalEntry { public static final int UPDATED_FIELD_NUMBER = 5; private boolean updated_; /** - * optional bool updated = 5; - * *
            **如果EventType=UPDATE,用于标识这个字段值是否有修改*
            * 
      + * + * optional bool updated = 5; */ public boolean hasUpdated() { return ((bitField0_ & 0x00000010) == 0x00000010); } /** - * optional bool updated = 5; - * *
            **如果EventType=UPDATE,用于标识这个字段值是否有修改*
            * 
      + * + * optional bool updated = 5; */ public boolean getUpdated() { return updated_; @@ -4407,108 +4782,108 @@ public final class CanalEntry { public static final int ISNULL_FIELD_NUMBER = 6; private boolean isNull_; /** - * optional bool isNull = 6 [default = false]; - * *
            ** 标识是否为空  *
            * 
      + * + * optional bool isNull = 6 [default = false]; */ public boolean hasIsNull() { return ((bitField0_ & 0x00000020) == 0x00000020); } /** - * optional bool isNull = 6 [default = false]; - * *
            ** 标识是否为空  *
            * 
      + * + * optional bool isNull = 6 [default = false]; */ public boolean getIsNull() { return isNull_; } public static final int PROPS_FIELD_NUMBER = 7; - private java.util.List props_; + private java.util.List props_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public int getPropsCount() { return props_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { return props_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { return props_.get(index); } public static final int VALUE_FIELD_NUMBER = 8; - private Object value_; + private volatile java.lang.Object value_; /** - * optional string value = 8; - * *
            ** 字段值,timestamp,Datetime是一个时间格式的文本 *
            * 
      + * + * optional string value = 8; */ public boolean hasValue() { return ((bitField0_ & 0x00000040) == 0x00000040); } /** - * optional string value = 8; - * *
            ** 字段值,timestamp,Datetime是一个时间格式的文本 *
            * 
      + * + * optional string value = 8; */ - public String getValue() { - Object ref = value_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getValue() { + java.lang.Object ref = value_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { value_ = s; } @@ -4516,19 +4891,19 @@ public final class CanalEntry { } } /** - * optional string value = 8; - * *
            ** 字段值,timestamp,Datetime是一个时间格式的文本 *
            * 
      + * + * optional string value = 8; */ public com.google.protobuf.ByteString getValueBytes() { - Object ref = value_; - if (ref instanceof String) { + java.lang.Object ref = value_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); value_ = b; return b; } else { @@ -4539,53 +4914,53 @@ public final class CanalEntry { public static final int LENGTH_FIELD_NUMBER = 9; private int length_; /** - * optional int32 length = 9; - * *
            ** 对应数据对象原始长度 *
            * 
      + * + * optional int32 length = 9; */ public boolean hasLength() { return ((bitField0_ & 0x00000080) == 0x00000080); } /** - * optional int32 length = 9; - * *
            ** 对应数据对象原始长度 *
            * 
      + * + * optional int32 length = 9; */ public int getLength() { return length_; } public static final int MYSQLTYPE_FIELD_NUMBER = 10; - private Object mysqlType_; + private volatile java.lang.Object mysqlType_; /** - * optional string mysqlType = 10; - * *
            **字段mysql类型*
            * 
      + * + * optional string mysqlType = 10; */ public boolean hasMysqlType() { return ((bitField0_ & 0x00000100) == 0x00000100); } /** - * optional string mysqlType = 10; - * *
            **字段mysql类型*
            * 
      + * + * optional string mysqlType = 10; */ - public String getMysqlType() { - Object ref = mysqlType_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getMysqlType() { + java.lang.Object ref = mysqlType_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { mysqlType_ = s; } @@ -4593,19 +4968,19 @@ public final class CanalEntry { } } /** - * optional string mysqlType = 10; - * *
            **字段mysql类型*
            * 
      + * + * optional string mysqlType = 10; */ public com.google.protobuf.ByteString getMysqlTypeBytes() { - Object ref = mysqlType_; - if (ref instanceof String) { + java.lang.Object ref = mysqlType_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); mysqlType_ = b; return b; } else { @@ -4613,19 +4988,8 @@ public final class CanalEntry { } } - private void initFields() { - index_ = 0; - sqlType_ = 0; - name_ = ""; - isKey_ = false; - updated_ = false; - isNull_ = false; - props_ = java.util.Collections.emptyList(); - value_ = ""; - length_ = 0; - mysqlType_ = ""; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -4635,9 +4999,9 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeInt32(1, index_); } @@ -4645,7 +5009,7 @@ public final class CanalEntry { output.writeInt32(2, sqlType_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(3, getNameBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 3, name_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeBool(4, isKey_); @@ -4660,20 +5024,20 @@ public final class CanalEntry { output.writeMessage(7, props_.get(i)); } if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeBytes(8, getValueBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 8, value_); } if (((bitField0_ & 0x00000080) == 0x00000080)) { output.writeInt32(9, length_); } if (((bitField0_ & 0x00000100) == 0x00000100)) { - output.writeBytes(10, getMysqlTypeBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 10, mysqlType_); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; @@ -4686,8 +5050,7 @@ public final class CanalEntry { .computeInt32Size(2, sqlType_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, getNameBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, name_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream @@ -4706,116 +5069,249 @@ public final class CanalEntry { .computeMessageSize(7, props_.get(i)); } if (((bitField0_ & 0x00000040) == 0x00000040)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(8, getValueBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, value_); } if (((bitField0_ & 0x00000080) == 0x00000080)) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(9, length_); } if (((bitField0_ & 0x00000100) == 0x00000100)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(10, getMysqlTypeBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(10, mysqlType_); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.Column)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.Column other = (com.alibaba.otter.canal.protocol.CanalEntry.Column) obj; + + boolean result = true; + result = result && (hasIndex() == other.hasIndex()); + if (hasIndex()) { + result = result && (getIndex() + == other.getIndex()); + } + result = result && (hasSqlType() == other.hasSqlType()); + if (hasSqlType()) { + result = result && (getSqlType() + == other.getSqlType()); + } + result = result && (hasName() == other.hasName()); + if (hasName()) { + result = result && getName() + .equals(other.getName()); + } + result = result && (hasIsKey() == other.hasIsKey()); + if (hasIsKey()) { + result = result && (getIsKey() + == other.getIsKey()); + } + result = result && (hasUpdated() == other.hasUpdated()); + if (hasUpdated()) { + result = result && (getUpdated() + == other.getUpdated()); + } + result = result && (hasIsNull() == other.hasIsNull()); + if (hasIsNull()) { + result = result && (getIsNull() + == other.getIsNull()); + } + result = result && getPropsList() + .equals(other.getPropsList()); + result = result && (hasValue() == other.hasValue()); + if (hasValue()) { + result = result && getValue() + .equals(other.getValue()); + } + result = result && (hasLength() == other.hasLength()); + if (hasLength()) { + result = result && (getLength() + == other.getLength()); + } + result = result && (hasMysqlType() == other.hasMysqlType()); + if (hasMysqlType()) { + result = result && getMysqlType() + .equals(other.getMysqlType()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static Column parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasIndex()) { + hash = (37 * hash) + INDEX_FIELD_NUMBER; + hash = (53 * hash) + getIndex(); + } + if (hasSqlType()) { + hash = (37 * hash) + SQLTYPE_FIELD_NUMBER; + hash = (53 * hash) + getSqlType(); + } + if (hasName()) { + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + } + if (hasIsKey()) { + hash = (37 * hash) + ISKEY_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getIsKey()); + } + if (hasUpdated()) { + hash = (37 * hash) + UPDATED_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getUpdated()); + } + if (hasIsNull()) { + hash = (37 * hash) + ISNULL_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getIsNull()); + } + if (getPropsCount() > 0) { + hash = (37 * hash) + PROPS_FIELD_NUMBER; + hash = (53 * hash) + getPropsList().hashCode(); + } + if (hasValue()) { + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); + } + if (hasLength()) { + hash = (37 * hash) + LENGTH_FIELD_NUMBER; + hash = (53 * hash) + getLength(); + } + if (hasMysqlType()) { + hash = (37 * hash) + MYSQLTYPE_FIELD_NUMBER; + hash = (53 * hash) + getMysqlType().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Column parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Column parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Column parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Column parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Column parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Column parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static Column parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static Column parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Column parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Column parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(Column prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Column prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Column} - * *
            **每个字段的数据结构*
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Column} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Column) - ColumnOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable .ensureFieldAccessorsInitialized( - Column.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.Column.class, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Column.newBuilder() @@ -4824,19 +5320,17 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { getPropsFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); index_ = 0; @@ -4866,29 +5360,29 @@ public final class CanalEntry { return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; } - public Column getDefaultInstanceForType() { - return Column.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Column getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance(); } - public Column build() { - Column result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Column build() { + com.alibaba.otter.canal.protocol.CanalEntry.Column result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public Column buildPartial() { - Column result = new Column(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Column buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.Column result = new com.alibaba.otter.canal.protocol.CanalEntry.Column(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -4941,17 +5435,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Column) { - return mergeFrom((Column)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Column) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Column)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(Column other) { - if (other == Column.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Column other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()) return this; if (other.hasIndex()) { setIndex(other.getIndex()); } @@ -4991,7 +5518,7 @@ public final class CanalEntry { props_ = other.props_; bitField0_ = (bitField0_ & ~0x00000040); propsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; } else { propsBuilder_.addAllMessages(other.props_); @@ -5011,24 +5538,27 @@ public final class CanalEntry { mysqlType_ = other.mysqlType_; onChanged(); } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - Column parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.Column parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Column) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Column) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -5040,31 +5570,31 @@ public final class CanalEntry { private int index_ ; /** - * optional int32 index = 1; - * *
              **字段下标*
              * 
      + * + * optional int32 index = 1; */ public boolean hasIndex() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int32 index = 1; - * *
              **字段下标*
              * 
      + * + * optional int32 index = 1; */ public int getIndex() { return index_; } /** - * optional int32 index = 1; - * *
              **字段下标*
              * 
      + * + * optional int32 index = 1; */ public Builder setIndex(int value) { bitField0_ |= 0x00000001; @@ -5073,11 +5603,11 @@ public final class CanalEntry { return this; } /** - * optional int32 index = 1; - * *
              **字段下标*
              * 
      + * + * optional int32 index = 1; */ public Builder clearIndex() { bitField0_ = (bitField0_ & ~0x00000001); @@ -5088,31 +5618,31 @@ public final class CanalEntry { private int sqlType_ ; /** - * optional int32 sqlType = 2; - * *
              **字段java中类型*
              * 
      + * + * optional int32 sqlType = 2; */ public boolean hasSqlType() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional int32 sqlType = 2; - * *
              **字段java中类型*
              * 
      + * + * optional int32 sqlType = 2; */ public int getSqlType() { return sqlType_; } /** - * optional int32 sqlType = 2; - * *
              **字段java中类型*
              * 
      + * + * optional int32 sqlType = 2; */ public Builder setSqlType(int value) { bitField0_ |= 0x00000002; @@ -5121,11 +5651,11 @@ public final class CanalEntry { return this; } /** - * optional int32 sqlType = 2; - * *
              **字段java中类型*
              * 
      + * + * optional int32 sqlType = 2; */ public Builder clearSqlType() { bitField0_ = (bitField0_ & ~0x00000002); @@ -5134,52 +5664,52 @@ public final class CanalEntry { return this; } - private Object name_ = ""; + private java.lang.Object name_ = ""; /** - * optional string name = 3; - * *
              **字段名称(忽略大小写),在mysql中是没有的*
              * 
      + * + * optional string name = 3; */ public boolean hasName() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional string name = 3; - * *
              **字段名称(忽略大小写),在mysql中是没有的*
              * 
      + * + * optional string name = 3; */ - public String getName() { - Object ref = name_; - if (!(ref instanceof String)) { + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { name_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string name = 3; - * *
              **字段名称(忽略大小写),在mysql中是没有的*
              * 
      + * + * optional string name = 3; */ public com.google.protobuf.ByteString getNameBytes() { - Object ref = name_; + java.lang.Object ref = name_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); name_ = b; return b; } else { @@ -5187,14 +5717,14 @@ public final class CanalEntry { } } /** - * optional string name = 3; - * *
              **字段名称(忽略大小写),在mysql中是没有的*
              * 
      + * + * optional string name = 3; */ public Builder setName( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -5204,11 +5734,11 @@ public final class CanalEntry { return this; } /** - * optional string name = 3; - * *
              **字段名称(忽略大小写),在mysql中是没有的*
              * 
      + * + * optional string name = 3; */ public Builder clearName() { bitField0_ = (bitField0_ & ~0x00000004); @@ -5217,11 +5747,11 @@ public final class CanalEntry { return this; } /** - * optional string name = 3; - * *
              **字段名称(忽略大小写),在mysql中是没有的*
              * 
      + * + * optional string name = 3; */ public Builder setNameBytes( com.google.protobuf.ByteString value) { @@ -5236,31 +5766,31 @@ public final class CanalEntry { private boolean isKey_ ; /** - * optional bool isKey = 4; - * *
              **是否是主键*
              * 
      + * + * optional bool isKey = 4; */ public boolean hasIsKey() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional bool isKey = 4; - * *
              **是否是主键*
              * 
      + * + * optional bool isKey = 4; */ public boolean getIsKey() { return isKey_; } /** - * optional bool isKey = 4; - * *
              **是否是主键*
              * 
      + * + * optional bool isKey = 4; */ public Builder setIsKey(boolean value) { bitField0_ |= 0x00000008; @@ -5269,11 +5799,11 @@ public final class CanalEntry { return this; } /** - * optional bool isKey = 4; - * *
              **是否是主键*
              * 
      + * + * optional bool isKey = 4; */ public Builder clearIsKey() { bitField0_ = (bitField0_ & ~0x00000008); @@ -5284,31 +5814,31 @@ public final class CanalEntry { private boolean updated_ ; /** - * optional bool updated = 5; - * *
              **如果EventType=UPDATE,用于标识这个字段值是否有修改*
              * 
      + * + * optional bool updated = 5; */ public boolean hasUpdated() { return ((bitField0_ & 0x00000010) == 0x00000010); } /** - * optional bool updated = 5; - * *
              **如果EventType=UPDATE,用于标识这个字段值是否有修改*
              * 
      + * + * optional bool updated = 5; */ public boolean getUpdated() { return updated_; } /** - * optional bool updated = 5; - * *
              **如果EventType=UPDATE,用于标识这个字段值是否有修改*
              * 
      + * + * optional bool updated = 5; */ public Builder setUpdated(boolean value) { bitField0_ |= 0x00000010; @@ -5317,11 +5847,11 @@ public final class CanalEntry { return this; } /** - * optional bool updated = 5; - * *
              **如果EventType=UPDATE,用于标识这个字段值是否有修改*
              * 
      + * + * optional bool updated = 5; */ public Builder clearUpdated() { bitField0_ = (bitField0_ & ~0x00000010); @@ -5332,31 +5862,31 @@ public final class CanalEntry { private boolean isNull_ ; /** - * optional bool isNull = 6 [default = false]; - * *
              ** 标识是否为空  *
              * 
      + * + * optional bool isNull = 6 [default = false]; */ public boolean hasIsNull() { return ((bitField0_ & 0x00000020) == 0x00000020); } /** - * optional bool isNull = 6 [default = false]; - * *
              ** 标识是否为空  *
              * 
      + * + * optional bool isNull = 6 [default = false]; */ public boolean getIsNull() { return isNull_; } /** - * optional bool isNull = 6 [default = false]; - * *
              ** 标识是否为空  *
              * 
      + * + * optional bool isNull = 6 [default = false]; */ public Builder setIsNull(boolean value) { bitField0_ |= 0x00000020; @@ -5365,11 +5895,11 @@ public final class CanalEntry { return this; } /** - * optional bool isNull = 6 [default = false]; - * *
              ** 标识是否为空  *
              * 
      + * + * optional bool isNull = 6 [default = false]; */ public Builder clearIsNull() { bitField0_ = (bitField0_ & ~0x00000020); @@ -5378,26 +5908,26 @@ public final class CanalEntry { return this; } - private java.util.List props_ = + private java.util.List props_ = java.util.Collections.emptyList(); private void ensurePropsIsMutable() { if (!((bitField0_ & 0x00000040) == 0x00000040)) { - props_ = new java.util.ArrayList(props_); + props_ = new java.util.ArrayList(props_); bitField0_ |= 0x00000040; } } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> propsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { if (propsBuilder_ == null) { return java.util.Collections.unmodifiableList(props_); } else { @@ -5405,11 +5935,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public int getPropsCount() { if (propsBuilder_ == null) { @@ -5419,13 +5949,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -5433,14 +5963,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder setProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -5454,14 +5984,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder setProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.set(index, builderForValue.build()); @@ -5472,13 +6002,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public Builder addProps(Pair value) { + public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -5492,14 +6022,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder addProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -5513,14 +6043,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder addProps( - Pair.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(builderForValue.build()); @@ -5531,14 +6061,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder addProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(index, builderForValue.build()); @@ -5549,14 +6079,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder addAllProps( - Iterable values) { + java.lang.Iterable values) { if (propsBuilder_ == null) { ensurePropsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -5568,11 +6098,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder clearProps() { if (propsBuilder_ == null) { @@ -5585,11 +6115,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ public Builder removeProps(int index) { if (propsBuilder_ == null) { @@ -5602,24 +6132,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public Pair.Builder getPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder( int index) { return getPropsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -5627,13 +6157,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { if (propsBuilder_ != null) { return propsBuilder_.getMessageOrBuilderList(); @@ -5642,45 +6172,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public Pair.Builder addPropsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { return getPropsFieldBuilder().addBuilder( - Pair.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public Pair.Builder addPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder( int index) { return getPropsFieldBuilder().addBuilder( - index, Pair.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 7; */ - public java.util.List + public java.util.List getPropsBuilderList() { return getPropsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> getPropsFieldBuilder() { if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder>( + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder>( props_, ((bitField0_ & 0x00000040) == 0x00000040), getParentForChildren(), @@ -5690,52 +6220,52 @@ public final class CanalEntry { return propsBuilder_; } - private Object value_ = ""; + private java.lang.Object value_ = ""; /** - * optional string value = 8; - * *
              ** 字段值,timestamp,Datetime是一个时间格式的文本 *
              * 
      + * + * optional string value = 8; */ public boolean hasValue() { return ((bitField0_ & 0x00000080) == 0x00000080); } /** - * optional string value = 8; - * *
              ** 字段值,timestamp,Datetime是一个时间格式的文本 *
              * 
      + * + * optional string value = 8; */ - public String getValue() { - Object ref = value_; - if (!(ref instanceof String)) { + public java.lang.String getValue() { + java.lang.Object ref = value_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { value_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string value = 8; - * *
              ** 字段值,timestamp,Datetime是一个时间格式的文本 *
              * 
      + * + * optional string value = 8; */ public com.google.protobuf.ByteString getValueBytes() { - Object ref = value_; + java.lang.Object ref = value_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); value_ = b; return b; } else { @@ -5743,14 +6273,14 @@ public final class CanalEntry { } } /** - * optional string value = 8; - * *
              ** 字段值,timestamp,Datetime是一个时间格式的文本 *
              * 
      + * + * optional string value = 8; */ public Builder setValue( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -5760,11 +6290,11 @@ public final class CanalEntry { return this; } /** - * optional string value = 8; - * *
              ** 字段值,timestamp,Datetime是一个时间格式的文本 *
              * 
      + * + * optional string value = 8; */ public Builder clearValue() { bitField0_ = (bitField0_ & ~0x00000080); @@ -5773,11 +6303,11 @@ public final class CanalEntry { return this; } /** - * optional string value = 8; - * *
              ** 字段值,timestamp,Datetime是一个时间格式的文本 *
              * 
      + * + * optional string value = 8; */ public Builder setValueBytes( com.google.protobuf.ByteString value) { @@ -5792,31 +6322,31 @@ public final class CanalEntry { private int length_ ; /** - * optional int32 length = 9; - * *
              ** 对应数据对象原始长度 *
              * 
      + * + * optional int32 length = 9; */ public boolean hasLength() { return ((bitField0_ & 0x00000100) == 0x00000100); } /** - * optional int32 length = 9; - * *
              ** 对应数据对象原始长度 *
              * 
      + * + * optional int32 length = 9; */ public int getLength() { return length_; } /** - * optional int32 length = 9; - * *
              ** 对应数据对象原始长度 *
              * 
      + * + * optional int32 length = 9; */ public Builder setLength(int value) { bitField0_ |= 0x00000100; @@ -5825,11 +6355,11 @@ public final class CanalEntry { return this; } /** - * optional int32 length = 9; - * *
              ** 对应数据对象原始长度 *
              * 
      + * + * optional int32 length = 9; */ public Builder clearLength() { bitField0_ = (bitField0_ & ~0x00000100); @@ -5838,52 +6368,52 @@ public final class CanalEntry { return this; } - private Object mysqlType_ = ""; + private java.lang.Object mysqlType_ = ""; /** - * optional string mysqlType = 10; - * *
              **字段mysql类型*
              * 
      + * + * optional string mysqlType = 10; */ public boolean hasMysqlType() { return ((bitField0_ & 0x00000200) == 0x00000200); } /** - * optional string mysqlType = 10; - * *
              **字段mysql类型*
              * 
      + * + * optional string mysqlType = 10; */ - public String getMysqlType() { - Object ref = mysqlType_; - if (!(ref instanceof String)) { + public java.lang.String getMysqlType() { + java.lang.Object ref = mysqlType_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { mysqlType_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string mysqlType = 10; - * *
              **字段mysql类型*
              * 
      + * + * optional string mysqlType = 10; */ public com.google.protobuf.ByteString getMysqlTypeBytes() { - Object ref = mysqlType_; + java.lang.Object ref = mysqlType_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); mysqlType_ = b; return b; } else { @@ -5891,14 +6421,14 @@ public final class CanalEntry { } } /** - * optional string mysqlType = 10; - * *
              **字段mysql类型*
              * 
      + * + * optional string mysqlType = 10; */ public Builder setMysqlType( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -5908,11 +6438,11 @@ public final class CanalEntry { return this; } /** - * optional string mysqlType = 10; - * *
              **字段mysql类型*
              * 
      + * + * optional string mysqlType = 10; */ public Builder clearMysqlType() { bitField0_ = (bitField0_ & ~0x00000200); @@ -5921,11 +6451,11 @@ public final class CanalEntry { return this; } /** - * optional string mysqlType = 10; - * *
              **字段mysql类型*
              * 
      + * + * optional string mysqlType = 10; */ public Builder setMysqlTypeBytes( com.google.protobuf.ByteString value) { @@ -5937,16 +6467,57 @@ public final class CanalEntry { onChanged(); return this; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Column) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Column) + private static final com.alibaba.otter.canal.protocol.CanalEntry.Column DEFAULT_INSTANCE; static { - defaultInstance = new Column(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.Column(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Column getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Column parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Column(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Column getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Column) } public interface RowDataOrBuilder extends @@ -5954,168 +6525,168 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - java.util.List + java.util.List getBeforeColumnsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - Column getBeforeColumns(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Column getBeforeColumns(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ int getBeforeColumnsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - java.util.List + java.util.List getBeforeColumnsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - ColumnOrBuilder getBeforeColumnsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getBeforeColumnsOrBuilder( + int index); /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - java.util.List + java.util.List getAfterColumnsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - Column getAfterColumns(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Column getAfterColumns(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ int getAfterColumnsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - java.util.List + java.util.List getAfterColumnsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - ColumnOrBuilder getAfterColumnsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getAfterColumnsOrBuilder( + int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - java.util.List + java.util.List getPropsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - Pair getProps(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ int getPropsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - java.util.List + java.util.List getPropsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - PairOrBuilder getPropsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( + int index); } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.RowData} */ - public static final class RowData extends - com.google.protobuf.GeneratedMessage implements + public static final class RowData extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.RowData) RowDataOrBuilder { + private static final long serialVersionUID = 0L; // Use RowData.newBuilder() to construct. - private RowData(com.google.protobuf.GeneratedMessage.Builder builder) { + private RowData(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private RowData(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final RowData defaultInstance; - public static RowData getDefaultInstance() { - return defaultInstance; + private RowData() { + beforeColumns_ = java.util.Collections.emptyList(); + afterColumns_ = java.util.Collections.emptyList(); + props_ = java.util.Collections.emptyList(); } - public RowData getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private RowData( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -6127,35 +6698,38 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 10: { if (!((mutable_bitField0_ & 0x00000001) == 0x00000001)) { - beforeColumns_ = new java.util.ArrayList(); + beforeColumns_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000001; } - beforeColumns_.add(input.readMessage(Column.PARSER, extensionRegistry)); + beforeColumns_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.PARSER, extensionRegistry)); break; } case 18: { if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - afterColumns_ = new java.util.ArrayList(); + afterColumns_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000002; } - afterColumns_.add(input.readMessage(Column.PARSER, extensionRegistry)); + afterColumns_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.PARSER, extensionRegistry)); break; } case 26: { if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(); + props_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000004; } - props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + props_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } break; } } @@ -6164,7 +6738,7 @@ public final class CanalEntry { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000001) == 0x00000001)) { beforeColumns_ = java.util.Collections.unmodifiableList(beforeColumns_); @@ -6181,202 +6755,184 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable .ensureFieldAccessorsInitialized( - RowData.class, Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public RowData parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new RowData(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.RowData.class, com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder.class); } public static final int BEFORECOLUMNS_FIELD_NUMBER = 1; - private java.util.List beforeColumns_; + private java.util.List beforeColumns_; /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public java.util.List getBeforeColumnsList() { + public java.util.List getBeforeColumnsList() { return beforeColumns_; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public java.util.List + public java.util.List getBeforeColumnsOrBuilderList() { return beforeColumns_; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public int getBeforeColumnsCount() { return beforeColumns_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public Column getBeforeColumns(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Column getBeforeColumns(int index) { return beforeColumns_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
            ** 字段信息,增量数据(修改前,删除前) *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public ColumnOrBuilder getBeforeColumnsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getBeforeColumnsOrBuilder( int index) { return beforeColumns_.get(index); } public static final int AFTERCOLUMNS_FIELD_NUMBER = 2; - private java.util.List afterColumns_; + private java.util.List afterColumns_; /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public java.util.List getAfterColumnsList() { + public java.util.List getAfterColumnsList() { return afterColumns_; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public java.util.List + public java.util.List getAfterColumnsOrBuilderList() { return afterColumns_; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public int getAfterColumnsCount() { return afterColumns_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public Column getAfterColumns(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Column getAfterColumns(int index) { return afterColumns_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
            ** 字段信息,增量数据(修改后,新增后)  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public ColumnOrBuilder getAfterColumnsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getAfterColumnsOrBuilder( int index) { return afterColumns_.get(index); } public static final int PROPS_FIELD_NUMBER = 3; - private java.util.List props_; + private java.util.List props_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public int getPropsCount() { return props_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { return props_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { return props_.get(index); } - private void initFields() { - beforeColumns_ = java.util.Collections.emptyList(); - afterColumns_ = java.util.Collections.emptyList(); - props_ = java.util.Collections.emptyList(); - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -6386,9 +6942,9 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); for (int i = 0; i < beforeColumns_.size(); i++) { output.writeMessage(1, beforeColumns_.get(i)); } @@ -6398,12 +6954,12 @@ public final class CanalEntry { for (int i = 0; i < props_.size(); i++) { output.writeMessage(3, props_.get(i)); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; @@ -6419,81 +6975,143 @@ public final class CanalEntry { size += com.google.protobuf.CodedOutputStream .computeMessageSize(3, props_.get(i)); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.RowData)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.RowData other = (com.alibaba.otter.canal.protocol.CanalEntry.RowData) obj; + + boolean result = true; + result = result && getBeforeColumnsList() + .equals(other.getBeforeColumnsList()); + result = result && getAfterColumnsList() + .equals(other.getAfterColumnsList()); + result = result && getPropsList() + .equals(other.getPropsList()); + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static RowData parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getBeforeColumnsCount() > 0) { + hash = (37 * hash) + BEFORECOLUMNS_FIELD_NUMBER; + hash = (53 * hash) + getBeforeColumnsList().hashCode(); + } + if (getAfterColumnsCount() > 0) { + hash = (37 * hash) + AFTERCOLUMNS_FIELD_NUMBER; + hash = (53 * hash) + getAfterColumnsList().hashCode(); + } + if (getPropsCount() > 0) { + hash = (37 * hash) + PROPS_FIELD_NUMBER; + hash = (53 * hash) + getPropsList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static RowData parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static RowData parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static RowData parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static RowData parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static RowData parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static RowData parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static RowData parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static RowData parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static RowData parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(RowData prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.RowData prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } @@ -6501,19 +7119,20 @@ public final class CanalEntry { * Protobuf type {@code com.alibaba.otter.canal.protocol.RowData} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.RowData) - RowDataOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable .ensureFieldAccessorsInitialized( - RowData.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.RowData.class, com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.RowData.newBuilder() @@ -6522,21 +7141,19 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { getBeforeColumnsFieldBuilder(); getAfterColumnsFieldBuilder(); getPropsFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); if (beforeColumnsBuilder_ == null) { @@ -6560,29 +7177,29 @@ public final class CanalEntry { return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; } - public RowData getDefaultInstanceForType() { - return RowData.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowData getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance(); } - public RowData build() { - RowData result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowData build() { + com.alibaba.otter.canal.protocol.CanalEntry.RowData result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public RowData buildPartial() { - RowData result = new RowData(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowData buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.RowData result = new com.alibaba.otter.canal.protocol.CanalEntry.RowData(this); int from_bitField0_ = bitField0_; if (beforeColumnsBuilder_ == null) { if (((bitField0_ & 0x00000001) == 0x00000001)) { @@ -6615,17 +7232,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof RowData) { - return mergeFrom((RowData)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.RowData) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.RowData)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(RowData other) { - if (other == RowData.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.RowData other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance()) return this; if (beforeColumnsBuilder_ == null) { if (!other.beforeColumns_.isEmpty()) { if (beforeColumns_.isEmpty()) { @@ -6645,7 +7295,7 @@ public final class CanalEntry { beforeColumns_ = other.beforeColumns_; bitField0_ = (bitField0_ & ~0x00000001); beforeColumnsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getBeforeColumnsFieldBuilder() : null; } else { beforeColumnsBuilder_.addAllMessages(other.beforeColumns_); @@ -6671,7 +7321,7 @@ public final class CanalEntry { afterColumns_ = other.afterColumns_; bitField0_ = (bitField0_ & ~0x00000002); afterColumnsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getAfterColumnsFieldBuilder() : null; } else { afterColumnsBuilder_.addAllMessages(other.afterColumns_); @@ -6697,31 +7347,34 @@ public final class CanalEntry { props_ = other.props_; bitField0_ = (bitField0_ & ~0x00000004); propsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; } else { propsBuilder_.addAllMessages(other.props_); } } } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - RowData parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.RowData parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (RowData) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.RowData) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -6731,26 +7384,26 @@ public final class CanalEntry { } private int bitField0_; - private java.util.List beforeColumns_ = + private java.util.List beforeColumns_ = java.util.Collections.emptyList(); private void ensureBeforeColumnsIsMutable() { if (!((bitField0_ & 0x00000001) == 0x00000001)) { - beforeColumns_ = new java.util.ArrayList(beforeColumns_); + beforeColumns_ = new java.util.ArrayList(beforeColumns_); bitField0_ |= 0x00000001; } } - private com.google.protobuf.RepeatedFieldBuilder< - Column, Column.Builder, ColumnOrBuilder> beforeColumnsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Column, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder, com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder> beforeColumnsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public java.util.List getBeforeColumnsList() { + public java.util.List getBeforeColumnsList() { if (beforeColumnsBuilder_ == null) { return java.util.Collections.unmodifiableList(beforeColumns_); } else { @@ -6758,11 +7411,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public int getBeforeColumnsCount() { if (beforeColumnsBuilder_ == null) { @@ -6772,13 +7425,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public Column getBeforeColumns(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Column getBeforeColumns(int index) { if (beforeColumnsBuilder_ == null) { return beforeColumns_.get(index); } else { @@ -6786,14 +7439,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder setBeforeColumns( - int index, Column value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { if (beforeColumnsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -6807,14 +7460,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder setBeforeColumns( - int index, Column.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { if (beforeColumnsBuilder_ == null) { ensureBeforeColumnsIsMutable(); beforeColumns_.set(index, builderForValue.build()); @@ -6825,13 +7478,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public Builder addBeforeColumns(Column value) { + public Builder addBeforeColumns(com.alibaba.otter.canal.protocol.CanalEntry.Column value) { if (beforeColumnsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -6845,14 +7498,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder addBeforeColumns( - int index, Column value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { if (beforeColumnsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -6866,14 +7519,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder addBeforeColumns( - Column.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { if (beforeColumnsBuilder_ == null) { ensureBeforeColumnsIsMutable(); beforeColumns_.add(builderForValue.build()); @@ -6884,14 +7537,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder addBeforeColumns( - int index, Column.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { if (beforeColumnsBuilder_ == null) { ensureBeforeColumnsIsMutable(); beforeColumns_.add(index, builderForValue.build()); @@ -6902,14 +7555,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder addAllBeforeColumns( - Iterable values) { + java.lang.Iterable values) { if (beforeColumnsBuilder_ == null) { ensureBeforeColumnsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -6921,11 +7574,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder clearBeforeColumns() { if (beforeColumnsBuilder_ == null) { @@ -6938,11 +7591,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ public Builder removeBeforeColumns(int index) { if (beforeColumnsBuilder_ == null) { @@ -6955,24 +7608,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public Column.Builder getBeforeColumnsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder getBeforeColumnsBuilder( int index) { return getBeforeColumnsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public ColumnOrBuilder getBeforeColumnsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getBeforeColumnsOrBuilder( int index) { if (beforeColumnsBuilder_ == null) { return beforeColumns_.get(index); } else { @@ -6980,13 +7633,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public java.util.List + public java.util.List getBeforeColumnsOrBuilderList() { if (beforeColumnsBuilder_ != null) { return beforeColumnsBuilder_.getMessageOrBuilderList(); @@ -6995,45 +7648,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public Column.Builder addBeforeColumnsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addBeforeColumnsBuilder() { return getBeforeColumnsFieldBuilder().addBuilder( - Column.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public Column.Builder addBeforeColumnsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addBeforeColumnsBuilder( int index) { return getBeforeColumnsFieldBuilder().addBuilder( - index, Column.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; - * *
              ** 字段信息,增量数据(修改前,删除前) *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column beforeColumns = 1; */ - public java.util.List + public java.util.List getBeforeColumnsBuilderList() { return getBeforeColumnsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Column, Column.Builder, ColumnOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Column, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder, com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder> getBeforeColumnsFieldBuilder() { if (beforeColumnsBuilder_ == null) { - beforeColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Column, Column.Builder, ColumnOrBuilder>( + beforeColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Column, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder, com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder>( beforeColumns_, ((bitField0_ & 0x00000001) == 0x00000001), getParentForChildren(), @@ -7043,26 +7696,26 @@ public final class CanalEntry { return beforeColumnsBuilder_; } - private java.util.List afterColumns_ = + private java.util.List afterColumns_ = java.util.Collections.emptyList(); private void ensureAfterColumnsIsMutable() { if (!((bitField0_ & 0x00000002) == 0x00000002)) { - afterColumns_ = new java.util.ArrayList(afterColumns_); + afterColumns_ = new java.util.ArrayList(afterColumns_); bitField0_ |= 0x00000002; } } - private com.google.protobuf.RepeatedFieldBuilder< - Column, Column.Builder, ColumnOrBuilder> afterColumnsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Column, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder, com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder> afterColumnsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public java.util.List getAfterColumnsList() { + public java.util.List getAfterColumnsList() { if (afterColumnsBuilder_ == null) { return java.util.Collections.unmodifiableList(afterColumns_); } else { @@ -7070,11 +7723,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public int getAfterColumnsCount() { if (afterColumnsBuilder_ == null) { @@ -7084,13 +7737,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public Column getAfterColumns(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Column getAfterColumns(int index) { if (afterColumnsBuilder_ == null) { return afterColumns_.get(index); } else { @@ -7098,14 +7751,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder setAfterColumns( - int index, Column value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { if (afterColumnsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -7119,14 +7772,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder setAfterColumns( - int index, Column.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { if (afterColumnsBuilder_ == null) { ensureAfterColumnsIsMutable(); afterColumns_.set(index, builderForValue.build()); @@ -7137,13 +7790,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public Builder addAfterColumns(Column value) { + public Builder addAfterColumns(com.alibaba.otter.canal.protocol.CanalEntry.Column value) { if (afterColumnsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -7157,14 +7810,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder addAfterColumns( - int index, Column value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column value) { if (afterColumnsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -7178,14 +7831,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder addAfterColumns( - Column.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { if (afterColumnsBuilder_ == null) { ensureAfterColumnsIsMutable(); afterColumns_.add(builderForValue.build()); @@ -7196,14 +7849,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder addAfterColumns( - int index, Column.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder builderForValue) { if (afterColumnsBuilder_ == null) { ensureAfterColumnsIsMutable(); afterColumns_.add(index, builderForValue.build()); @@ -7214,14 +7867,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder addAllAfterColumns( - Iterable values) { + java.lang.Iterable values) { if (afterColumnsBuilder_ == null) { ensureAfterColumnsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -7233,11 +7886,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder clearAfterColumns() { if (afterColumnsBuilder_ == null) { @@ -7250,11 +7903,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ public Builder removeAfterColumns(int index) { if (afterColumnsBuilder_ == null) { @@ -7267,24 +7920,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public Column.Builder getAfterColumnsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder getAfterColumnsBuilder( int index) { return getAfterColumnsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public ColumnOrBuilder getAfterColumnsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder getAfterColumnsOrBuilder( int index) { if (afterColumnsBuilder_ == null) { return afterColumns_.get(index); } else { @@ -7292,13 +7945,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public java.util.List + public java.util.List getAfterColumnsOrBuilderList() { if (afterColumnsBuilder_ != null) { return afterColumnsBuilder_.getMessageOrBuilderList(); @@ -7307,45 +7960,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public Column.Builder addAfterColumnsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addAfterColumnsBuilder() { return getAfterColumnsFieldBuilder().addBuilder( - Column.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public Column.Builder addAfterColumnsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder addAfterColumnsBuilder( int index) { return getAfterColumnsFieldBuilder().addBuilder( - index, Column.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; - * *
              ** 字段信息,增量数据(修改后,新增后)  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Column afterColumns = 2; */ - public java.util.List + public java.util.List getAfterColumnsBuilderList() { return getAfterColumnsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Column, Column.Builder, ColumnOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Column, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder, com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder> getAfterColumnsFieldBuilder() { if (afterColumnsBuilder_ == null) { - afterColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Column, Column.Builder, ColumnOrBuilder>( + afterColumnsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Column, com.alibaba.otter.canal.protocol.CanalEntry.Column.Builder, com.alibaba.otter.canal.protocol.CanalEntry.ColumnOrBuilder>( afterColumns_, ((bitField0_ & 0x00000002) == 0x00000002), getParentForChildren(), @@ -7355,26 +8008,26 @@ public final class CanalEntry { return afterColumnsBuilder_; } - private java.util.List props_ = + private java.util.List props_ = java.util.Collections.emptyList(); private void ensurePropsIsMutable() { if (!((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(props_); + props_ = new java.util.ArrayList(props_); bitField0_ |= 0x00000004; } } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> propsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { if (propsBuilder_ == null) { return java.util.Collections.unmodifiableList(props_); } else { @@ -7382,11 +8035,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public int getPropsCount() { if (propsBuilder_ == null) { @@ -7396,13 +8049,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -7410,14 +8063,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder setProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -7431,14 +8084,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder setProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.set(index, builderForValue.build()); @@ -7449,13 +8102,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Builder addProps(Pair value) { + public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -7469,14 +8122,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -7490,14 +8143,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - Pair.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(builderForValue.build()); @@ -7508,14 +8161,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(index, builderForValue.build()); @@ -7526,14 +8179,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addAllProps( - Iterable values) { + java.lang.Iterable values) { if (propsBuilder_ == null) { ensurePropsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -7545,11 +8198,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder clearProps() { if (propsBuilder_ == null) { @@ -7562,11 +8215,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder removeProps(int index) { if (propsBuilder_ == null) { @@ -7579,24 +8232,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder getPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder( int index) { return getPropsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -7604,13 +8257,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { if (propsBuilder_ != null) { return propsBuilder_.getMessageOrBuilderList(); @@ -7619,45 +8272,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder addPropsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { return getPropsFieldBuilder().addBuilder( - Pair.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder addPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder( int index) { return getPropsFieldBuilder().addBuilder( - index, Pair.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsBuilderList() { return getPropsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> getPropsFieldBuilder() { if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder>( + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder>( props_, ((bitField0_ & 0x00000004) == 0x00000004), getParentForChildren(), @@ -7666,16 +8319,57 @@ public final class CanalEntry { } return propsBuilder_; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.RowData) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowData) + private static final com.alibaba.otter.canal.protocol.CanalEntry.RowData DEFAULT_INSTANCE; static { - defaultInstance = new RowData(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.RowData(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.RowData getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public RowData parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new RowData(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowData getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowData) } public interface RowChangeOrBuilder extends @@ -7683,232 +8377,235 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * optional int64 tableId = 1; - * *
            **tableId,由数据库产生*
            * 
      + * + * optional int64 tableId = 1; */ boolean hasTableId(); /** - * optional int64 tableId = 1; - * *
            **tableId,由数据库产生*
            * 
      + * + * optional int64 tableId = 1; */ long getTableId(); /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ boolean hasEventType(); /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ - EventType getEventType(); + com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType(); /** - * optional bool isDdl = 10 [default = false]; - * *
            ** 标识是否是ddl语句  *
            * 
      + * + * optional bool isDdl = 10 [default = false]; */ boolean hasIsDdl(); /** - * optional bool isDdl = 10 [default = false]; - * *
            ** 标识是否是ddl语句  *
            * 
      + * + * optional bool isDdl = 10 [default = false]; */ boolean getIsDdl(); /** - * optional string sql = 11; - * *
            ** ddl/query的sql语句  *
            * 
      + * + * optional string sql = 11; */ boolean hasSql(); /** - * optional string sql = 11; - * *
            ** ddl/query的sql语句  *
            * 
      + * + * optional string sql = 11; */ - String getSql(); + java.lang.String getSql(); /** - * optional string sql = 11; - * *
            ** ddl/query的sql语句  *
            * 
      + * + * optional string sql = 11; */ com.google.protobuf.ByteString getSqlBytes(); /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - java.util.List + java.util.List getRowDatasList(); /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - RowData getRowDatas(int index); + com.alibaba.otter.canal.protocol.CanalEntry.RowData getRowDatas(int index); /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ int getRowDatasCount(); /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - java.util.List + java.util.List getRowDatasOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - RowDataOrBuilder getRowDatasOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder getRowDatasOrBuilder( + int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - java.util.List + java.util.List getPropsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - Pair getProps(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ int getPropsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - java.util.List + java.util.List getPropsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - PairOrBuilder getPropsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( + int index); /** - * optional string ddlSchemaName = 14; - * *
            ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
            * 
      + * + * optional string ddlSchemaName = 14; */ boolean hasDdlSchemaName(); /** - * optional string ddlSchemaName = 14; - * *
            ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
            * 
      + * + * optional string ddlSchemaName = 14; */ - String getDdlSchemaName(); + java.lang.String getDdlSchemaName(); /** - * optional string ddlSchemaName = 14; - * *
            ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
            * 
      + * + * optional string ddlSchemaName = 14; */ com.google.protobuf.ByteString getDdlSchemaNameBytes(); } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.RowChange} - * *
          **message row 每行变更数据的数据结构*
          * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.RowChange} */ - public static final class RowChange extends - com.google.protobuf.GeneratedMessage implements + public static final class RowChange extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.RowChange) RowChangeOrBuilder { + private static final long serialVersionUID = 0L; // Use RowChange.newBuilder() to construct. - private RowChange(com.google.protobuf.GeneratedMessage.Builder builder) { + private RowChange(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private RowChange(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final RowChange defaultInstance; - public static RowChange getDefaultInstance() { - return defaultInstance; + private RowChange() { + tableId_ = 0L; + eventType_ = 2; + isDdl_ = false; + sql_ = ""; + rowDatas_ = java.util.Collections.emptyList(); + props_ = java.util.Collections.emptyList(); + ddlSchemaName_ = ""; } - public RowChange getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private RowChange( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -7920,13 +8617,6 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 8: { bitField0_ |= 0x00000001; tableId_ = input.readInt64(); @@ -7934,12 +8624,13 @@ public final class CanalEntry { } case 16: { int rawValue = input.readEnum(); - EventType value = EventType.valueOf(rawValue); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType value = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(rawValue); if (value == null) { unknownFields.mergeVarintField(2, rawValue); } else { bitField0_ |= 0x00000002; - eventType_ = value; + eventType_ = rawValue; } break; } @@ -7956,18 +8647,20 @@ public final class CanalEntry { } case 98: { if (!((mutable_bitField0_ & 0x00000010) == 0x00000010)) { - rowDatas_ = new java.util.ArrayList(); + rowDatas_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000010; } - rowDatas_.add(input.readMessage(RowData.PARSER, extensionRegistry)); + rowDatas_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.RowData.PARSER, extensionRegistry)); break; } case 106: { if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) { - props_ = new java.util.ArrayList(); + props_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000020; } - props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + props_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); break; } case 114: { @@ -7976,13 +8669,20 @@ public final class CanalEntry { ddlSchemaName_ = bs; break; } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000010) == 0x00000010)) { rowDatas_ = java.util.Collections.unmodifiableList(rowDatas_); @@ -7996,128 +8696,116 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable .ensureFieldAccessorsInitialized( - RowChange.class, Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public RowChange parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new RowChange(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.RowChange.class, com.alibaba.otter.canal.protocol.CanalEntry.RowChange.Builder.class); } private int bitField0_; public static final int TABLEID_FIELD_NUMBER = 1; private long tableId_; /** - * optional int64 tableId = 1; - * *
            **tableId,由数据库产生*
            * 
      + * + * optional int64 tableId = 1; */ public boolean hasTableId() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int64 tableId = 1; - * *
            **tableId,由数据库产生*
            * 
      + * + * optional int64 tableId = 1; */ public long getTableId() { return tableId_; } public static final int EVENTTYPE_FIELD_NUMBER = 2; - private EventType eventType_; + private int eventType_; /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ public boolean hasEventType() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
            **数据变更类型*
            * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ - public EventType getEventType() { - return eventType_; + public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; } public static final int ISDDL_FIELD_NUMBER = 10; private boolean isDdl_; /** - * optional bool isDdl = 10 [default = false]; - * *
            ** 标识是否是ddl语句  *
            * 
      + * + * optional bool isDdl = 10 [default = false]; */ public boolean hasIsDdl() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional bool isDdl = 10 [default = false]; - * *
            ** 标识是否是ddl语句  *
            * 
      + * + * optional bool isDdl = 10 [default = false]; */ public boolean getIsDdl() { return isDdl_; } public static final int SQL_FIELD_NUMBER = 11; - private Object sql_; + private volatile java.lang.Object sql_; /** - * optional string sql = 11; - * *
            ** ddl/query的sql语句  *
            * 
      + * + * optional string sql = 11; */ public boolean hasSql() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional string sql = 11; - * *
            ** ddl/query的sql语句  *
            * 
      + * + * optional string sql = 11; */ - public String getSql() { - Object ref = sql_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getSql() { + java.lang.Object ref = sql_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { sql_ = s; } @@ -8125,19 +8813,19 @@ public final class CanalEntry { } } /** - * optional string sql = 11; - * *
            ** ddl/query的sql语句  *
            * 
      + * + * optional string sql = 11; */ public com.google.protobuf.ByteString getSqlBytes() { - Object ref = sql_; - if (ref instanceof String) { + java.lang.Object ref = sql_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); sql_ = b; return b; } else { @@ -8146,142 +8834,142 @@ public final class CanalEntry { } public static final int ROWDATAS_FIELD_NUMBER = 12; - private java.util.List rowDatas_; + private java.util.List rowDatas_; /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public java.util.List getRowDatasList() { + public java.util.List getRowDatasList() { return rowDatas_; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public java.util.List + public java.util.List getRowDatasOrBuilderList() { return rowDatas_; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public int getRowDatasCount() { return rowDatas_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public RowData getRowDatas(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.RowData getRowDatas(int index) { return rowDatas_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
            ** 一次数据库变更可能存在多行  *
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public RowDataOrBuilder getRowDatasOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder getRowDatasOrBuilder( int index) { return rowDatas_.get(index); } public static final int PROPS_FIELD_NUMBER = 13; - private java.util.List props_; + private java.util.List props_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public int getPropsCount() { return props_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { return props_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { return props_.get(index); } public static final int DDLSCHEMANAME_FIELD_NUMBER = 14; - private Object ddlSchemaName_; + private volatile java.lang.Object ddlSchemaName_; /** - * optional string ddlSchemaName = 14; - * *
            ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
            * 
      + * + * optional string ddlSchemaName = 14; */ public boolean hasDdlSchemaName() { return ((bitField0_ & 0x00000010) == 0x00000010); } /** - * optional string ddlSchemaName = 14; - * *
            ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
            * 
      + * + * optional string ddlSchemaName = 14; */ - public String getDdlSchemaName() { - Object ref = ddlSchemaName_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getDdlSchemaName() { + java.lang.Object ref = ddlSchemaName_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { ddlSchemaName_ = s; } @@ -8289,19 +8977,19 @@ public final class CanalEntry { } } /** - * optional string ddlSchemaName = 14; - * *
            ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
            * 
      + * + * optional string ddlSchemaName = 14; */ public com.google.protobuf.ByteString getDdlSchemaNameBytes() { - Object ref = ddlSchemaName_; - if (ref instanceof String) { + java.lang.Object ref = ddlSchemaName_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); ddlSchemaName_ = b; return b; } else { @@ -8309,16 +8997,8 @@ public final class CanalEntry { } } - private void initFields() { - tableId_ = 0L; - eventType_ = EventType.UPDATE; - isDdl_ = false; - sql_ = ""; - rowDatas_ = java.util.Collections.emptyList(); - props_ = java.util.Collections.emptyList(); - ddlSchemaName_ = ""; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -8328,20 +9008,20 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeInt64(1, tableId_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, eventType_.getNumber()); + output.writeEnum(2, eventType_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeBool(10, isDdl_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBytes(11, getSqlBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 11, sql_); } for (int i = 0; i < rowDatas_.size(); i++) { output.writeMessage(12, rowDatas_.get(i)); @@ -8350,14 +9030,14 @@ public final class CanalEntry { output.writeMessage(13, props_.get(i)); } if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBytes(14, getDdlSchemaNameBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 14, ddlSchemaName_); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; @@ -8367,15 +9047,14 @@ public final class CanalEntry { } if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(2, eventType_.getNumber()); + .computeEnumSize(2, eventType_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream .computeBoolSize(10, isDdl_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(11, getSqlBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(11, sql_); } for (int i = 0; i < rowDatas_.size(); i++) { size += com.google.protobuf.CodedOutputStream @@ -8386,108 +9065,210 @@ public final class CanalEntry { .computeMessageSize(13, props_.get(i)); } if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(14, getDdlSchemaNameBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(14, ddlSchemaName_); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.RowChange)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.RowChange other = (com.alibaba.otter.canal.protocol.CanalEntry.RowChange) obj; + + boolean result = true; + result = result && (hasTableId() == other.hasTableId()); + if (hasTableId()) { + result = result && (getTableId() + == other.getTableId()); + } + result = result && (hasEventType() == other.hasEventType()); + if (hasEventType()) { + result = result && eventType_ == other.eventType_; + } + result = result && (hasIsDdl() == other.hasIsDdl()); + if (hasIsDdl()) { + result = result && (getIsDdl() + == other.getIsDdl()); + } + result = result && (hasSql() == other.hasSql()); + if (hasSql()) { + result = result && getSql() + .equals(other.getSql()); + } + result = result && getRowDatasList() + .equals(other.getRowDatasList()); + result = result && getPropsList() + .equals(other.getPropsList()); + result = result && (hasDdlSchemaName() == other.hasDdlSchemaName()); + if (hasDdlSchemaName()) { + result = result && getDdlSchemaName() + .equals(other.getDdlSchemaName()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static RowChange parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasTableId()) { + hash = (37 * hash) + TABLEID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getTableId()); + } + if (hasEventType()) { + hash = (37 * hash) + EVENTTYPE_FIELD_NUMBER; + hash = (53 * hash) + eventType_; + } + if (hasIsDdl()) { + hash = (37 * hash) + ISDDL_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getIsDdl()); + } + if (hasSql()) { + hash = (37 * hash) + SQL_FIELD_NUMBER; + hash = (53 * hash) + getSql().hashCode(); + } + if (getRowDatasCount() > 0) { + hash = (37 * hash) + ROWDATAS_FIELD_NUMBER; + hash = (53 * hash) + getRowDatasList().hashCode(); + } + if (getPropsCount() > 0) { + hash = (37 * hash) + PROPS_FIELD_NUMBER; + hash = (53 * hash) + getPropsList().hashCode(); + } + if (hasDdlSchemaName()) { + hash = (37 * hash) + DDLSCHEMANAME_FIELD_NUMBER; + hash = (53 * hash) + getDdlSchemaName().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static RowChange parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static RowChange parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static RowChange parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static RowChange parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static RowChange parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static RowChange parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static RowChange parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static RowChange parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static RowChange parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(RowChange prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.RowChange prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.RowChange} - * *
            **message row 每行变更数据的数据结构*
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.RowChange} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.RowChange) - RowChangeOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.RowChangeOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable .ensureFieldAccessorsInitialized( - RowChange.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.RowChange.class, com.alibaba.otter.canal.protocol.CanalEntry.RowChange.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.RowChange.newBuilder() @@ -8496,25 +9277,23 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { getRowDatasFieldBuilder(); getPropsFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); tableId_ = 0L; bitField0_ = (bitField0_ & ~0x00000001); - eventType_ = EventType.UPDATE; + eventType_ = 2; bitField0_ = (bitField0_ & ~0x00000002); isDdl_ = false; bitField0_ = (bitField0_ & ~0x00000004); @@ -8537,29 +9316,29 @@ public final class CanalEntry { return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; } - public RowChange getDefaultInstanceForType() { - return RowChange.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowChange getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.RowChange.getDefaultInstance(); } - public RowChange build() { - RowChange result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowChange build() { + com.alibaba.otter.canal.protocol.CanalEntry.RowChange result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public RowChange buildPartial() { - RowChange result = new RowChange(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowChange buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.RowChange result = new com.alibaba.otter.canal.protocol.CanalEntry.RowChange(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -8605,17 +9384,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof RowChange) { - return mergeFrom((RowChange)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.RowChange) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.RowChange)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(RowChange other) { - if (other == RowChange.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.RowChange other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.RowChange.getDefaultInstance()) return this; if (other.hasTableId()) { setTableId(other.getTableId()); } @@ -8649,7 +9461,7 @@ public final class CanalEntry { rowDatas_ = other.rowDatas_; bitField0_ = (bitField0_ & ~0x00000010); rowDatasBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getRowDatasFieldBuilder() : null; } else { rowDatasBuilder_.addAllMessages(other.rowDatas_); @@ -8675,7 +9487,7 @@ public final class CanalEntry { props_ = other.props_; bitField0_ = (bitField0_ & ~0x00000020); propsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; } else { propsBuilder_.addAllMessages(other.props_); @@ -8687,24 +9499,27 @@ public final class CanalEntry { ddlSchemaName_ = other.ddlSchemaName_; onChanged(); } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - RowChange parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.RowChange parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (RowChange) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.RowChange) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -8716,31 +9531,31 @@ public final class CanalEntry { private long tableId_ ; /** - * optional int64 tableId = 1; - * *
              **tableId,由数据库产生*
              * 
      + * + * optional int64 tableId = 1; */ public boolean hasTableId() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int64 tableId = 1; - * *
              **tableId,由数据库产生*
              * 
      + * + * optional int64 tableId = 1; */ public long getTableId() { return tableId_; } /** - * optional int64 tableId = 1; - * *
              **tableId,由数据库产生*
              * 
      + * + * optional int64 tableId = 1; */ public Builder setTableId(long value) { bitField0_ |= 0x00000001; @@ -8749,11 +9564,11 @@ public final class CanalEntry { return this; } /** - * optional int64 tableId = 1; - * *
              **tableId,由数据库产生*
              * 
      + * + * optional int64 tableId = 1; */ public Builder clearTableId() { bitField0_ = (bitField0_ & ~0x00000001); @@ -8762,84 +9577,86 @@ public final class CanalEntry { return this; } - private EventType eventType_ = EventType.UPDATE; + private int eventType_ = 2; /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ public boolean hasEventType() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ - public EventType getEventType() { - return eventType_; + public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ - public Builder setEventType(EventType value) { + public Builder setEventType(com.alibaba.otter.canal.protocol.CanalEntry.EventType value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000002; - eventType_ = value; + eventType_ = value.getNumber(); onChanged(); return this; } /** - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; - * *
              **数据变更类型*
              * 
      + * + * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; */ public Builder clearEventType() { bitField0_ = (bitField0_ & ~0x00000002); - eventType_ = EventType.UPDATE; + eventType_ = 2; onChanged(); return this; } private boolean isDdl_ ; /** - * optional bool isDdl = 10 [default = false]; - * *
              ** 标识是否是ddl语句  *
              * 
      + * + * optional bool isDdl = 10 [default = false]; */ public boolean hasIsDdl() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional bool isDdl = 10 [default = false]; - * *
              ** 标识是否是ddl语句  *
              * 
      + * + * optional bool isDdl = 10 [default = false]; */ public boolean getIsDdl() { return isDdl_; } /** - * optional bool isDdl = 10 [default = false]; - * *
              ** 标识是否是ddl语句  *
              * 
      + * + * optional bool isDdl = 10 [default = false]; */ public Builder setIsDdl(boolean value) { bitField0_ |= 0x00000004; @@ -8848,11 +9665,11 @@ public final class CanalEntry { return this; } /** - * optional bool isDdl = 10 [default = false]; - * *
              ** 标识是否是ddl语句  *
              * 
      + * + * optional bool isDdl = 10 [default = false]; */ public Builder clearIsDdl() { bitField0_ = (bitField0_ & ~0x00000004); @@ -8861,52 +9678,52 @@ public final class CanalEntry { return this; } - private Object sql_ = ""; + private java.lang.Object sql_ = ""; /** - * optional string sql = 11; - * *
              ** ddl/query的sql语句  *
              * 
      + * + * optional string sql = 11; */ public boolean hasSql() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional string sql = 11; - * *
              ** ddl/query的sql语句  *
              * 
      + * + * optional string sql = 11; */ - public String getSql() { - Object ref = sql_; - if (!(ref instanceof String)) { + public java.lang.String getSql() { + java.lang.Object ref = sql_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { sql_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string sql = 11; - * *
              ** ddl/query的sql语句  *
              * 
      + * + * optional string sql = 11; */ public com.google.protobuf.ByteString getSqlBytes() { - Object ref = sql_; + java.lang.Object ref = sql_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); sql_ = b; return b; } else { @@ -8914,14 +9731,14 @@ public final class CanalEntry { } } /** - * optional string sql = 11; - * *
              ** ddl/query的sql语句  *
              * 
      + * + * optional string sql = 11; */ public Builder setSql( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -8931,11 +9748,11 @@ public final class CanalEntry { return this; } /** - * optional string sql = 11; - * *
              ** ddl/query的sql语句  *
              * 
      + * + * optional string sql = 11; */ public Builder clearSql() { bitField0_ = (bitField0_ & ~0x00000008); @@ -8944,11 +9761,11 @@ public final class CanalEntry { return this; } /** - * optional string sql = 11; - * *
              ** ddl/query的sql语句  *
              * 
      + * + * optional string sql = 11; */ public Builder setSqlBytes( com.google.protobuf.ByteString value) { @@ -8961,26 +9778,26 @@ public final class CanalEntry { return this; } - private java.util.List rowDatas_ = + private java.util.List rowDatas_ = java.util.Collections.emptyList(); private void ensureRowDatasIsMutable() { if (!((bitField0_ & 0x00000010) == 0x00000010)) { - rowDatas_ = new java.util.ArrayList(rowDatas_); + rowDatas_ = new java.util.ArrayList(rowDatas_); bitField0_ |= 0x00000010; } } - private com.google.protobuf.RepeatedFieldBuilder< - RowData, RowData.Builder, RowDataOrBuilder> rowDatasBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.RowData, com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder, com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder> rowDatasBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public java.util.List getRowDatasList() { + public java.util.List getRowDatasList() { if (rowDatasBuilder_ == null) { return java.util.Collections.unmodifiableList(rowDatas_); } else { @@ -8988,11 +9805,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public int getRowDatasCount() { if (rowDatasBuilder_ == null) { @@ -9002,13 +9819,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public RowData getRowDatas(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.RowData getRowDatas(int index) { if (rowDatasBuilder_ == null) { return rowDatas_.get(index); } else { @@ -9016,14 +9833,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder setRowDatas( - int index, RowData value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.RowData value) { if (rowDatasBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -9037,14 +9854,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder setRowDatas( - int index, RowData.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder builderForValue) { if (rowDatasBuilder_ == null) { ensureRowDatasIsMutable(); rowDatas_.set(index, builderForValue.build()); @@ -9055,13 +9872,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public Builder addRowDatas(RowData value) { + public Builder addRowDatas(com.alibaba.otter.canal.protocol.CanalEntry.RowData value) { if (rowDatasBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -9075,14 +9892,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder addRowDatas( - int index, RowData value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.RowData value) { if (rowDatasBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -9096,14 +9913,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder addRowDatas( - RowData.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder builderForValue) { if (rowDatasBuilder_ == null) { ensureRowDatasIsMutable(); rowDatas_.add(builderForValue.build()); @@ -9114,14 +9931,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder addRowDatas( - int index, RowData.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder builderForValue) { if (rowDatasBuilder_ == null) { ensureRowDatasIsMutable(); rowDatas_.add(index, builderForValue.build()); @@ -9132,14 +9949,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder addAllRowDatas( - Iterable values) { + java.lang.Iterable values) { if (rowDatasBuilder_ == null) { ensureRowDatasIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -9151,11 +9968,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder clearRowDatas() { if (rowDatasBuilder_ == null) { @@ -9168,11 +9985,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ public Builder removeRowDatas(int index) { if (rowDatasBuilder_ == null) { @@ -9185,24 +10002,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public RowData.Builder getRowDatasBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder getRowDatasBuilder( int index) { return getRowDatasFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public RowDataOrBuilder getRowDatasOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder getRowDatasOrBuilder( int index) { if (rowDatasBuilder_ == null) { return rowDatas_.get(index); } else { @@ -9210,13 +10027,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public java.util.List + public java.util.List getRowDatasOrBuilderList() { if (rowDatasBuilder_ != null) { return rowDatasBuilder_.getMessageOrBuilderList(); @@ -9225,45 +10042,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public RowData.Builder addRowDatasBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder addRowDatasBuilder() { return getRowDatasFieldBuilder().addBuilder( - RowData.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public RowData.Builder addRowDatasBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder addRowDatasBuilder( int index) { return getRowDatasFieldBuilder().addBuilder( - index, RowData.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.RowData.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; - * *
              ** 一次数据库变更可能存在多行  *
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.RowData rowDatas = 12; */ - public java.util.List + public java.util.List getRowDatasBuilderList() { return getRowDatasFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - RowData, RowData.Builder, RowDataOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.RowData, com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder, com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder> getRowDatasFieldBuilder() { if (rowDatasBuilder_ == null) { - rowDatasBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - RowData, RowData.Builder, RowDataOrBuilder>( + rowDatasBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.RowData, com.alibaba.otter.canal.protocol.CanalEntry.RowData.Builder, com.alibaba.otter.canal.protocol.CanalEntry.RowDataOrBuilder>( rowDatas_, ((bitField0_ & 0x00000010) == 0x00000010), getParentForChildren(), @@ -9273,26 +10090,26 @@ public final class CanalEntry { return rowDatasBuilder_; } - private java.util.List props_ = + private java.util.List props_ = java.util.Collections.emptyList(); private void ensurePropsIsMutable() { if (!((bitField0_ & 0x00000020) == 0x00000020)) { - props_ = new java.util.ArrayList(props_); + props_ = new java.util.ArrayList(props_); bitField0_ |= 0x00000020; } } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> propsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { if (propsBuilder_ == null) { return java.util.Collections.unmodifiableList(props_); } else { @@ -9300,11 +10117,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public int getPropsCount() { if (propsBuilder_ == null) { @@ -9314,13 +10131,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -9328,14 +10145,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder setProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -9349,14 +10166,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder setProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.set(index, builderForValue.build()); @@ -9367,13 +10184,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public Builder addProps(Pair value) { + public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -9387,14 +10204,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder addProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -9408,14 +10225,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder addProps( - Pair.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(builderForValue.build()); @@ -9426,14 +10243,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder addProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(index, builderForValue.build()); @@ -9444,14 +10261,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder addAllProps( - Iterable values) { + java.lang.Iterable values) { if (propsBuilder_ == null) { ensurePropsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -9463,11 +10280,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder clearProps() { if (propsBuilder_ == null) { @@ -9480,11 +10297,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ public Builder removeProps(int index) { if (propsBuilder_ == null) { @@ -9497,24 +10314,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public Pair.Builder getPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder( int index) { return getPropsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -9522,13 +10339,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { if (propsBuilder_ != null) { return propsBuilder_.getMessageOrBuilderList(); @@ -9537,45 +10354,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public Pair.Builder addPropsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { return getPropsFieldBuilder().addBuilder( - Pair.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public Pair.Builder addPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder( int index) { return getPropsFieldBuilder().addBuilder( - index, Pair.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 13; */ - public java.util.List + public java.util.List getPropsBuilderList() { return getPropsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> getPropsFieldBuilder() { if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder>( + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder>( props_, ((bitField0_ & 0x00000020) == 0x00000020), getParentForChildren(), @@ -9585,52 +10402,52 @@ public final class CanalEntry { return propsBuilder_; } - private Object ddlSchemaName_ = ""; + private java.lang.Object ddlSchemaName_ = ""; /** - * optional string ddlSchemaName = 14; - * *
              ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
              * 
      + * + * optional string ddlSchemaName = 14; */ public boolean hasDdlSchemaName() { return ((bitField0_ & 0x00000040) == 0x00000040); } /** - * optional string ddlSchemaName = 14; - * *
              ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
              * 
      + * + * optional string ddlSchemaName = 14; */ - public String getDdlSchemaName() { - Object ref = ddlSchemaName_; - if (!(ref instanceof String)) { + public java.lang.String getDdlSchemaName() { + java.lang.Object ref = ddlSchemaName_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { ddlSchemaName_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string ddlSchemaName = 14; - * *
              ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
              * 
      + * + * optional string ddlSchemaName = 14; */ public com.google.protobuf.ByteString getDdlSchemaNameBytes() { - Object ref = ddlSchemaName_; + java.lang.Object ref = ddlSchemaName_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); ddlSchemaName_ = b; return b; } else { @@ -9638,14 +10455,14 @@ public final class CanalEntry { } } /** - * optional string ddlSchemaName = 14; - * *
              ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
              * 
      + * + * optional string ddlSchemaName = 14; */ public Builder setDdlSchemaName( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -9655,11 +10472,11 @@ public final class CanalEntry { return this; } /** - * optional string ddlSchemaName = 14; - * *
              ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
              * 
      + * + * optional string ddlSchemaName = 14; */ public Builder clearDdlSchemaName() { bitField0_ = (bitField0_ & ~0x00000040); @@ -9668,11 +10485,11 @@ public final class CanalEntry { return this; } /** - * optional string ddlSchemaName = 14; - * *
              ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
              * 
      + * + * optional string ddlSchemaName = 14; */ public Builder setDdlSchemaNameBytes( com.google.protobuf.ByteString value) { @@ -9684,16 +10501,57 @@ public final class CanalEntry { onChanged(); return this; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.RowChange) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowChange) + private static final com.alibaba.otter.canal.protocol.CanalEntry.RowChange DEFAULT_INSTANCE; static { - defaultInstance = new RowChange(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.RowChange(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.RowChange getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public RowChange parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new RowChange(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.RowChange getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.RowChange) } public interface TransactionBeginOrBuilder extends @@ -9701,146 +10559,145 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ boolean hasExecuteTime(); /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ long getExecuteTime(); /** - * optional string transactionId = 2; - * *
            **已废弃,Begin里不提供事务id*
            * 
      + * + * optional string transactionId = 2; */ boolean hasTransactionId(); /** - * optional string transactionId = 2; - * *
            **已废弃,Begin里不提供事务id*
            * 
      + * + * optional string transactionId = 2; */ - String getTransactionId(); + java.lang.String getTransactionId(); /** - * optional string transactionId = 2; - * *
            **已废弃,Begin里不提供事务id*
            * 
      + * + * optional string transactionId = 2; */ com.google.protobuf.ByteString getTransactionIdBytes(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - java.util.List + java.util.List getPropsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - Pair getProps(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ int getPropsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - java.util.List + java.util.List getPropsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - PairOrBuilder getPropsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( + int index); /** - * optional int64 threadId = 4; - * *
            **执行的thread Id*
            * 
      + * + * optional int64 threadId = 4; */ boolean hasThreadId(); /** - * optional int64 threadId = 4; - * *
            **执行的thread Id*
            * 
      + * + * optional int64 threadId = 4; */ long getThreadId(); } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionBegin} - * *
          **开始事务的一些信息*
          * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionBegin} */ - public static final class TransactionBegin extends - com.google.protobuf.GeneratedMessage implements + public static final class TransactionBegin extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.TransactionBegin) TransactionBeginOrBuilder { + private static final long serialVersionUID = 0L; // Use TransactionBegin.newBuilder() to construct. - private TransactionBegin(com.google.protobuf.GeneratedMessage.Builder builder) { + private TransactionBegin(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private TransactionBegin(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final TransactionBegin defaultInstance; - public static TransactionBegin getDefaultInstance() { - return defaultInstance; + private TransactionBegin() { + executeTime_ = 0L; + transactionId_ = ""; + props_ = java.util.Collections.emptyList(); + threadId_ = 0L; } - public TransactionBegin getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private TransactionBegin( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -9852,13 +10709,6 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 8: { bitField0_ |= 0x00000001; executeTime_ = input.readInt64(); @@ -9872,10 +10722,11 @@ public final class CanalEntry { } case 26: { if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(); + props_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000004; } - props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + props_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); break; } case 32: { @@ -9883,13 +10734,20 @@ public final class CanalEntry { threadId_ = input.readInt64(); break; } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { props_ = java.util.Collections.unmodifiableList(props_); @@ -9900,82 +10758,68 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable .ensureFieldAccessorsInitialized( - TransactionBegin.class, Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public TransactionBegin parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new TransactionBegin(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.class, com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.Builder.class); } private int bitField0_; public static final int EXECUTETIME_FIELD_NUMBER = 1; private long executeTime_; /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ public boolean hasExecuteTime() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; } public static final int TRANSACTIONID_FIELD_NUMBER = 2; - private Object transactionId_; + private volatile java.lang.Object transactionId_; /** - * optional string transactionId = 2; - * *
            **已废弃,Begin里不提供事务id*
            * 
      + * + * optional string transactionId = 2; */ public boolean hasTransactionId() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional string transactionId = 2; - * *
            **已废弃,Begin里不提供事务id*
            * 
      + * + * optional string transactionId = 2; */ - public String getTransactionId() { - Object ref = transactionId_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getTransactionId() { + java.lang.Object ref = transactionId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { transactionId_ = s; } @@ -9983,19 +10827,19 @@ public final class CanalEntry { } } /** - * optional string transactionId = 2; - * *
            **已废弃,Begin里不提供事务id*
            * 
      + * + * optional string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { - Object ref = transactionId_; - if (ref instanceof String) { + java.lang.Object ref = transactionId_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); transactionId_ = b; return b; } else { @@ -10004,56 +10848,56 @@ public final class CanalEntry { } public static final int PROPS_FIELD_NUMBER = 3; - private java.util.List props_; + private java.util.List props_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public int getPropsCount() { return props_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { return props_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { return props_.get(index); } @@ -10061,33 +10905,28 @@ public final class CanalEntry { public static final int THREADID_FIELD_NUMBER = 4; private long threadId_; /** - * optional int64 threadId = 4; - * *
            **执行的thread Id*
            * 
      + * + * optional int64 threadId = 4; */ public boolean hasThreadId() { return ((bitField0_ & 0x00000004) == 0x00000004); } /** - * optional int64 threadId = 4; - * *
            **执行的thread Id*
            * 
      + * + * optional int64 threadId = 4; */ public long getThreadId() { return threadId_; } - private void initFields() { - executeTime_ = 0L; - transactionId_ = ""; - props_ = java.util.Collections.emptyList(); - threadId_ = 0L; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -10097,14 +10936,14 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeInt64(1, executeTime_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getTransactionIdBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, transactionId_); } for (int i = 0; i < props_.size(); i++) { output.writeMessage(3, props_.get(i)); @@ -10112,12 +10951,12 @@ public final class CanalEntry { if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeInt64(4, threadId_); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; @@ -10126,8 +10965,7 @@ public final class CanalEntry { .computeInt64Size(1, executeTime_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getTransactionIdBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, transactionId_); } for (int i = 0; i < props_.size(); i++) { size += com.google.protobuf.CodedOutputStream @@ -10137,105 +10975,185 @@ public final class CanalEntry { size += com.google.protobuf.CodedOutputStream .computeInt64Size(4, threadId_); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin other = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin) obj; + + boolean result = true; + result = result && (hasExecuteTime() == other.hasExecuteTime()); + if (hasExecuteTime()) { + result = result && (getExecuteTime() + == other.getExecuteTime()); + } + result = result && (hasTransactionId() == other.hasTransactionId()); + if (hasTransactionId()) { + result = result && getTransactionId() + .equals(other.getTransactionId()); + } + result = result && getPropsList() + .equals(other.getPropsList()); + result = result && (hasThreadId() == other.hasThreadId()); + if (hasThreadId()) { + result = result && (getThreadId() + == other.getThreadId()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static TransactionBegin parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasExecuteTime()) { + hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getExecuteTime()); + } + if (hasTransactionId()) { + hash = (37 * hash) + TRANSACTIONID_FIELD_NUMBER; + hash = (53 * hash) + getTransactionId().hashCode(); + } + if (getPropsCount() > 0) { + hash = (37 * hash) + PROPS_FIELD_NUMBER; + hash = (53 * hash) + getPropsList().hashCode(); + } + if (hasThreadId()) { + hash = (37 * hash) + THREADID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getThreadId()); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static TransactionBegin parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static TransactionBegin parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static TransactionBegin parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static TransactionBegin parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static TransactionBegin parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static TransactionBegin parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static TransactionBegin parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static TransactionBegin parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static TransactionBegin parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(TransactionBegin prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionBegin} - * *
            **开始事务的一些信息*
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionBegin} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.TransactionBegin) - TransactionBeginOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.TransactionBeginOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable .ensureFieldAccessorsInitialized( - TransactionBegin.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.class, com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.newBuilder() @@ -10244,19 +11162,17 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { getPropsFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); executeTime_ = 0L; @@ -10274,29 +11190,29 @@ public final class CanalEntry { return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; } - public TransactionBegin getDefaultInstanceForType() { - return TransactionBegin.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.getDefaultInstance(); } - public TransactionBegin build() { - TransactionBegin result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin build() { + com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public TransactionBegin buildPartial() { - TransactionBegin result = new TransactionBegin(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin result = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -10325,17 +11241,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof TransactionBegin) { - return mergeFrom((TransactionBegin)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(TransactionBegin other) { - if (other == TransactionBegin.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.getDefaultInstance()) return this; if (other.hasExecuteTime()) { setExecuteTime(other.getExecuteTime()); } @@ -10363,7 +11312,7 @@ public final class CanalEntry { props_ = other.props_; bitField0_ = (bitField0_ & ~0x00000004); propsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; } else { propsBuilder_.addAllMessages(other.props_); @@ -10373,24 +11322,27 @@ public final class CanalEntry { if (other.hasThreadId()) { setThreadId(other.getThreadId()); } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - TransactionBegin parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (TransactionBegin) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -10402,31 +11354,31 @@ public final class CanalEntry { private long executeTime_ ; /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public boolean hasExecuteTime() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; } /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public Builder setExecuteTime(long value) { bitField0_ |= 0x00000001; @@ -10435,11 +11387,11 @@ public final class CanalEntry { return this; } /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public Builder clearExecuteTime() { bitField0_ = (bitField0_ & ~0x00000001); @@ -10448,52 +11400,52 @@ public final class CanalEntry { return this; } - private Object transactionId_ = ""; + private java.lang.Object transactionId_ = ""; /** - * optional string transactionId = 2; - * *
              **已废弃,Begin里不提供事务id*
              * 
      + * + * optional string transactionId = 2; */ public boolean hasTransactionId() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional string transactionId = 2; - * *
              **已废弃,Begin里不提供事务id*
              * 
      + * + * optional string transactionId = 2; */ - public String getTransactionId() { - Object ref = transactionId_; - if (!(ref instanceof String)) { + public java.lang.String getTransactionId() { + java.lang.Object ref = transactionId_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { transactionId_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string transactionId = 2; - * *
              **已废弃,Begin里不提供事务id*
              * 
      + * + * optional string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { - Object ref = transactionId_; + java.lang.Object ref = transactionId_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); transactionId_ = b; return b; } else { @@ -10501,14 +11453,14 @@ public final class CanalEntry { } } /** - * optional string transactionId = 2; - * *
              **已废弃,Begin里不提供事务id*
              * 
      + * + * optional string transactionId = 2; */ public Builder setTransactionId( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -10518,11 +11470,11 @@ public final class CanalEntry { return this; } /** - * optional string transactionId = 2; - * *
              **已废弃,Begin里不提供事务id*
              * 
      + * + * optional string transactionId = 2; */ public Builder clearTransactionId() { bitField0_ = (bitField0_ & ~0x00000002); @@ -10531,11 +11483,11 @@ public final class CanalEntry { return this; } /** - * optional string transactionId = 2; - * *
              **已废弃,Begin里不提供事务id*
              * 
      + * + * optional string transactionId = 2; */ public Builder setTransactionIdBytes( com.google.protobuf.ByteString value) { @@ -10548,26 +11500,26 @@ public final class CanalEntry { return this; } - private java.util.List props_ = + private java.util.List props_ = java.util.Collections.emptyList(); private void ensurePropsIsMutable() { if (!((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(props_); + props_ = new java.util.ArrayList(props_); bitField0_ |= 0x00000004; } } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> propsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { if (propsBuilder_ == null) { return java.util.Collections.unmodifiableList(props_); } else { @@ -10575,11 +11527,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public int getPropsCount() { if (propsBuilder_ == null) { @@ -10589,13 +11541,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -10603,14 +11555,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder setProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -10624,14 +11576,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder setProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.set(index, builderForValue.build()); @@ -10642,13 +11594,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Builder addProps(Pair value) { + public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -10662,14 +11614,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -10683,14 +11635,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - Pair.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(builderForValue.build()); @@ -10701,14 +11653,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(index, builderForValue.build()); @@ -10719,14 +11671,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addAllProps( - Iterable values) { + java.lang.Iterable values) { if (propsBuilder_ == null) { ensurePropsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -10738,11 +11690,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder clearProps() { if (propsBuilder_ == null) { @@ -10755,11 +11707,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder removeProps(int index) { if (propsBuilder_ == null) { @@ -10772,24 +11724,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder getPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder( int index) { return getPropsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -10797,13 +11749,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { if (propsBuilder_ != null) { return propsBuilder_.getMessageOrBuilderList(); @@ -10812,45 +11764,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder addPropsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { return getPropsFieldBuilder().addBuilder( - Pair.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder addPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder( int index) { return getPropsFieldBuilder().addBuilder( - index, Pair.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsBuilderList() { return getPropsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> getPropsFieldBuilder() { if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder>( + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder>( props_, ((bitField0_ & 0x00000004) == 0x00000004), getParentForChildren(), @@ -10862,31 +11814,31 @@ public final class CanalEntry { private long threadId_ ; /** - * optional int64 threadId = 4; - * *
              **执行的thread Id*
              * 
      + * + * optional int64 threadId = 4; */ public boolean hasThreadId() { return ((bitField0_ & 0x00000008) == 0x00000008); } /** - * optional int64 threadId = 4; - * *
              **执行的thread Id*
              * 
      + * + * optional int64 threadId = 4; */ public long getThreadId() { return threadId_; } /** - * optional int64 threadId = 4; - * *
              **执行的thread Id*
              * 
      + * + * optional int64 threadId = 4; */ public Builder setThreadId(long value) { bitField0_ |= 0x00000008; @@ -10895,11 +11847,11 @@ public final class CanalEntry { return this; } /** - * optional int64 threadId = 4; - * *
              **执行的thread Id*
              * 
      + * + * optional int64 threadId = 4; */ public Builder clearThreadId() { bitField0_ = (bitField0_ & ~0x00000008); @@ -10907,16 +11859,57 @@ public final class CanalEntry { onChanged(); return this; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.TransactionBegin) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionBegin) + private static final com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin DEFAULT_INSTANCE; static { - defaultInstance = new TransactionBegin(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public TransactionBegin parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TransactionBegin(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionBegin) } public interface TransactionEndOrBuilder extends @@ -10924,129 +11917,127 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ boolean hasExecuteTime(); /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ long getExecuteTime(); /** - * optional string transactionId = 2; - * *
            **事务号*
            * 
      + * + * optional string transactionId = 2; */ boolean hasTransactionId(); /** - * optional string transactionId = 2; - * *
            **事务号*
            * 
      + * + * optional string transactionId = 2; */ - String getTransactionId(); + java.lang.String getTransactionId(); /** - * optional string transactionId = 2; - * *
            **事务号*
            * 
      + * + * optional string transactionId = 2; */ com.google.protobuf.ByteString getTransactionIdBytes(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - java.util.List + java.util.List getPropsList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - Pair getProps(int index); + com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ int getPropsCount(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - java.util.List + java.util.List getPropsOrBuilderList(); /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - PairOrBuilder getPropsOrBuilder(int index); + com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( + int index); } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionEnd} - * *
          **结束事务的一些信息*
          * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionEnd} */ - public static final class TransactionEnd extends - com.google.protobuf.GeneratedMessage implements + public static final class TransactionEnd extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.TransactionEnd) TransactionEndOrBuilder { + private static final long serialVersionUID = 0L; // Use TransactionEnd.newBuilder() to construct. - private TransactionEnd(com.google.protobuf.GeneratedMessage.Builder builder) { + private TransactionEnd(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private TransactionEnd(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final TransactionEnd defaultInstance; - public static TransactionEnd getDefaultInstance() { - return defaultInstance; + private TransactionEnd() { + executeTime_ = 0L; + transactionId_ = ""; + props_ = java.util.Collections.emptyList(); } - public TransactionEnd getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private TransactionEnd( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -11058,13 +12049,6 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 8: { bitField0_ |= 0x00000001; executeTime_ = input.readInt64(); @@ -11078,10 +12062,18 @@ public final class CanalEntry { } case 26: { if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(); + props_ = new java.util.ArrayList(); mutable_bitField0_ |= 0x00000004; } - props_.add(input.readMessage(Pair.PARSER, extensionRegistry)); + props_.add( + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } break; } } @@ -11090,7 +12082,7 @@ public final class CanalEntry { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { props_ = java.util.Collections.unmodifiableList(props_); @@ -11101,82 +12093,68 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable .ensureFieldAccessorsInitialized( - TransactionEnd.class, Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public TransactionEnd parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new TransactionEnd(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.class, com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.Builder.class); } private int bitField0_; public static final int EXECUTETIME_FIELD_NUMBER = 1; private long executeTime_; /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ public boolean hasExecuteTime() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int64 executeTime = 1; - * *
            **已废弃,请使用header里的executeTime*
            * 
      + * + * optional int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; } public static final int TRANSACTIONID_FIELD_NUMBER = 2; - private Object transactionId_; + private volatile java.lang.Object transactionId_; /** - * optional string transactionId = 2; - * *
            **事务号*
            * 
      + * + * optional string transactionId = 2; */ public boolean hasTransactionId() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional string transactionId = 2; - * *
            **事务号*
            * 
      + * + * optional string transactionId = 2; */ - public String getTransactionId() { - Object ref = transactionId_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getTransactionId() { + java.lang.Object ref = transactionId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { transactionId_ = s; } @@ -11184,19 +12162,19 @@ public final class CanalEntry { } } /** - * optional string transactionId = 2; - * *
            **事务号*
            * 
      + * + * optional string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { - Object ref = transactionId_; - if (ref instanceof String) { + java.lang.Object ref = transactionId_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); transactionId_ = b; return b; } else { @@ -11205,66 +12183,62 @@ public final class CanalEntry { } public static final int PROPS_FIELD_NUMBER = 3; - private java.util.List props_; + private java.util.List props_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { return props_; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public int getPropsCount() { return props_.size(); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { return props_.get(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
            **预留扩展*
            * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { return props_.get(index); } - private void initFields() { - executeTime_ = 0L; - transactionId_ = ""; - props_ = java.util.Collections.emptyList(); - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -11274,24 +12248,24 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeInt64(1, executeTime_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getTransactionIdBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, transactionId_); } for (int i = 0; i < props_.size(); i++) { output.writeMessage(3, props_.get(i)); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; @@ -11300,112 +12274,181 @@ public final class CanalEntry { .computeInt64Size(1, executeTime_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getTransactionIdBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, transactionId_); } for (int i = 0; i < props_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(3, props_.get(i)); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd other = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd) obj; + + boolean result = true; + result = result && (hasExecuteTime() == other.hasExecuteTime()); + if (hasExecuteTime()) { + result = result && (getExecuteTime() + == other.getExecuteTime()); + } + result = result && (hasTransactionId() == other.hasTransactionId()); + if (hasTransactionId()) { + result = result && getTransactionId() + .equals(other.getTransactionId()); + } + result = result && getPropsList() + .equals(other.getPropsList()); + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static TransactionEnd parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasExecuteTime()) { + hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getExecuteTime()); + } + if (hasTransactionId()) { + hash = (37 * hash) + TRANSACTIONID_FIELD_NUMBER; + hash = (53 * hash) + getTransactionId().hashCode(); + } + if (getPropsCount() > 0) { + hash = (37 * hash) + PROPS_FIELD_NUMBER; + hash = (53 * hash) + getPropsList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static TransactionEnd parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static TransactionEnd parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static TransactionEnd parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static TransactionEnd parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static TransactionEnd parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static TransactionEnd parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static TransactionEnd parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static TransactionEnd parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static TransactionEnd parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(TransactionEnd prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionEnd} - * *
            **结束事务的一些信息*
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.TransactionEnd} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.TransactionEnd) - TransactionEndOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.TransactionEndOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable .ensureFieldAccessorsInitialized( - TransactionEnd.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.class, com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.newBuilder() @@ -11414,19 +12457,17 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { getPropsFieldBuilder(); } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); executeTime_ = 0L; @@ -11442,29 +12483,29 @@ public final class CanalEntry { return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; } - public TransactionEnd getDefaultInstanceForType() { - return TransactionEnd.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.getDefaultInstance(); } - public TransactionEnd build() { - TransactionEnd result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd build() { + com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public TransactionEnd buildPartial() { - TransactionEnd result = new TransactionEnd(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd result = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -11489,17 +12530,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof TransactionEnd) { - return mergeFrom((TransactionEnd)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(TransactionEnd other) { - if (other == TransactionEnd.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.getDefaultInstance()) return this; if (other.hasExecuteTime()) { setExecuteTime(other.getExecuteTime()); } @@ -11527,31 +12601,34 @@ public final class CanalEntry { props_ = other.props_; bitField0_ = (bitField0_ & ~0x00000004); propsBuilder_ = - com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? getPropsFieldBuilder() : null; } else { propsBuilder_.addAllMessages(other.props_); } } } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - TransactionEnd parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (TransactionEnd) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -11563,31 +12640,31 @@ public final class CanalEntry { private long executeTime_ ; /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public boolean hasExecuteTime() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; } /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public Builder setExecuteTime(long value) { bitField0_ |= 0x00000001; @@ -11596,11 +12673,11 @@ public final class CanalEntry { return this; } /** - * optional int64 executeTime = 1; - * *
              **已废弃,请使用header里的executeTime*
              * 
      + * + * optional int64 executeTime = 1; */ public Builder clearExecuteTime() { bitField0_ = (bitField0_ & ~0x00000001); @@ -11609,52 +12686,52 @@ public final class CanalEntry { return this; } - private Object transactionId_ = ""; + private java.lang.Object transactionId_ = ""; /** - * optional string transactionId = 2; - * *
              **事务号*
              * 
      + * + * optional string transactionId = 2; */ public boolean hasTransactionId() { return ((bitField0_ & 0x00000002) == 0x00000002); } /** - * optional string transactionId = 2; - * *
              **事务号*
              * 
      + * + * optional string transactionId = 2; */ - public String getTransactionId() { - Object ref = transactionId_; - if (!(ref instanceof String)) { + public java.lang.String getTransactionId() { + java.lang.Object ref = transactionId_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { transactionId_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** - * optional string transactionId = 2; - * *
              **事务号*
              * 
      + * + * optional string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { - Object ref = transactionId_; + java.lang.Object ref = transactionId_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); transactionId_ = b; return b; } else { @@ -11662,14 +12739,14 @@ public final class CanalEntry { } } /** - * optional string transactionId = 2; - * *
              **事务号*
              * 
      + * + * optional string transactionId = 2; */ public Builder setTransactionId( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -11679,11 +12756,11 @@ public final class CanalEntry { return this; } /** - * optional string transactionId = 2; - * *
              **事务号*
              * 
      + * + * optional string transactionId = 2; */ public Builder clearTransactionId() { bitField0_ = (bitField0_ & ~0x00000002); @@ -11692,11 +12769,11 @@ public final class CanalEntry { return this; } /** - * optional string transactionId = 2; - * *
              **事务号*
              * 
      + * + * optional string transactionId = 2; */ public Builder setTransactionIdBytes( com.google.protobuf.ByteString value) { @@ -11709,26 +12786,26 @@ public final class CanalEntry { return this; } - private java.util.List props_ = + private java.util.List props_ = java.util.Collections.emptyList(); private void ensurePropsIsMutable() { if (!((bitField0_ & 0x00000004) == 0x00000004)) { - props_ = new java.util.ArrayList(props_); + props_ = new java.util.ArrayList(props_); bitField0_ |= 0x00000004; } } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> propsBuilder_; + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> propsBuilder_; /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List getPropsList() { + public java.util.List getPropsList() { if (propsBuilder_ == null) { return java.util.Collections.unmodifiableList(props_); } else { @@ -11736,11 +12813,11 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public int getPropsCount() { if (propsBuilder_ == null) { @@ -11750,13 +12827,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair getProps(int index) { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getProps(int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -11764,14 +12841,14 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder setProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -11785,14 +12862,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder setProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.set(index, builderForValue.build()); @@ -11803,13 +12880,13 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Builder addProps(Pair value) { + public Builder addProps(com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -11823,14 +12900,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - int index, Pair value) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair value) { if (propsBuilder_ == null) { if (value == null) { throw new NullPointerException(); @@ -11844,14 +12921,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - Pair.Builder builderForValue) { + com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(builderForValue.build()); @@ -11862,14 +12939,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addProps( - int index, Pair.Builder builderForValue) { + int index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder builderForValue) { if (propsBuilder_ == null) { ensurePropsIsMutable(); props_.add(index, builderForValue.build()); @@ -11880,14 +12957,14 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder addAllProps( - Iterable values) { + java.lang.Iterable values) { if (propsBuilder_ == null) { ensurePropsIsMutable(); com.google.protobuf.AbstractMessageLite.Builder.addAll( @@ -11899,11 +12976,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder clearProps() { if (propsBuilder_ == null) { @@ -11916,11 +12993,11 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ public Builder removeProps(int index) { if (propsBuilder_ == null) { @@ -11933,24 +13010,24 @@ public final class CanalEntry { return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder getPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder getPropsBuilder( int index) { return getPropsFieldBuilder().getBuilder(index); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public PairOrBuilder getPropsOrBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder getPropsOrBuilder( int index) { if (propsBuilder_ == null) { return props_.get(index); } else { @@ -11958,13 +13035,13 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsOrBuilderList() { if (propsBuilder_ != null) { return propsBuilder_.getMessageOrBuilderList(); @@ -11973,45 +13050,45 @@ public final class CanalEntry { } } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder addPropsBuilder() { + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder() { return getPropsFieldBuilder().addBuilder( - Pair.getDefaultInstance()); + com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public Pair.Builder addPropsBuilder( + public com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder addPropsBuilder( int index) { return getPropsFieldBuilder().addBuilder( - index, Pair.getDefaultInstance()); + index, com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()); } /** - * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; - * *
              **预留扩展*
              * 
      + * + * repeated .com.alibaba.otter.canal.protocol.Pair props = 3; */ - public java.util.List + public java.util.List getPropsBuilderList() { return getPropsFieldBuilder().getBuilderList(); } - private com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder> + private com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder> getPropsFieldBuilder() { if (propsBuilder_ == null) { - propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< - Pair, Pair.Builder, PairOrBuilder>( + propsBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.alibaba.otter.canal.protocol.CanalEntry.Pair, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder, com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder>( props_, ((bitField0_ & 0x00000004) == 0x00000004), getParentForChildren(), @@ -12020,16 +13097,57 @@ public final class CanalEntry { } return propsBuilder_; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.TransactionEnd) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionEnd) + private static final com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd DEFAULT_INSTANCE; static { - defaultInstance = new TransactionEnd(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public TransactionEnd parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new TransactionEnd(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.TransactionEnd) } public interface PairOrBuilder extends @@ -12043,7 +13161,7 @@ public final class CanalEntry { /** * optional string key = 1; */ - String getKey(); + java.lang.String getKey(); /** * optional string key = 1; */ @@ -12057,7 +13175,7 @@ public final class CanalEntry { /** * optional string value = 2; */ - String getValue(); + java.lang.String getValue(); /** * optional string value = 2; */ @@ -12065,43 +13183,39 @@ public final class CanalEntry { getValueBytes(); } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Pair} - * *
          **预留扩展*
          * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Pair} */ - public static final class Pair extends - com.google.protobuf.GeneratedMessage implements + public static final class Pair extends + com.google.protobuf.GeneratedMessageV3 implements // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Pair) PairOrBuilder { + private static final long serialVersionUID = 0L; // Use Pair.newBuilder() to construct. - private Pair(com.google.protobuf.GeneratedMessage.Builder builder) { + private Pair(com.google.protobuf.GeneratedMessageV3.Builder builder) { super(builder); - this.unknownFields = builder.getUnknownFields(); } - private Pair(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final Pair defaultInstance; - public static Pair getDefaultInstance() { - return defaultInstance; + private Pair() { + key_ = ""; + value_ = ""; } - public Pair getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @Override + @java.lang.Override public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { + getUnknownFields() { return this.unknownFields; } private Pair( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } int mutable_bitField0_ = 0; com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); @@ -12113,13 +13227,6 @@ public final class CanalEntry { case 0: done = true; break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } case 10: { com.google.protobuf.ByteString bs = input.readBytes(); bitField0_ |= 0x00000001; @@ -12132,13 +13239,20 @@ public final class CanalEntry { value_ = bs; break; } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { throw e.setUnfinishedMessage(this); } catch (java.io.IOException e) { throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); + e).setUnfinishedMessage(this); } finally { this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); @@ -12146,34 +13260,20 @@ public final class CanalEntry { } public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable .ensureFieldAccessorsInitialized( - Pair.class, Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Pair parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Pair(input, extensionRegistry); - } - }; - - @Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; + com.alibaba.otter.canal.protocol.CanalEntry.Pair.class, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder.class); } private int bitField0_; public static final int KEY_FIELD_NUMBER = 1; - private Object key_; + private volatile java.lang.Object key_; /** * optional string key = 1; */ @@ -12183,14 +13283,14 @@ public final class CanalEntry { /** * optional string key = 1; */ - public String getKey() { - Object ref = key_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getKey() { + java.lang.Object ref = key_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { key_ = s; } @@ -12202,11 +13302,11 @@ public final class CanalEntry { */ public com.google.protobuf.ByteString getKeyBytes() { - Object ref = key_; - if (ref instanceof String) { + java.lang.Object ref = key_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); key_ = b; return b; } else { @@ -12215,7 +13315,7 @@ public final class CanalEntry { } public static final int VALUE_FIELD_NUMBER = 2; - private Object value_; + private volatile java.lang.Object value_; /** * optional string value = 2; */ @@ -12225,14 +13325,14 @@ public final class CanalEntry { /** * optional string value = 2; */ - public String getValue() { - Object ref = value_; - if (ref instanceof String) { - return (String) ref; + public java.lang.String getValue() { + java.lang.Object ref = value_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { value_ = s; } @@ -12244,11 +13344,11 @@ public final class CanalEntry { */ public com.google.protobuf.ByteString getValueBytes() { - Object ref = value_; - if (ref instanceof String) { + java.lang.Object ref = value_; + if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); value_ = b; return b; } else { @@ -12256,11 +13356,8 @@ public final class CanalEntry { } } - private void initFields() { - key_ = ""; - value_ = ""; - } private byte memoizedIsInitialized = -1; + @java.lang.Override public final boolean isInitialized() { byte isInitialized = memoizedIsInitialized; if (isInitialized == 1) return true; @@ -12270,131 +13367,192 @@ public final class CanalEntry { return true; } + @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getKeyBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, key_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getValueBytes()); + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, value_); } - getUnknownFields().writeTo(output); + unknownFields.writeTo(output); } - private int memoizedSerializedSize = -1; + @java.lang.Override public int getSerializedSize() { - int size = memoizedSerializedSize; + int size = memoizedSize; if (size != -1) return size; size = 0; if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, getKeyBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, key_); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(2, getValueBytes()); + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, value_); } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; + size += unknownFields.getSerializedSize(); + memoizedSize = size; return size; } - private static final long serialVersionUID = 0L; - @Override - protected Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalEntry.Pair)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalEntry.Pair other = (com.alibaba.otter.canal.protocol.CanalEntry.Pair) obj; + + boolean result = true; + result = result && (hasKey() == other.hasKey()); + if (hasKey()) { + result = result && getKey() + .equals(other.getKey()); + } + result = result && (hasValue() == other.hasValue()); + if (hasValue()) { + result = result && getValue() + .equals(other.getValue()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; } - public static Pair parseFrom( + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasKey()) { + hash = (37 * hash) + KEY_FIELD_NUMBER; + hash = (53 * hash) + getKey().hashCode(); + } + if (hasValue()) { + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( com.google.protobuf.ByteString data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Pair parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( com.google.protobuf.ByteString data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Pair parseFrom(byte[] data) + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(byte[] data) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data); } - public static Pair parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws com.google.protobuf.InvalidProtocolBufferException { return PARSER.parseFrom(data, extensionRegistry); } - public static Pair parseFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Pair parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Pair parseDelimitedFrom(java.io.InputStream input) + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseDelimitedFrom(java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); } - public static Pair parseDelimitedFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseDelimitedFrom( java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); } - public static Pair parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); } - public static Pair parseFrom( + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair parseFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); } - public static Builder newBuilder() { return Builder.create(); } + @java.lang.Override public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(Pair prototype) { - return newBuilder().mergeFrom(prototype); + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalEntry.Pair prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); } - public Builder toBuilder() { return newBuilder(this); } - @Override + @java.lang.Override protected Builder newBuilderForType( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { Builder builder = new Builder(parent); return builder; } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Pair} - * *
            **预留扩展*
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Pair} */ public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder implements + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Pair) - PairOrBuilder { + com.alibaba.otter.canal.protocol.CanalEntry.PairOrBuilder { public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; } - protected FieldAccessorTable + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internalGetFieldAccessorTable() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable .ensureFieldAccessorsInitialized( - Pair.class, Builder.class); + com.alibaba.otter.canal.protocol.CanalEntry.Pair.class, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder.class); } // Construct using com.alibaba.otter.canal.protocol.CanalEntry.Pair.newBuilder() @@ -12403,18 +13561,16 @@ public final class CanalEntry { } private Builder( - BuilderParent parent) { + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { super(parent); maybeForceBuilderInitialization(); } private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } } - private static Builder create() { - return new Builder(); - } - + @java.lang.Override public Builder clear() { super.clear(); key_ = ""; @@ -12424,29 +13580,29 @@ public final class CanalEntry { return this; } - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - + @java.lang.Override public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; + return com.alibaba.otter.canal.protocol.CanalEntry.internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; } - public Pair getDefaultInstanceForType() { - return Pair.getDefaultInstance(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance(); } - public Pair build() { - Pair result = buildPartial(); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Pair build() { + com.alibaba.otter.canal.protocol.CanalEntry.Pair result = buildPartial(); if (!result.isInitialized()) { throw newUninitializedMessageException(result); } return result; } - public Pair buildPartial() { - Pair result = new Pair(this); + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Pair buildPartial() { + com.alibaba.otter.canal.protocol.CanalEntry.Pair result = new com.alibaba.otter.canal.protocol.CanalEntry.Pair(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; if (((from_bitField0_ & 0x00000001) == 0x00000001)) { @@ -12462,17 +13618,50 @@ public final class CanalEntry { return result; } + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof Pair) { - return mergeFrom((Pair)other); + if (other instanceof com.alibaba.otter.canal.protocol.CanalEntry.Pair) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalEntry.Pair)other); } else { super.mergeFrom(other); return this; } } - public Builder mergeFrom(Pair other) { - if (other == Pair.getDefaultInstance()) return this; + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Pair other) { + if (other == com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()) return this; if (other.hasKey()) { bitField0_ |= 0x00000001; key_ = other.key_; @@ -12483,24 +13672,27 @@ public final class CanalEntry { value_ = other.value_; onChanged(); } - this.mergeUnknownFields(other.getUnknownFields()); + this.mergeUnknownFields(other.unknownFields); + onChanged(); return this; } + @java.lang.Override public final boolean isInitialized() { return true; } + @java.lang.Override public Builder mergeFrom( com.google.protobuf.CodedInputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) throws java.io.IOException { - Pair parsedMessage = null; + com.alibaba.otter.canal.protocol.CanalEntry.Pair parsedMessage = null; try { parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (Pair) e.getUnfinishedMessage(); - throw e; + parsedMessage = (com.alibaba.otter.canal.protocol.CanalEntry.Pair) e.getUnfinishedMessage(); + throw e.unwrapIOException(); } finally { if (parsedMessage != null) { mergeFrom(parsedMessage); @@ -12510,7 +13702,7 @@ public final class CanalEntry { } private int bitField0_; - private Object key_ = ""; + private java.lang.Object key_ = ""; /** * optional string key = 1; */ @@ -12520,18 +13712,18 @@ public final class CanalEntry { /** * optional string key = 1; */ - public String getKey() { - Object ref = key_; - if (!(ref instanceof String)) { + public java.lang.String getKey() { + java.lang.Object ref = key_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { key_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** @@ -12539,11 +13731,11 @@ public final class CanalEntry { */ public com.google.protobuf.ByteString getKeyBytes() { - Object ref = key_; + java.lang.Object ref = key_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); key_ = b; return b; } else { @@ -12554,7 +13746,7 @@ public final class CanalEntry { * optional string key = 1; */ public Builder setKey( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -12586,7 +13778,7 @@ public final class CanalEntry { return this; } - private Object value_ = ""; + private java.lang.Object value_ = ""; /** * optional string value = 2; */ @@ -12596,18 +13788,18 @@ public final class CanalEntry { /** * optional string value = 2; */ - public String getValue() { - Object ref = value_; - if (!(ref instanceof String)) { + public java.lang.String getValue() { + java.lang.Object ref = value_; + if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - String s = bs.toStringUtf8(); + java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { value_ = s; } return s; } else { - return (String) ref; + return (java.lang.String) ref; } } /** @@ -12615,11 +13807,11 @@ public final class CanalEntry { */ public com.google.protobuf.ByteString getValueBytes() { - Object ref = value_; + java.lang.Object ref = value_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( - (String) ref); + (java.lang.String) ref); value_ = b; return b; } else { @@ -12630,7 +13822,7 @@ public final class CanalEntry { * optional string value = 2; */ public Builder setValue( - String value) { + java.lang.String value) { if (value == null) { throw new NullPointerException(); } @@ -12661,67 +13853,108 @@ public final class CanalEntry { onChanged(); return this; } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Pair) } + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Pair) + private static final com.alibaba.otter.canal.protocol.CanalEntry.Pair DEFAULT_INSTANCE; static { - defaultInstance = new Pair(true); - defaultInstance.initFields(); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalEntry.Pair(); + } + + public static com.alibaba.otter.canal.protocol.CanalEntry.Pair getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Pair parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Pair(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalEntry.Pair getDefaultInstanceForType() { + return DEFAULT_INSTANCE; } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Pair) } private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Header_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Column_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable; private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { return descriptor; } - private static com.google.protobuf.Descriptors.FileDescriptor + private static com.google.protobuf.Descriptors.FileDescriptor descriptor; static { - String[] descriptorData = { + java.lang.String[] descriptorData = { "\n\023EntryProtocol.proto\022 com.alibaba.otter" + ".canal.protocol\"\236\001\n\005Entry\0228\n\006header\030\001 \001(" + "\0132(.com.alibaba.otter.canal.protocol.Hea" + @@ -12731,7 +13964,7 @@ public final class CanalEntry { " \001(\005:\0011\022\023\n\013logfileName\030\002 \001(\t\022\025\n\rlogfileO" + "ffset\030\003 \001(\003\022\020\n\010serverId\030\004 \001(\003\022\024\n\014servere" + "nCode\030\005 \001(\t\022\023\n\013executeTime\030\006 \001(\003\022A\n\nsour" + - "ceType\030\007 \001(\0162&.com.alibaba.otter.canal.p", + "ceType\030\007 \001(\0162&.com.alibaba.otter.canal.p" + "rotocol.Type:\005MYSQL\022\022\n\nschemaName\030\010 \001(\t\022" + "\021\n\ttableName\030\t \001(\t\022\023\n\013eventLength\030\n \001(\003\022" + "F\n\teventType\030\013 \001(\0162+.com.alibaba.otter.c" + @@ -12741,7 +13974,7 @@ public final class CanalEntry { "\030\001 \001(\005\022\017\n\007sqlType\030\002 \001(\005\022\014\n\004name\030\003 \001(\t\022\r\n" + "\005isKey\030\004 \001(\010\022\017\n\007updated\030\005 \001(\010\022\025\n\006isNull\030" + "\006 \001(\010:\005false\0225\n\005props\030\007 \003(\0132&.com.alibab" + - "a.otter.canal.protocol.Pair\022\r\n\005value\030\010 \001", + "a.otter.canal.protocol.Pair\022\r\n\005value\030\010 \001" + "(\t\022\016\n\006length\030\t \001(\005\022\021\n\tmysqlType\030\n \001(\t\"\301\001" + "\n\007RowData\022?\n\rbeforeColumns\030\001 \003(\0132(.com.a" + "libaba.otter.canal.protocol.Column\022>\n\014af" + @@ -12751,7 +13984,7 @@ public final class CanalEntry { "wChange\022\017\n\007tableId\030\001 \001(\003\022F\n\teventType\030\002 " + "\001(\0162+.com.alibaba.otter.canal.protocol.E" + "ventType:\006UPDATE\022\024\n\005isDdl\030\n \001(\010:\005false\022\013" + - "\n\003sql\030\013 \001(\t\022;\n\010rowDatas\030\014 \003(\0132).com.alib", + "\n\003sql\030\013 \001(\t\022;\n\010rowDatas\030\014 \003(\0132).com.alib" + "aba.otter.canal.protocol.RowData\0225\n\005prop" + "s\030\r \003(\0132&.com.alibaba.otter.canal.protoc" + "ol.Pair\022\025\n\rddlSchemaName\030\016 \001(\t\"\207\001\n\020Trans" + @@ -12761,7 +13994,7 @@ public final class CanalEntry { "Id\030\004 \001(\003\"s\n\016TransactionEnd\022\023\n\013executeTim" + "e\030\001 \001(\003\022\025\n\rtransactionId\030\002 \001(\t\0225\n\005props\030" + "\003 \003(\0132&.com.alibaba.otter.canal.protocol" + - ".Pair\"\"\n\004Pair\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(", + ".Pair\"\"\n\004Pair\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(" + "\t*^\n\tEntryType\022\024\n\020TRANSACTIONBEGIN\020\001\022\013\n\007" + "ROWDATA\020\002\022\022\n\016TRANSACTIONEND\020\003\022\r\n\tHEARTBE" + "AT\020\004\022\013\n\007GTIDLOG\020\005*\306\001\n\tEventType\022\n\n\006INSER" + @@ -12788,51 +14021,51 @@ public final class CanalEntry { internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor = getDescriptor().getMessageTypes().get(0); internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor, - new String[] { "Header", "EntryType", "StoreValue", }); + new java.lang.String[] { "Header", "EntryType", "StoreValue", }); internal_static_com_alibaba_otter_canal_protocol_Header_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_Header_descriptor, - new String[] { "Version", "LogfileName", "LogfileOffset", "ServerId", "ServerenCode", "ExecuteTime", "SourceType", "SchemaName", "TableName", "EventLength", "EventType", "Props", "Gtid", }); + new java.lang.String[] { "Version", "LogfileName", "LogfileOffset", "ServerId", "ServerenCode", "ExecuteTime", "SourceType", "SchemaName", "TableName", "EventLength", "EventType", "Props", "Gtid", }); internal_static_com_alibaba_otter_canal_protocol_Column_descriptor = getDescriptor().getMessageTypes().get(2); internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_Column_descriptor, - new String[] { "Index", "SqlType", "Name", "IsKey", "Updated", "IsNull", "Props", "Value", "Length", "MysqlType", }); + new java.lang.String[] { "Index", "SqlType", "Name", "IsKey", "Updated", "IsNull", "Props", "Value", "Length", "MysqlType", }); internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor = getDescriptor().getMessageTypes().get(3); internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor, - new String[] { "BeforeColumns", "AfterColumns", "Props", }); + new java.lang.String[] { "BeforeColumns", "AfterColumns", "Props", }); internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor = getDescriptor().getMessageTypes().get(4); internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor, - new String[] { "TableId", "EventType", "IsDdl", "Sql", "RowDatas", "Props", "DdlSchemaName", }); + new java.lang.String[] { "TableId", "EventType", "IsDdl", "Sql", "RowDatas", "Props", "DdlSchemaName", }); internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor = getDescriptor().getMessageTypes().get(5); internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor, - new String[] { "ExecuteTime", "TransactionId", "Props", "ThreadId", }); + new java.lang.String[] { "ExecuteTime", "TransactionId", "Props", "ThreadId", }); internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor = getDescriptor().getMessageTypes().get(6); internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_TransactionEnd_descriptor, - new String[] { "ExecuteTime", "TransactionId", "Props", }); + new java.lang.String[] { "ExecuteTime", "TransactionId", "Props", }); internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor = getDescriptor().getMessageTypes().get(7); internal_static_com_alibaba_otter_canal_protocol_Pair_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_Pair_descriptor, - new String[] { "Key", "Value", }); + new java.lang.String[] { "Key", "Value", }); } // @@protoc_insertion_point(outer_class_scope) diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java index 3909668a..9c912af7 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java @@ -4,9847 +4,11256 @@ package com.alibaba.otter.canal.protocol; public final class CanalPacket { + private CanalPacket() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } - private CanalPacket(){ - } + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + /** + * Protobuf enum {@code com.alibaba.otter.canal.protocol.Compression} + */ + public enum Compression + implements com.google.protobuf.ProtocolMessageEnum { + /** + * NONE = 1; + */ + NONE(1), + /** + * ZLIB = 2; + */ + ZLIB(2), + /** + * GZIP = 3; + */ + GZIP(3), + /** + * LZF = 4; + */ + LZF(4), + ; - public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) { + /** + * NONE = 1; + */ + public static final int NONE_VALUE = 1; + /** + * ZLIB = 2; + */ + public static final int ZLIB_VALUE = 2; + /** + * GZIP = 3; + */ + public static final int GZIP_VALUE = 3; + /** + * LZF = 4; + */ + public static final int LZF_VALUE = 4; + + + public final int getNumber() { + return value; } /** - * Protobuf enum {@code com.alibaba.otter.canal.protocol.Compression} + * @deprecated Use {@link #forNumber(int)} instead. */ - public enum Compression implements com.google.protobuf.ProtocolMessageEnum { - /** - * NONE = 1; - */ - NONE(0, 1), - /** - * ZLIB = 2; - */ - ZLIB(1, 2), - /** - * GZIP = 3; - */ - GZIP(2, 3), - /** - * LZF = 4; - */ - LZF(3, 4), ; + @java.lang.Deprecated + public static Compression valueOf(int value) { + return forNumber(value); + } - /** - * NONE = 1; - */ - public static final int NONE_VALUE = 1; - /** - * ZLIB = 2; - */ - public static final int ZLIB_VALUE = 2; - /** - * GZIP = 3; - */ - public static final int GZIP_VALUE = 3; - /** - * LZF = 4; - */ - public static final int LZF_VALUE = 4; + public static Compression forNumber(int value) { + switch (value) { + case 1: return NONE; + case 2: return ZLIB; + case 3: return GZIP; + case 4: return LZF; + default: return null; + } + } - public final int getNumber() { - return value; - } - - public static Compression valueOf(int value) { - switch (value) { - case 1: - return NONE; - case 2: - return ZLIB; - case 3: - return GZIP; - case 4: - return LZF; - default: - return null; + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static final com.google.protobuf.Internal.EnumLiteMap< + Compression> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Compression findValueByNumber(int number) { + return Compression.forNumber(number); } - } + }; - public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { - return internalValueMap; - } + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(ordinal()); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.getDescriptor().getEnumTypes().get(0); + } - private static com.google.protobuf.Internal.EnumLiteMap internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { + private static final Compression[] VALUES = values(); - public Compression findValueByNumber(int number) { - return Compression.valueOf(number); - } - }; + public static Compression valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } - public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); - } + private final int value; - public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { - return getDescriptor(); - } + private Compression(int value) { + this.value = value; + } - public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.getDescriptor().getEnumTypes().get(0); - } + // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.Compression) + } - private static final Compression[] VALUES = values(); + /** + * Protobuf enum {@code com.alibaba.otter.canal.protocol.PacketType} + */ + public enum PacketType + implements com.google.protobuf.ProtocolMessageEnum { + /** + * HANDSHAKE = 1; + */ + HANDSHAKE(1), + /** + * CLIENTAUTHENTICATION = 2; + */ + CLIENTAUTHENTICATION(2), + /** + * ACK = 3; + */ + ACK(3), + /** + * SUBSCRIPTION = 4; + */ + SUBSCRIPTION(4), + /** + * UNSUBSCRIPTION = 5; + */ + UNSUBSCRIPTION(5), + /** + * GET = 6; + */ + GET(6), + /** + * MESSAGES = 7; + */ + MESSAGES(7), + /** + * CLIENTACK = 8; + */ + CLIENTACK(8), + /** + *
      +     * management part
      +     * 
      + * + * SHUTDOWN = 9; + */ + SHUTDOWN(9), + /** + *
      +     * integration
      +     * 
      + * + * DUMP = 10; + */ + DUMP(10), + /** + * HEARTBEAT = 11; + */ + HEARTBEAT(11), + /** + * CLIENTROLLBACK = 12; + */ + CLIENTROLLBACK(12), + ; - public static Compression valueOf(com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException("EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } + /** + * HANDSHAKE = 1; + */ + public static final int HANDSHAKE_VALUE = 1; + /** + * CLIENTAUTHENTICATION = 2; + */ + public static final int CLIENTAUTHENTICATION_VALUE = 2; + /** + * ACK = 3; + */ + public static final int ACK_VALUE = 3; + /** + * SUBSCRIPTION = 4; + */ + public static final int SUBSCRIPTION_VALUE = 4; + /** + * UNSUBSCRIPTION = 5; + */ + public static final int UNSUBSCRIPTION_VALUE = 5; + /** + * GET = 6; + */ + public static final int GET_VALUE = 6; + /** + * MESSAGES = 7; + */ + public static final int MESSAGES_VALUE = 7; + /** + * CLIENTACK = 8; + */ + public static final int CLIENTACK_VALUE = 8; + /** + *
      +     * management part
      +     * 
      + * + * SHUTDOWN = 9; + */ + public static final int SHUTDOWN_VALUE = 9; + /** + *
      +     * integration
      +     * 
      + * + * DUMP = 10; + */ + public static final int DUMP_VALUE = 10; + /** + * HEARTBEAT = 11; + */ + public static final int HEARTBEAT_VALUE = 11; + /** + * CLIENTROLLBACK = 12; + */ + public static final int CLIENTROLLBACK_VALUE = 12; - private final int index; - private final int value; - private Compression(int index, int value){ - this.index = index; - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.Compression) + public final int getNumber() { + return value; } /** - * Protobuf enum {@code com.alibaba.otter.canal.protocol.PacketType} + * @deprecated Use {@link #forNumber(int)} instead. */ - public enum PacketType implements com.google.protobuf.ProtocolMessageEnum { - /** - * HANDSHAKE = 1; - */ - HANDSHAKE(0, 1), - /** - * CLIENTAUTHENTICATION = 2; - */ - CLIENTAUTHENTICATION(1, 2), - /** - * ACK = 3; - */ - ACK(2, 3), - /** - * SUBSCRIPTION = 4; - */ - SUBSCRIPTION(3, 4), - /** - * UNSUBSCRIPTION = 5; - */ - UNSUBSCRIPTION(4, 5), - /** - * GET = 6; - */ - GET(5, 6), - /** - * MESSAGES = 7; - */ - MESSAGES(6, 7), - /** - * CLIENTACK = 8; - */ - CLIENTACK(7, 8), - /** - * SHUTDOWN = 9; - * - *
      -         * management part
      -         * 
      - */ - SHUTDOWN(8, 9), - /** - * DUMP = 10; - * - *
      -         * integration
      -         * 
      - */ - DUMP(9, 10), - /** - * HEARTBEAT = 11; - */ - HEARTBEAT(10, 11), - /** - * CLIENTROLLBACK = 12; - */ - CLIENTROLLBACK(11, 12), ; - - /** - * HANDSHAKE = 1; - */ - public static final int HANDSHAKE_VALUE = 1; - /** - * CLIENTAUTHENTICATION = 2; - */ - public static final int CLIENTAUTHENTICATION_VALUE = 2; - /** - * ACK = 3; - */ - public static final int ACK_VALUE = 3; - /** - * SUBSCRIPTION = 4; - */ - public static final int SUBSCRIPTION_VALUE = 4; - /** - * UNSUBSCRIPTION = 5; - */ - public static final int UNSUBSCRIPTION_VALUE = 5; - /** - * GET = 6; - */ - public static final int GET_VALUE = 6; - /** - * MESSAGES = 7; - */ - public static final int MESSAGES_VALUE = 7; - /** - * CLIENTACK = 8; - */ - public static final int CLIENTACK_VALUE = 8; - /** - * SHUTDOWN = 9; - * - *
      -         * management part
      -         * 
      - */ - public static final int SHUTDOWN_VALUE = 9; - /** - * DUMP = 10; - * - *
      -         * integration
      -         * 
      - */ - public static final int DUMP_VALUE = 10; - /** - * HEARTBEAT = 11; - */ - public static final int HEARTBEAT_VALUE = 11; - /** - * CLIENTROLLBACK = 12; - */ - public static final int CLIENTROLLBACK_VALUE = 12; - - public final int getNumber() { - return value; - } - - public static PacketType valueOf(int value) { - switch (value) { - case 1: - return HANDSHAKE; - case 2: - return CLIENTAUTHENTICATION; - case 3: - return ACK; - case 4: - return SUBSCRIPTION; - case 5: - return UNSUBSCRIPTION; - case 6: - return GET; - case 7: - return MESSAGES; - case 8: - return CLIENTACK; - case 9: - return SHUTDOWN; - case 10: - return DUMP; - case 11: - return HEARTBEAT; - case 12: - return CLIENTROLLBACK; - default: - return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap internalGetValueMap() { - return internalValueMap; - } - - private static com.google.protobuf.Internal.EnumLiteMap internalValueMap = new com.google.protobuf.Internal.EnumLiteMap() { - - public PacketType findValueByNumber(int number) { - return PacketType.valueOf(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor getValueDescriptor() { - return getDescriptor().getValues().get(index); - } - - public final com.google.protobuf.Descriptors.EnumDescriptor getDescriptorForType() { - return getDescriptor(); - } - - public static final com.google.protobuf.Descriptors.EnumDescriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.getDescriptor().getEnumTypes().get(1); - } - - private static final PacketType[] VALUES = values(); - - public static PacketType valueOf(com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException("EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int index; - private final int value; - - private PacketType(int index, int value){ - this.index = index; - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.PacketType) + @java.lang.Deprecated + public static PacketType valueOf(int value) { + return forNumber(value); } - public interface PacketOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Packet) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - boolean hasMagicNumber(); - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - int getMagicNumber(); - - /** - * optional int32 version = 2 [default = 1]; - */ - boolean hasVersion(); - - /** - * optional int32 version = 2 [default = 1]; - */ - int getVersion(); - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - boolean hasType(); - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType(); - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - boolean hasCompression(); - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression(); - - /** - * optional bytes body = 5; - */ - boolean hasBody(); - - /** - * optional bytes body = 5; - */ - com.google.protobuf.ByteString getBody(); + public static PacketType forNumber(int value) { + switch (value) { + case 1: return HANDSHAKE; + case 2: return CLIENTAUTHENTICATION; + case 3: return ACK; + case 4: return SUBSCRIPTION; + case 5: return UNSUBSCRIPTION; + case 6: return GET; + case 7: return MESSAGES; + case 8: return CLIENTACK; + case 9: return SHUTDOWN; + case 10: return DUMP; + case 11: return HEARTBEAT; + case 12: return CLIENTROLLBACK; + default: return null; + } } + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static final com.google.protobuf.Internal.EnumLiteMap< + PacketType> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public PacketType findValueByNumber(int number) { + return PacketType.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(ordinal()); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.getDescriptor().getEnumTypes().get(1); + } + + private static final PacketType[] VALUES = values(); + + public static PacketType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private PacketType(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:com.alibaba.otter.canal.protocol.PacketType) + } + + public interface PacketOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Packet) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int32 magic_number = 1 [default = 17]; + */ + boolean hasMagicNumber(); + /** + * optional int32 magic_number = 1 [default = 17]; + */ + int getMagicNumber(); + + /** + * optional int32 version = 2 [default = 1]; + */ + boolean hasVersion(); + /** + * optional int32 version = 2 [default = 1]; + */ + int getVersion(); + + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + boolean hasType(); + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType(); + + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + boolean hasCompression(); + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression(); + + /** + * optional bytes body = 5; + */ + boolean hasBody(); + /** + * optional bytes body = 5; + */ + com.google.protobuf.ByteString getBody(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.Packet} + */ + public static final class Packet extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Packet) + PacketOrBuilder { + private static final long serialVersionUID = 0L; + // Use Packet.newBuilder() to construct. + private Packet(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Packet() { + magicNumber_ = 17; + version_ = 1; + type_ = 1; + compression_ = 1; + body_ = com.google.protobuf.ByteString.EMPTY; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Packet( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + bitField0_ |= 0x00000001; + magicNumber_ = input.readInt32(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + version_ = input.readInt32(); + break; + } + case 24: { + int rawValue = input.readEnum(); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.PacketType value = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(3, rawValue); + } else { + bitField0_ |= 0x00000004; + type_ = rawValue; + } + break; + } + case 32: { + int rawValue = input.readEnum(); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(4, rawValue); + } else { + bitField0_ |= 0x00000008; + compression_ = rawValue; + } + break; + } + case 42: { + bitField0_ |= 0x00000010; + body_ = input.readBytes(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Packet.class, com.alibaba.otter.canal.protocol.CanalPacket.Packet.Builder.class); + } + + private int bitField0_; + public static final int MAGIC_NUMBER_FIELD_NUMBER = 1; + private int magicNumber_; + /** + * optional int32 magic_number = 1 [default = 17]; + */ + public boolean hasMagicNumber() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 magic_number = 1 [default = 17]; + */ + public int getMagicNumber() { + return magicNumber_; + } + + public static final int VERSION_FIELD_NUMBER = 2; + private int version_; + /** + * optional int32 version = 2 [default = 1]; + */ + public boolean hasVersion() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int32 version = 2 [default = 1]; + */ + public int getVersion() { + return version_; + } + + public static final int TYPE_FIELD_NUMBER = 3; + private int type_; + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + public boolean hasType() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + public com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.PacketType result = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.valueOf(type_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE : result; + } + + public static final int COMPRESSION_FIELD_NUMBER = 4; + private int compression_; + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + public boolean hasCompression() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + public com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(compression_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE : result; + } + + public static final int BODY_FIELD_NUMBER = 5; + private com.google.protobuf.ByteString body_; + /** + * optional bytes body = 5; + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional bytes body = 5; + */ + public com.google.protobuf.ByteString getBody() { + return body_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt32(1, magicNumber_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeInt32(2, version_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeEnum(3, type_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeEnum(4, compression_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(5, body_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, magicNumber_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(2, version_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(3, type_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(4, compression_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(5, body_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Packet)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Packet other = (com.alibaba.otter.canal.protocol.CanalPacket.Packet) obj; + + boolean result = true; + result = result && (hasMagicNumber() == other.hasMagicNumber()); + if (hasMagicNumber()) { + result = result && (getMagicNumber() + == other.getMagicNumber()); + } + result = result && (hasVersion() == other.hasVersion()); + if (hasVersion()) { + result = result && (getVersion() + == other.getVersion()); + } + result = result && (hasType() == other.hasType()); + if (hasType()) { + result = result && type_ == other.type_; + } + result = result && (hasCompression() == other.hasCompression()); + if (hasCompression()) { + result = result && compression_ == other.compression_; + } + result = result && (hasBody() == other.hasBody()); + if (hasBody()) { + result = result && getBody() + .equals(other.getBody()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasMagicNumber()) { + hash = (37 * hash) + MAGIC_NUMBER_FIELD_NUMBER; + hash = (53 * hash) + getMagicNumber(); + } + if (hasVersion()) { + hash = (37 * hash) + VERSION_FIELD_NUMBER; + hash = (53 * hash) + getVersion(); + } + if (hasType()) { + hash = (37 * hash) + TYPE_FIELD_NUMBER; + hash = (53 * hash) + type_; + } + if (hasCompression()) { + hash = (37 * hash) + COMPRESSION_FIELD_NUMBER; + hash = (53 * hash) + compression_; + } + if (hasBody()) { + hash = (37 * hash) + BODY_FIELD_NUMBER; + hash = (53 * hash) + getBody().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Packet prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Packet} */ - public static final class Packet extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Packet) - PacketOrBuilder { - - // Use Packet.newBuilder() to construct. - private Packet(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Packet(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Packet defaultInstance; - - public static Packet getDefaultInstance() { - return defaultInstance; - } - - public Packet getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Packet(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - magicNumber_ = input.readInt32(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - version_ = input.readInt32(); - break; - } - case 24: { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalPacket.PacketType value = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(3, rawValue); - } else { - bitField0_ |= 0x00000004; - type_ = value; - } - break; - } - case 32: { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(4, rawValue); - } else { - bitField0_ |= 0x00000008; - compression_ = value; - } - break; - } - case 42: { - bitField0_ |= 0x00000010; - body_ = input.readBytes(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Packet.class, - com.alibaba.otter.canal.protocol.CanalPacket.Packet.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Packet parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Packet(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int MAGIC_NUMBER_FIELD_NUMBER = 1; - private int magicNumber_; - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public boolean hasMagicNumber() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public int getMagicNumber() { - return magicNumber_; - } - - public static final int VERSION_FIELD_NUMBER = 2; - private int version_; - - /** - * optional int32 version = 2 [default = 1]; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int32 version = 2 [default = 1]; - */ - public int getVersion() { - return version_; - } - - public static final int TYPE_FIELD_NUMBER = 3; - private com.alibaba.otter.canal.protocol.CanalPacket.PacketType type_; - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - public boolean hasType() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - public com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType() { - return type_; - } - - public static final int COMPRESSION_FIELD_NUMBER = 4; - private com.alibaba.otter.canal.protocol.CanalPacket.Compression compression_; - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - public boolean hasCompression() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - public com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression() { - return compression_; - } - - public static final int BODY_FIELD_NUMBER = 5; - private com.google.protobuf.ByteString body_; - - /** - * optional bytes body = 5; - */ - public boolean hasBody() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional bytes body = 5; - */ - public com.google.protobuf.ByteString getBody() { - return body_; - } - - private void initFields() { - magicNumber_ = 17; - version_ = 1; - type_ = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE; - compression_ = com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE; - body_ = com.google.protobuf.ByteString.EMPTY; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt32(1, magicNumber_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeInt32(2, version_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeEnum(3, type_.getNumber()); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeEnum(4, compression_.getNumber()); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBytes(5, body_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(1, magicNumber_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(2, version_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeEnumSize(3, type_.getNumber()); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream.computeEnumSize(4, compression_.getNumber()); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(5, body_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Packet parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Packet prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Packet} - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Packet) com.alibaba.otter.canal.protocol.CanalPacket.PacketOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Packet.class, com.alibaba.otter.canal.protocol.CanalPacket.Packet.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Packet.class, - com.alibaba.otter.canal.protocol.CanalPacket.Packet.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Packet.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Packet.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - magicNumber_ = 17; - bitField0_ = (bitField0_ & ~0x00000001); - version_ = 1; - bitField0_ = (bitField0_ & ~0x00000002); - type_ = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE; - bitField0_ = (bitField0_ & ~0x00000004); - compression_ = com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE; - bitField0_ = (bitField0_ & ~0x00000008); - body_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000010); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Packet getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Packet.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Packet build() { - com.alibaba.otter.canal.protocol.CanalPacket.Packet result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Packet buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Packet result = new com.alibaba.otter.canal.protocol.CanalPacket.Packet(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.magicNumber_ = magicNumber_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.version_ = version_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.type_ = type_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.compression_ = compression_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - result.body_ = body_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Packet) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Packet) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Packet other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Packet.getDefaultInstance()) return this; - if (other.hasMagicNumber()) { - setMagicNumber(other.getMagicNumber()); - } - if (other.hasVersion()) { - setVersion(other.getVersion()); - } - if (other.hasType()) { - setType(other.getType()); - } - if (other.hasCompression()) { - setCompression(other.getCompression()); - } - if (other.hasBody()) { - setBody(other.getBody()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Packet parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Packet) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private int magicNumber_ = 17; - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public boolean hasMagicNumber() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public int getMagicNumber() { - return magicNumber_; - } - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public Builder setMagicNumber(int value) { - bitField0_ |= 0x00000001; - magicNumber_ = value; - onChanged(); - return this; - } - - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public Builder clearMagicNumber() { - bitField0_ = (bitField0_ & ~0x00000001); - magicNumber_ = 17; - onChanged(); - return this; - } - - private int version_ = 1; - - /** - * optional int32 version = 2 [default = 1]; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int32 version = 2 [default = 1]; - */ - public int getVersion() { - return version_; - } - - /** - * optional int32 version = 2 [default = 1]; - */ - public Builder setVersion(int value) { - bitField0_ |= 0x00000002; - version_ = value; - onChanged(); - return this; - } - - /** - * optional int32 version = 2 [default = 1]; - */ - public Builder clearVersion() { - bitField0_ = (bitField0_ & ~0x00000002); - version_ = 1; - onChanged(); - return this; - } - - private com.alibaba.otter.canal.protocol.CanalPacket.PacketType type_ = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE; - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - public boolean hasType() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - public com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType() { - return type_; - } - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - public Builder setType(com.alibaba.otter.canal.protocol.CanalPacket.PacketType value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - type_ = value; - onChanged(); - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; - */ - public Builder clearType() { - bitField0_ = (bitField0_ & ~0x00000004); - type_ = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE; - onChanged(); - return this; - } - - private com.alibaba.otter.canal.protocol.CanalPacket.Compression compression_ = com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE; - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - public boolean hasCompression() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - public com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression() { - return compression_; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - public Builder setCompression(com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000008; - compression_ = value; - onChanged(); - return this; - } - - /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; - */ - public Builder clearCompression() { - bitField0_ = (bitField0_ & ~0x00000008); - compression_ = com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString body_ = com.google.protobuf.ByteString.EMPTY; - - /** - * optional bytes body = 5; - */ - public boolean hasBody() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional bytes body = 5; - */ - public com.google.protobuf.ByteString getBody() { - return body_; - } - - /** - * optional bytes body = 5; - */ - public Builder setBody(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000010; - body_ = value; - onChanged(); - return this; - } - - /** - * optional bytes body = 5; - */ - public Builder clearBody() { - bitField0_ = (bitField0_ & ~0x00000010); - body_ = getDefaultInstance().getBody(); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Packet) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + magicNumber_ = 17; + bitField0_ = (bitField0_ & ~0x00000001); + version_ = 1; + bitField0_ = (bitField0_ & ~0x00000002); + type_ = 1; + bitField0_ = (bitField0_ & ~0x00000004); + compression_ = 1; + bitField0_ = (bitField0_ & ~0x00000008); + body_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); + return this; + } - static { - defaultInstance = new Packet(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Packet getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Packet.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Packet build() { + com.alibaba.otter.canal.protocol.CanalPacket.Packet result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Packet) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Packet buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Packet result = new com.alibaba.otter.canal.protocol.CanalPacket.Packet(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.magicNumber_ = magicNumber_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.version_ = version_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.type_ = type_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.compression_ = compression_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.body_ = body_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Packet) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Packet)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Packet other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Packet.getDefaultInstance()) return this; + if (other.hasMagicNumber()) { + setMagicNumber(other.getMagicNumber()); + } + if (other.hasVersion()) { + setVersion(other.getVersion()); + } + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasCompression()) { + setCompression(other.getCompression()); + } + if (other.hasBody()) { + setBody(other.getBody()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Packet parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Packet) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int magicNumber_ = 17; + /** + * optional int32 magic_number = 1 [default = 17]; + */ + public boolean hasMagicNumber() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 magic_number = 1 [default = 17]; + */ + public int getMagicNumber() { + return magicNumber_; + } + /** + * optional int32 magic_number = 1 [default = 17]; + */ + public Builder setMagicNumber(int value) { + bitField0_ |= 0x00000001; + magicNumber_ = value; + onChanged(); + return this; + } + /** + * optional int32 magic_number = 1 [default = 17]; + */ + public Builder clearMagicNumber() { + bitField0_ = (bitField0_ & ~0x00000001); + magicNumber_ = 17; + onChanged(); + return this; + } + + private int version_ = 1; + /** + * optional int32 version = 2 [default = 1]; + */ + public boolean hasVersion() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int32 version = 2 [default = 1]; + */ + public int getVersion() { + return version_; + } + /** + * optional int32 version = 2 [default = 1]; + */ + public Builder setVersion(int value) { + bitField0_ |= 0x00000002; + version_ = value; + onChanged(); + return this; + } + /** + * optional int32 version = 2 [default = 1]; + */ + public Builder clearVersion() { + bitField0_ = (bitField0_ & ~0x00000002); + version_ = 1; + onChanged(); + return this; + } + + private int type_ = 1; + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + public boolean hasType() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + public com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.PacketType result = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.valueOf(type_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE : result; + } + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + public Builder setType(com.alibaba.otter.canal.protocol.CanalPacket.PacketType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + type_ = value.getNumber(); + onChanged(); + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000004); + type_ = 1; + onChanged(); + return this; + } + + private int compression_ = 1; + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + public boolean hasCompression() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + public com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(compression_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE : result; + } + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + public Builder setCompression(com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000008; + compression_ = value.getNumber(); + onChanged(); + return this; + } + /** + * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + */ + public Builder clearCompression() { + bitField0_ = (bitField0_ & ~0x00000008); + compression_ = 1; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString body_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes body = 5; + */ + public boolean hasBody() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional bytes body = 5; + */ + public com.google.protobuf.ByteString getBody() { + return body_; + } + /** + * optional bytes body = 5; + */ + public Builder setBody(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + body_ = value; + onChanged(); + return this; + } + /** + * optional bytes body = 5; + */ + public Builder clearBody() { + bitField0_ = (bitField0_ & ~0x00000010); + body_ = getDefaultInstance().getBody(); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Packet) } - public interface HeartBeatOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.HeartBeat) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 send_timestamp = 1; - */ - boolean hasSendTimestamp(); - - /** - * optional int64 send_timestamp = 1; - */ - long getSendTimestamp(); - - /** - * optional int64 start_timestamp = 2; - */ - boolean hasStartTimestamp(); - - /** - * optional int64 start_timestamp = 2; - */ - long getStartTimestamp(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Packet) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Packet DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Packet(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Packet getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Packet parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Packet(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Packet getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface HeartBeatOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.HeartBeat) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int64 send_timestamp = 1; + */ + boolean hasSendTimestamp(); + /** + * optional int64 send_timestamp = 1; + */ + long getSendTimestamp(); + + /** + * optional int64 start_timestamp = 2; + */ + boolean hasStartTimestamp(); + /** + * optional int64 start_timestamp = 2; + */ + long getStartTimestamp(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.HeartBeat} + */ + public static final class HeartBeat extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.HeartBeat) + HeartBeatOrBuilder { + private static final long serialVersionUID = 0L; + // Use HeartBeat.newBuilder() to construct. + private HeartBeat(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private HeartBeat() { + sendTimestamp_ = 0L; + startTimestamp_ = 0L; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private HeartBeat( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + bitField0_ |= 0x00000001; + sendTimestamp_ = input.readInt64(); + break; + } + case 16: { + bitField0_ |= 0x00000002; + startTimestamp_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.class, com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.Builder.class); + } + + private int bitField0_; + public static final int SEND_TIMESTAMP_FIELD_NUMBER = 1; + private long sendTimestamp_; + /** + * optional int64 send_timestamp = 1; + */ + public boolean hasSendTimestamp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 send_timestamp = 1; + */ + public long getSendTimestamp() { + return sendTimestamp_; + } + + public static final int START_TIMESTAMP_FIELD_NUMBER = 2; + private long startTimestamp_; + /** + * optional int64 start_timestamp = 2; + */ + public boolean hasStartTimestamp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int64 start_timestamp = 2; + */ + public long getStartTimestamp() { + return startTimestamp_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt64(1, sendTimestamp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeInt64(2, startTimestamp_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, sendTimestamp_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(2, startTimestamp_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat other = (com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat) obj; + + boolean result = true; + result = result && (hasSendTimestamp() == other.hasSendTimestamp()); + if (hasSendTimestamp()) { + result = result && (getSendTimestamp() + == other.getSendTimestamp()); + } + result = result && (hasStartTimestamp() == other.hasStartTimestamp()); + if (hasStartTimestamp()) { + result = result && (getStartTimestamp() + == other.getStartTimestamp()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasSendTimestamp()) { + hash = (37 * hash) + SEND_TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getSendTimestamp()); + } + if (hasStartTimestamp()) { + hash = (37 * hash) + START_TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getStartTimestamp()); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.HeartBeat} */ - public static final class HeartBeat extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.HeartBeat) - HeartBeatOrBuilder { - - // Use HeartBeat.newBuilder() to construct. - private HeartBeat(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private HeartBeat(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final HeartBeat defaultInstance; - - public static HeartBeat getDefaultInstance() { - return defaultInstance; - } - - public HeartBeat getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private HeartBeat(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - sendTimestamp_ = input.readInt64(); - break; - } - case 16: { - bitField0_ |= 0x00000002; - startTimestamp_ = input.readInt64(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.class, - com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public HeartBeat parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new HeartBeat(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int SEND_TIMESTAMP_FIELD_NUMBER = 1; - private long sendTimestamp_; - - /** - * optional int64 send_timestamp = 1; - */ - public boolean hasSendTimestamp() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 send_timestamp = 1; - */ - public long getSendTimestamp() { - return sendTimestamp_; - } - - public static final int START_TIMESTAMP_FIELD_NUMBER = 2; - private long startTimestamp_; - - /** - * optional int64 start_timestamp = 2; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int64 start_timestamp = 2; - */ - public long getStartTimestamp() { - return startTimestamp_; - } - - private void initFields() { - sendTimestamp_ = 0L; - startTimestamp_ = 0L; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt64(1, sendTimestamp_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeInt64(2, startTimestamp_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(1, sendTimestamp_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(2, startTimestamp_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.HeartBeat} - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.HeartBeat) com.alibaba.otter.canal.protocol.CanalPacket.HeartBeatOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.class, com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.Builder.class); + } + + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { + } + } + @java.lang.Override + public Builder clear() { + super.clear(); + sendTimestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + startTimestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat build() { + com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat result = new com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.sendTimestamp_ = sendTimestamp_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.startTimestamp_ = startTimestamp_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.getDefaultInstance()) return this; + if (other.hasSendTimestamp()) { + setSendTimestamp(other.getSendTimestamp()); + } + if (other.hasStartTimestamp()) { + setStartTimestamp(other.getStartTimestamp()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private long sendTimestamp_ ; + /** + * optional int64 send_timestamp = 1; + */ + public boolean hasSendTimestamp() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 send_timestamp = 1; + */ + public long getSendTimestamp() { + return sendTimestamp_; + } + /** + * optional int64 send_timestamp = 1; + */ + public Builder setSendTimestamp(long value) { + bitField0_ |= 0x00000001; + sendTimestamp_ = value; + onChanged(); + return this; + } + /** + * optional int64 send_timestamp = 1; + */ + public Builder clearSendTimestamp() { + bitField0_ = (bitField0_ & ~0x00000001); + sendTimestamp_ = 0L; + onChanged(); + return this; + } + + private long startTimestamp_ ; + /** + * optional int64 start_timestamp = 2; + */ + public boolean hasStartTimestamp() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int64 start_timestamp = 2; + */ + public long getStartTimestamp() { + return startTimestamp_; + } + /** + * optional int64 start_timestamp = 2; + */ + public Builder setStartTimestamp(long value) { + bitField0_ |= 0x00000002; + startTimestamp_ = value; + onChanged(); + return this; + } + /** + * optional int64 start_timestamp = 2; + */ + public Builder clearStartTimestamp() { + bitField0_ = (bitField0_ & ~0x00000002); + startTimestamp_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.HeartBeat) + } + + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.HeartBeat) + private static final com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat(); + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public HeartBeat parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new HeartBeat(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface HandshakeOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Handshake) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + boolean hasCommunicationEncoding(); + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + java.lang.String getCommunicationEncoding(); + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + com.google.protobuf.ByteString + getCommunicationEncodingBytes(); + + /** + * optional bytes seeds = 2; + */ + boolean hasSeeds(); + /** + * optional bytes seeds = 2; + */ + com.google.protobuf.ByteString getSeeds(); + + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + java.util.List getSupportedCompressionsList(); + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + int getSupportedCompressionsCount(); + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.Handshake} + */ + public static final class Handshake extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Handshake) + HandshakeOrBuilder { + private static final long serialVersionUID = 0L; + // Use Handshake.newBuilder() to construct. + private Handshake(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Handshake() { + communicationEncoding_ = "utf8"; + seeds_ = com.google.protobuf.ByteString.EMPTY; + supportedCompressions_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Handshake( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + communicationEncoding_ = bs; + break; } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.class, - com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.Builder.class); + case 18: { + bitField0_ |= 0x00000002; + seeds_ = input.readBytes(); + break; } - - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + case 24: { + int rawValue = input.readEnum(); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(3, rawValue); + } else { + if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + supportedCompressions_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; } + supportedCompressions_.add(rawValue); + } + break; } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - sendTimestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - startTimestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat build() { - com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat result = new com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.sendTimestamp_ = sendTimestamp_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.startTimestamp_ = startTimestamp_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat) other); + case 26: { + int length = input.readRawVarint32(); + int oldLimit = input.pushLimit(length); + while(input.getBytesUntilLimit() > 0) { + int rawValue = input.readEnum(); + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(3, rawValue); } else { - super.mergeFrom(other); - return this; + if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + supportedCompressions_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000004; + } + supportedCompressions_.add(rawValue); } + } + input.popLimit(oldLimit); + break; } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.getDefaultInstance()) return this; - if (other.hasSendTimestamp()) { - setSendTimestamp(other.getSendTimestamp()); - } - if (other.hasStartTimestamp()) { - setStartTimestamp(other.getStartTimestamp()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private long sendTimestamp_; - - /** - * optional int64 send_timestamp = 1; - */ - public boolean hasSendTimestamp() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 send_timestamp = 1; - */ - public long getSendTimestamp() { - return sendTimestamp_; - } - - /** - * optional int64 send_timestamp = 1; - */ - public Builder setSendTimestamp(long value) { - bitField0_ |= 0x00000001; - sendTimestamp_ = value; - onChanged(); - return this; - } - - /** - * optional int64 send_timestamp = 1; - */ - public Builder clearSendTimestamp() { - bitField0_ = (bitField0_ & ~0x00000001); - sendTimestamp_ = 0L; - onChanged(); - return this; - } - - private long startTimestamp_; - - /** - * optional int64 start_timestamp = 2; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int64 start_timestamp = 2; - */ - public long getStartTimestamp() { - return startTimestamp_; - } - - /** - * optional int64 start_timestamp = 2; - */ - public Builder setStartTimestamp(long value) { - bitField0_ |= 0x00000002; - startTimestamp_ = value; - onChanged(); - return this; - } - - /** - * optional int64 start_timestamp = 2; - */ - public Builder clearStartTimestamp() { - bitField0_ = (bitField0_ & ~0x00000002); - startTimestamp_ = 0L; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.HeartBeat) + } } - - static { - defaultInstance = new HeartBeat(true); - defaultInstance.initFields(); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { + supportedCompressions_ = java.util.Collections.unmodifiableList(supportedCompressions_); } - - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.HeartBeat) + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; } - public interface HandshakeOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Handshake) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - boolean hasCommunicationEncoding(); - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - java.lang.String getCommunicationEncoding(); - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - com.google.protobuf.ByteString getCommunicationEncodingBytes(); - - /** - * optional bytes seeds = 2; - */ - boolean hasSeeds(); - - /** - * optional bytes seeds = 2; - */ - com.google.protobuf.ByteString getSeeds(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - java.util.List getSupportedCompressionsList(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - int getSupportedCompressionsCount(); - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index); + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Handshake.class, com.alibaba.otter.canal.protocol.CanalPacket.Handshake.Builder.class); } + private int bitField0_; + public static final int COMMUNICATION_ENCODING_FIELD_NUMBER = 1; + private volatile java.lang.Object communicationEncoding_; + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public boolean hasCommunicationEncoding() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public java.lang.String getCommunicationEncoding() { + java.lang.Object ref = communicationEncoding_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + communicationEncoding_ = s; + } + return s; + } + } + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public com.google.protobuf.ByteString + getCommunicationEncodingBytes() { + java.lang.Object ref = communicationEncoding_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + communicationEncoding_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int SEEDS_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString seeds_; + /** + * optional bytes seeds = 2; + */ + public boolean hasSeeds() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes seeds = 2; + */ + public com.google.protobuf.ByteString getSeeds() { + return seeds_; + } + + public static final int SUPPORTED_COMPRESSIONS_FIELD_NUMBER = 3; + private java.util.List supportedCompressions_; + private static final com.google.protobuf.Internal.ListAdapter.Converter< + java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression> supportedCompressions_converter_ = + new com.google.protobuf.Internal.ListAdapter.Converter< + java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression>() { + public com.alibaba.otter.canal.protocol.CanalPacket.Compression convert(java.lang.Integer from) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(from); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE : result; + } + }; + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public java.util.List getSupportedCompressionsList() { + return new com.google.protobuf.Internal.ListAdapter< + java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression>(supportedCompressions_, supportedCompressions_converter_); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public int getSupportedCompressionsCount() { + return supportedCompressions_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index) { + return supportedCompressions_converter_.convert(supportedCompressions_.get(index)); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, communicationEncoding_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, seeds_); + } + for (int i = 0; i < supportedCompressions_.size(); i++) { + output.writeEnum(3, supportedCompressions_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, communicationEncoding_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, seeds_); + } + { + int dataSize = 0; + for (int i = 0; i < supportedCompressions_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeEnumSizeNoTag(supportedCompressions_.get(i)); + } + size += dataSize; + size += 1 * supportedCompressions_.size(); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Handshake)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Handshake other = (com.alibaba.otter.canal.protocol.CanalPacket.Handshake) obj; + + boolean result = true; + result = result && (hasCommunicationEncoding() == other.hasCommunicationEncoding()); + if (hasCommunicationEncoding()) { + result = result && getCommunicationEncoding() + .equals(other.getCommunicationEncoding()); + } + result = result && (hasSeeds() == other.hasSeeds()); + if (hasSeeds()) { + result = result && getSeeds() + .equals(other.getSeeds()); + } + result = result && supportedCompressions_.equals(other.supportedCompressions_); + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasCommunicationEncoding()) { + hash = (37 * hash) + COMMUNICATION_ENCODING_FIELD_NUMBER; + hash = (53 * hash) + getCommunicationEncoding().hashCode(); + } + if (hasSeeds()) { + hash = (37 * hash) + SEEDS_FIELD_NUMBER; + hash = (53 * hash) + getSeeds().hashCode(); + } + if (getSupportedCompressionsCount() > 0) { + hash = (37 * hash) + SUPPORTED_COMPRESSIONS_FIELD_NUMBER; + hash = (53 * hash) + supportedCompressions_.hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Handshake prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Handshake} */ - public static final class Handshake extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Handshake) - HandshakeOrBuilder { - - // Use Handshake.newBuilder() to construct. - private Handshake(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Handshake(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Handshake defaultInstance; - - public static Handshake getDefaultInstance() { - return defaultInstance; - } - - public Handshake getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Handshake(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - communicationEncoding_ = bs; - break; - } - case 18: { - bitField0_ |= 0x00000002; - seeds_ = input.readBytes(); - break; - } - case 24: { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(3, rawValue); - } else { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - supportedCompressions_.add(value); - } - break; - } - case 26: { - int length = input.readRawVarint32(); - int oldLimit = input.pushLimit(length); - while (input.getBytesUntilLimit() > 0) { - int rawValue = input.readEnum(); - com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(3, rawValue); - } else { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - supportedCompressions_.add(value); - } - } - input.popLimit(oldLimit); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = java.util.Collections.unmodifiableList(supportedCompressions_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Handshake.class, - com.alibaba.otter.canal.protocol.CanalPacket.Handshake.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Handshake parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Handshake(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int COMMUNICATION_ENCODING_FIELD_NUMBER = 1; - private java.lang.Object communicationEncoding_; - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public boolean hasCommunicationEncoding() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public java.lang.String getCommunicationEncoding() { - java.lang.Object ref = communicationEncoding_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - communicationEncoding_ = s; - } - return s; - } - } - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public com.google.protobuf.ByteString getCommunicationEncodingBytes() { - java.lang.Object ref = communicationEncoding_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - communicationEncoding_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int SEEDS_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString seeds_; - - /** - * optional bytes seeds = 2; - */ - public boolean hasSeeds() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional bytes seeds = 2; - */ - public com.google.protobuf.ByteString getSeeds() { - return seeds_; - } - - public static final int SUPPORTED_COMPRESSIONS_FIELD_NUMBER = 3; - private java.util.List supportedCompressions_; - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public java.util.List getSupportedCompressionsList() { - return supportedCompressions_; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public int getSupportedCompressionsCount() { - return supportedCompressions_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index) { - return supportedCompressions_.get(index); - } - - private void initFields() { - communicationEncoding_ = "utf8"; - seeds_ = com.google.protobuf.ByteString.EMPTY; - supportedCompressions_ = java.util.Collections.emptyList(); - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getCommunicationEncodingBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, seeds_); - } - for (int i = 0; i < supportedCompressions_.size(); i++) { - output.writeEnum(3, supportedCompressions_.get(i).getNumber()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getCommunicationEncodingBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, seeds_); - } - { - int dataSize = 0; - for (int i = 0; i < supportedCompressions_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream.computeEnumSizeNoTag(supportedCompressions_.get(i) - .getNumber()); - } - size += dataSize; - size += 1 * supportedCompressions_.size(); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Handshake prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Handshake} - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Handshake) com.alibaba.otter.canal.protocol.CanalPacket.HandshakeOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Handshake.class, com.alibaba.otter.canal.protocol.CanalPacket.Handshake.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Handshake.class, - com.alibaba.otter.canal.protocol.CanalPacket.Handshake.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Handshake.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Handshake.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - communicationEncoding_ = "utf8"; - bitField0_ = (bitField0_ & ~0x00000001); - seeds_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000002); - supportedCompressions_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Handshake getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Handshake.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Handshake build() { - com.alibaba.otter.canal.protocol.CanalPacket.Handshake result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Handshake buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Handshake result = new com.alibaba.otter.canal.protocol.CanalPacket.Handshake(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.communicationEncoding_ = communicationEncoding_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.seeds_ = seeds_; - if (((bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = java.util.Collections.unmodifiableList(supportedCompressions_); - bitField0_ = (bitField0_ & ~0x00000004); - } - result.supportedCompressions_ = supportedCompressions_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Handshake) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Handshake) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Handshake other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Handshake.getDefaultInstance()) return this; - if (other.hasCommunicationEncoding()) { - bitField0_ |= 0x00000001; - communicationEncoding_ = other.communicationEncoding_; - onChanged(); - } - if (other.hasSeeds()) { - setSeeds(other.getSeeds()); - } - if (!other.supportedCompressions_.isEmpty()) { - if (supportedCompressions_.isEmpty()) { - supportedCompressions_ = other.supportedCompressions_; - bitField0_ = (bitField0_ & ~0x00000004); - } else { - ensureSupportedCompressionsIsMutable(); - supportedCompressions_.addAll(other.supportedCompressions_); - } - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Handshake parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Handshake) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object communicationEncoding_ = "utf8"; - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public boolean hasCommunicationEncoding() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public java.lang.String getCommunicationEncoding() { - java.lang.Object ref = communicationEncoding_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - communicationEncoding_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public com.google.protobuf.ByteString getCommunicationEncodingBytes() { - java.lang.Object ref = communicationEncoding_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - communicationEncoding_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public Builder setCommunicationEncoding(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - communicationEncoding_ = value; - onChanged(); - return this; - } - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public Builder clearCommunicationEncoding() { - bitField0_ = (bitField0_ & ~0x00000001); - communicationEncoding_ = getDefaultInstance().getCommunicationEncoding(); - onChanged(); - return this; - } - - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public Builder setCommunicationEncodingBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - communicationEncoding_ = value; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString seeds_ = com.google.protobuf.ByteString.EMPTY; - - /** - * optional bytes seeds = 2; - */ - public boolean hasSeeds() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional bytes seeds = 2; - */ - public com.google.protobuf.ByteString getSeeds() { - return seeds_; - } - - /** - * optional bytes seeds = 2; - */ - public Builder setSeeds(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - seeds_ = value; - onChanged(); - return this; - } - - /** - * optional bytes seeds = 2; - */ - public Builder clearSeeds() { - bitField0_ = (bitField0_ & ~0x00000002); - seeds_ = getDefaultInstance().getSeeds(); - onChanged(); - return this; - } - - private java.util.List supportedCompressions_ = java.util.Collections.emptyList(); - - private void ensureSupportedCompressionsIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = new java.util.ArrayList(supportedCompressions_); - bitField0_ |= 0x00000004; - } - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public java.util.List getSupportedCompressionsList() { - return java.util.Collections.unmodifiableList(supportedCompressions_); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public int getSupportedCompressionsCount() { - return supportedCompressions_.size(); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index) { - return supportedCompressions_.get(index); - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public Builder setSupportedCompressions(int index, - com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { - if (value == null) { - throw new NullPointerException(); - } - ensureSupportedCompressionsIsMutable(); - supportedCompressions_.set(index, value); - onChanged(); - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public Builder addSupportedCompressions(com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { - if (value == null) { - throw new NullPointerException(); - } - ensureSupportedCompressionsIsMutable(); - supportedCompressions_.add(value); - onChanged(); - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public Builder addAllSupportedCompressions(java.lang.Iterable values) { - ensureSupportedCompressionsIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, supportedCompressions_); - onChanged(); - return this; - } - - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public Builder clearSupportedCompressions() { - supportedCompressions_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Handshake) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + communicationEncoding_ = "utf8"; + bitField0_ = (bitField0_ & ~0x00000001); + seeds_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + supportedCompressions_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } - static { - defaultInstance = new Handshake(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Handshake getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Handshake.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Handshake build() { + com.alibaba.otter.canal.protocol.CanalPacket.Handshake result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Handshake) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Handshake buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Handshake result = new com.alibaba.otter.canal.protocol.CanalPacket.Handshake(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.communicationEncoding_ = communicationEncoding_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.seeds_ = seeds_; + if (((bitField0_ & 0x00000004) == 0x00000004)) { + supportedCompressions_ = java.util.Collections.unmodifiableList(supportedCompressions_); + bitField0_ = (bitField0_ & ~0x00000004); + } + result.supportedCompressions_ = supportedCompressions_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Handshake) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Handshake)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Handshake other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Handshake.getDefaultInstance()) return this; + if (other.hasCommunicationEncoding()) { + bitField0_ |= 0x00000001; + communicationEncoding_ = other.communicationEncoding_; + onChanged(); + } + if (other.hasSeeds()) { + setSeeds(other.getSeeds()); + } + if (!other.supportedCompressions_.isEmpty()) { + if (supportedCompressions_.isEmpty()) { + supportedCompressions_ = other.supportedCompressions_; + bitField0_ = (bitField0_ & ~0x00000004); + } else { + ensureSupportedCompressionsIsMutable(); + supportedCompressions_.addAll(other.supportedCompressions_); + } + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Handshake parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Handshake) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object communicationEncoding_ = "utf8"; + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public boolean hasCommunicationEncoding() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public java.lang.String getCommunicationEncoding() { + java.lang.Object ref = communicationEncoding_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + communicationEncoding_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public com.google.protobuf.ByteString + getCommunicationEncodingBytes() { + java.lang.Object ref = communicationEncoding_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + communicationEncoding_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public Builder setCommunicationEncoding( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + communicationEncoding_ = value; + onChanged(); + return this; + } + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public Builder clearCommunicationEncoding() { + bitField0_ = (bitField0_ & ~0x00000001); + communicationEncoding_ = getDefaultInstance().getCommunicationEncoding(); + onChanged(); + return this; + } + /** + * optional string communication_encoding = 1 [default = "utf8"]; + */ + public Builder setCommunicationEncodingBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + communicationEncoding_ = value; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString seeds_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes seeds = 2; + */ + public boolean hasSeeds() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes seeds = 2; + */ + public com.google.protobuf.ByteString getSeeds() { + return seeds_; + } + /** + * optional bytes seeds = 2; + */ + public Builder setSeeds(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + seeds_ = value; + onChanged(); + return this; + } + /** + * optional bytes seeds = 2; + */ + public Builder clearSeeds() { + bitField0_ = (bitField0_ & ~0x00000002); + seeds_ = getDefaultInstance().getSeeds(); + onChanged(); + return this; + } + + private java.util.List supportedCompressions_ = + java.util.Collections.emptyList(); + private void ensureSupportedCompressionsIsMutable() { + if (!((bitField0_ & 0x00000004) == 0x00000004)) { + supportedCompressions_ = new java.util.ArrayList(supportedCompressions_); + bitField0_ |= 0x00000004; + } + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public java.util.List getSupportedCompressionsList() { + return new com.google.protobuf.Internal.ListAdapter< + java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression>(supportedCompressions_, supportedCompressions_converter_); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public int getSupportedCompressionsCount() { + return supportedCompressions_.size(); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index) { + return supportedCompressions_converter_.convert(supportedCompressions_.get(index)); + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public Builder setSupportedCompressions( + int index, com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { + if (value == null) { + throw new NullPointerException(); + } + ensureSupportedCompressionsIsMutable(); + supportedCompressions_.set(index, value.getNumber()); + onChanged(); + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public Builder addSupportedCompressions(com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { + if (value == null) { + throw new NullPointerException(); + } + ensureSupportedCompressionsIsMutable(); + supportedCompressions_.add(value.getNumber()); + onChanged(); + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public Builder addAllSupportedCompressions( + java.lang.Iterable values) { + ensureSupportedCompressionsIsMutable(); + for (com.alibaba.otter.canal.protocol.CanalPacket.Compression value : values) { + supportedCompressions_.add(value.getNumber()); + } + onChanged(); + return this; + } + /** + * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public Builder clearSupportedCompressions() { + supportedCompressions_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Handshake) } - public interface ClientAuthOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.ClientAuth) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string username = 1; - */ - boolean hasUsername(); - - /** - * optional string username = 1; - */ - java.lang.String getUsername(); - - /** - * optional string username = 1; - */ - com.google.protobuf.ByteString getUsernameBytes(); - - /** - * optional bytes password = 2; - * - *
      -         * hashed password with seeds from Handshake message
      -         * 
      - */ - boolean hasPassword(); - - /** - * optional bytes password = 2; - * - *
      -         * hashed password with seeds from Handshake message
      -         * 
      - */ - com.google.protobuf.ByteString getPassword(); - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - boolean hasNetReadTimeout(); - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - int getNetReadTimeout(); - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - boolean hasNetWriteTimeout(); - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - int getNetWriteTimeout(); - - /** - * optional string destination = 5; - */ - boolean hasDestination(); - - /** - * optional string destination = 5; - */ - java.lang.String getDestination(); - - /** - * optional string destination = 5; - */ - com.google.protobuf.ByteString getDestinationBytes(); - - /** - * optional string client_id = 6; - */ - boolean hasClientId(); - - /** - * optional string client_id = 6; - */ - java.lang.String getClientId(); - - /** - * optional string client_id = 6; - */ - com.google.protobuf.ByteString getClientIdBytes(); - - /** - * optional string filter = 7; - */ - boolean hasFilter(); - - /** - * optional string filter = 7; - */ - java.lang.String getFilter(); - - /** - * optional string filter = 7; - */ - com.google.protobuf.ByteString getFilterBytes(); - - /** - * optional int64 start_timestamp = 8; - */ - boolean hasStartTimestamp(); - - /** - * optional int64 start_timestamp = 8; - */ - long getStartTimestamp(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Handshake) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Handshake DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Handshake(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Handshake getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Handshake parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Handshake(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Handshake getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ClientAuthOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.ClientAuth) + com.google.protobuf.MessageOrBuilder { + /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientAuth} + * optional string username = 1; + */ + boolean hasUsername(); + /** + * optional string username = 1; + */ + java.lang.String getUsername(); + /** + * optional string username = 1; + */ + com.google.protobuf.ByteString + getUsernameBytes(); + + /** + *
      +     * hashed password with seeds from Handshake message
      +     * 
      * + * optional bytes password = 2; + */ + boolean hasPassword(); + /** + *
      +     * hashed password with seeds from Handshake message
      +     * 
      + * + * optional bytes password = 2; + */ + com.google.protobuf.ByteString getPassword(); + + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + boolean hasNetReadTimeout(); + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + int getNetReadTimeout(); + + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + boolean hasNetWriteTimeout(); + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + int getNetWriteTimeout(); + + /** + * optional string destination = 5; + */ + boolean hasDestination(); + /** + * optional string destination = 5; + */ + java.lang.String getDestination(); + /** + * optional string destination = 5; + */ + com.google.protobuf.ByteString + getDestinationBytes(); + + /** + * optional string client_id = 6; + */ + boolean hasClientId(); + /** + * optional string client_id = 6; + */ + java.lang.String getClientId(); + /** + * optional string client_id = 6; + */ + com.google.protobuf.ByteString + getClientIdBytes(); + + /** + * optional string filter = 7; + */ + boolean hasFilter(); + /** + * optional string filter = 7; + */ + java.lang.String getFilter(); + /** + * optional string filter = 7; + */ + com.google.protobuf.ByteString + getFilterBytes(); + + /** + * optional int64 start_timestamp = 8; + */ + boolean hasStartTimestamp(); + /** + * optional int64 start_timestamp = 8; + */ + long getStartTimestamp(); + } + /** + *
      +   * client authentication
      +   * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientAuth} + */ + public static final class ClientAuth extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.ClientAuth) + ClientAuthOrBuilder { + private static final long serialVersionUID = 0L; + // Use ClientAuth.newBuilder() to construct. + private ClientAuth(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ClientAuth() { + username_ = ""; + password_ = com.google.protobuf.ByteString.EMPTY; + netReadTimeout_ = 0; + netWriteTimeout_ = 0; + destination_ = ""; + clientId_ = ""; + filter_ = ""; + startTimestamp_ = 0L; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ClientAuth( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + username_ = bs; + break; + } + case 18: { + bitField0_ |= 0x00000002; + password_ = input.readBytes(); + break; + } + case 24: { + bitField0_ |= 0x00000004; + netReadTimeout_ = input.readInt32(); + break; + } + case 32: { + bitField0_ |= 0x00000008; + netWriteTimeout_ = input.readInt32(); + break; + } + case 42: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000010; + destination_ = bs; + break; + } + case 50: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000020; + clientId_ = bs; + break; + } + case 58: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000040; + filter_ = bs; + break; + } + case 64: { + bitField0_ |= 0x00000080; + startTimestamp_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.Builder.class); + } + + private int bitField0_; + public static final int USERNAME_FIELD_NUMBER = 1; + private volatile java.lang.Object username_; + /** + * optional string username = 1; + */ + public boolean hasUsername() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string username = 1; + */ + public java.lang.String getUsername() { + java.lang.Object ref = username_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + username_ = s; + } + return s; + } + } + /** + * optional string username = 1; + */ + public com.google.protobuf.ByteString + getUsernameBytes() { + java.lang.Object ref = username_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + username_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PASSWORD_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString password_; + /** + *
      +     * hashed password with seeds from Handshake message
      +     * 
      + * + * optional bytes password = 2; + */ + public boolean hasPassword() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + *
      +     * hashed password with seeds from Handshake message
      +     * 
      + * + * optional bytes password = 2; + */ + public com.google.protobuf.ByteString getPassword() { + return password_; + } + + public static final int NET_READ_TIMEOUT_FIELD_NUMBER = 3; + private int netReadTimeout_; + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + public boolean hasNetReadTimeout() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + public int getNetReadTimeout() { + return netReadTimeout_; + } + + public static final int NET_WRITE_TIMEOUT_FIELD_NUMBER = 4; + private int netWriteTimeout_; + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + public boolean hasNetWriteTimeout() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + *
      +     * in seconds
      +     * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + public int getNetWriteTimeout() { + return netWriteTimeout_; + } + + public static final int DESTINATION_FIELD_NUMBER = 5; + private volatile java.lang.Object destination_; + /** + * optional string destination = 5; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional string destination = 5; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } + } + /** + * optional string destination = 5; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CLIENT_ID_FIELD_NUMBER = 6; + private volatile java.lang.Object clientId_; + /** + * optional string client_id = 6; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional string client_id = 6; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } + } + /** + * optional string client_id = 6; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int FILTER_FIELD_NUMBER = 7; + private volatile java.lang.Object filter_; + /** + * optional string filter = 7; + */ + public boolean hasFilter() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional string filter = 7; + */ + public java.lang.String getFilter() { + java.lang.Object ref = filter_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + filter_ = s; + } + return s; + } + } + /** + * optional string filter = 7; + */ + public com.google.protobuf.ByteString + getFilterBytes() { + java.lang.Object ref = filter_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + filter_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int START_TIMESTAMP_FIELD_NUMBER = 8; + private long startTimestamp_; + /** + * optional int64 start_timestamp = 8; + */ + public boolean hasStartTimestamp() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional int64 start_timestamp = 8; + */ + public long getStartTimestamp() { + return startTimestamp_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, username_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, password_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt32(3, netReadTimeout_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeInt32(4, netWriteTimeout_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 5, destination_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 6, clientId_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 7, filter_); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + output.writeInt64(8, startTimestamp_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, username_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, password_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(3, netReadTimeout_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(4, netWriteTimeout_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, destination_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, clientId_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, filter_); + } + if (((bitField0_ & 0x00000080) == 0x00000080)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(8, startTimestamp_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth other = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth) obj; + + boolean result = true; + result = result && (hasUsername() == other.hasUsername()); + if (hasUsername()) { + result = result && getUsername() + .equals(other.getUsername()); + } + result = result && (hasPassword() == other.hasPassword()); + if (hasPassword()) { + result = result && getPassword() + .equals(other.getPassword()); + } + result = result && (hasNetReadTimeout() == other.hasNetReadTimeout()); + if (hasNetReadTimeout()) { + result = result && (getNetReadTimeout() + == other.getNetReadTimeout()); + } + result = result && (hasNetWriteTimeout() == other.hasNetWriteTimeout()); + if (hasNetWriteTimeout()) { + result = result && (getNetWriteTimeout() + == other.getNetWriteTimeout()); + } + result = result && (hasDestination() == other.hasDestination()); + if (hasDestination()) { + result = result && getDestination() + .equals(other.getDestination()); + } + result = result && (hasClientId() == other.hasClientId()); + if (hasClientId()) { + result = result && getClientId() + .equals(other.getClientId()); + } + result = result && (hasFilter() == other.hasFilter()); + if (hasFilter()) { + result = result && getFilter() + .equals(other.getFilter()); + } + result = result && (hasStartTimestamp() == other.hasStartTimestamp()); + if (hasStartTimestamp()) { + result = result && (getStartTimestamp() + == other.getStartTimestamp()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasUsername()) { + hash = (37 * hash) + USERNAME_FIELD_NUMBER; + hash = (53 * hash) + getUsername().hashCode(); + } + if (hasPassword()) { + hash = (37 * hash) + PASSWORD_FIELD_NUMBER; + hash = (53 * hash) + getPassword().hashCode(); + } + if (hasNetReadTimeout()) { + hash = (37 * hash) + NET_READ_TIMEOUT_FIELD_NUMBER; + hash = (53 * hash) + getNetReadTimeout(); + } + if (hasNetWriteTimeout()) { + hash = (37 * hash) + NET_WRITE_TIMEOUT_FIELD_NUMBER; + hash = (53 * hash) + getNetWriteTimeout(); + } + if (hasDestination()) { + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + } + if (hasClientId()) { + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + } + if (hasFilter()) { + hash = (37 * hash) + FILTER_FIELD_NUMBER; + hash = (53 * hash) + getFilter().hashCode(); + } + if (hasStartTimestamp()) { + hash = (37 * hash) + START_TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getStartTimestamp()); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** *
            * client authentication
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientAuth} */ - public static final class ClientAuth extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.ClientAuth) - ClientAuthOrBuilder { - - // Use ClientAuth.newBuilder() to construct. - private ClientAuth(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private ClientAuth(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final ClientAuth defaultInstance; - - public static ClientAuth getDefaultInstance() { - return defaultInstance; - } - - public ClientAuth getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private ClientAuth(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - username_ = bs; - break; - } - case 18: { - bitField0_ |= 0x00000002; - password_ = input.readBytes(); - break; - } - case 24: { - bitField0_ |= 0x00000004; - netReadTimeout_ = input.readInt32(); - break; - } - case 32: { - bitField0_ |= 0x00000008; - netWriteTimeout_ = input.readInt32(); - break; - } - case 42: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000010; - destination_ = bs; - break; - } - case 50: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000020; - clientId_ = bs; - break; - } - case 58: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000040; - filter_ = bs; - break; - } - case 64: { - bitField0_ |= 0x00000080; - startTimestamp_ = input.readInt64(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.class, - com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public ClientAuth parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new ClientAuth(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int USERNAME_FIELD_NUMBER = 1; - private java.lang.Object username_; - - /** - * optional string username = 1; - */ - public boolean hasUsername() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string username = 1; - */ - public java.lang.String getUsername() { - java.lang.Object ref = username_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - username_ = s; - } - return s; - } - } - - /** - * optional string username = 1; - */ - public com.google.protobuf.ByteString getUsernameBytes() { - java.lang.Object ref = username_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - username_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int PASSWORD_FIELD_NUMBER = 2; - private com.google.protobuf.ByteString password_; - - /** - * optional bytes password = 2; - * - *
      -         * hashed password with seeds from Handshake message
      -         * 
      - */ - public boolean hasPassword() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional bytes password = 2; - * - *
      -         * hashed password with seeds from Handshake message
      -         * 
      - */ - public com.google.protobuf.ByteString getPassword() { - return password_; - } - - public static final int NET_READ_TIMEOUT_FIELD_NUMBER = 3; - private int netReadTimeout_; - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - public boolean hasNetReadTimeout() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - public int getNetReadTimeout() { - return netReadTimeout_; - } - - public static final int NET_WRITE_TIMEOUT_FIELD_NUMBER = 4; - private int netWriteTimeout_; - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - public boolean hasNetWriteTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -         * in seconds
      -         * 
      - */ - public int getNetWriteTimeout() { - return netWriteTimeout_; - } - - public static final int DESTINATION_FIELD_NUMBER = 5; - private java.lang.Object destination_; - - /** - * optional string destination = 5; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional string destination = 5; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } - } - - /** - * optional string destination = 5; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int CLIENT_ID_FIELD_NUMBER = 6; - private java.lang.Object clientId_; - - /** - * optional string client_id = 6; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional string client_id = 6; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } - } - - /** - * optional string client_id = 6; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int FILTER_FIELD_NUMBER = 7; - private java.lang.Object filter_; - - /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - - /** - * optional string filter = 7; - */ - public java.lang.String getFilter() { - java.lang.Object ref = filter_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } - return s; - } - } - - /** - * optional string filter = 7; - */ - public com.google.protobuf.ByteString getFilterBytes() { - java.lang.Object ref = filter_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - filter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int START_TIMESTAMP_FIELD_NUMBER = 8; - private long startTimestamp_; - - /** - * optional int64 start_timestamp = 8; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - - /** - * optional int64 start_timestamp = 8; - */ - public long getStartTimestamp() { - return startTimestamp_; - } - - private void initFields() { - username_ = ""; - password_ = com.google.protobuf.ByteString.EMPTY; - netReadTimeout_ = 0; - netWriteTimeout_ = 0; - destination_ = ""; - clientId_ = ""; - filter_ = ""; - startTimestamp_ = 0L; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getUsernameBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, password_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt32(3, netReadTimeout_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeInt32(4, netWriteTimeout_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeBytes(5, getDestinationBytes()); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBytes(6, getClientIdBytes()); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeBytes(7, getFilterBytes()); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - output.writeInt64(8, startTimestamp_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getUsernameBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, password_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(3, netReadTimeout_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(4, netWriteTimeout_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(5, getDestinationBytes()); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(6, getClientIdBytes()); - } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(7, getFilterBytes()); - } - if (((bitField0_ & 0x00000080) == 0x00000080)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(8, startTimestamp_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientAuth} - * - *
      -         * client authentication
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.ClientAuth) com.alibaba.otter.canal.protocol.CanalPacket.ClientAuthOrBuilder { - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.class, - com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.Builder.class); - } - - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - username_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - password_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000002); - netReadTimeout_ = 0; - bitField0_ = (bitField0_ & ~0x00000004); - netWriteTimeout_ = 0; - bitField0_ = (bitField0_ & ~0x00000008); - destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000010); - clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000020); - filter_ = ""; - bitField0_ = (bitField0_ & ~0x00000040); - startTimestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000080); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth build() { - com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.username_ = username_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.password_ = password_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.netReadTimeout_ = netReadTimeout_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.netWriteTimeout_ = netWriteTimeout_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - result.destination_ = destination_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } - result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000040) == 0x00000040)) { - to_bitField0_ |= 0x00000040; - } - result.filter_ = filter_; - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000080; - } - result.startTimestamp_ = startTimestamp_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.getDefaultInstance()) return this; - if (other.hasUsername()) { - bitField0_ |= 0x00000001; - username_ = other.username_; - onChanged(); - } - if (other.hasPassword()) { - setPassword(other.getPassword()); - } - if (other.hasNetReadTimeout()) { - setNetReadTimeout(other.getNetReadTimeout()); - } - if (other.hasNetWriteTimeout()) { - setNetWriteTimeout(other.getNetWriteTimeout()); - } - if (other.hasDestination()) { - bitField0_ |= 0x00000010; - destination_ = other.destination_; - onChanged(); - } - if (other.hasClientId()) { - bitField0_ |= 0x00000020; - clientId_ = other.clientId_; - onChanged(); - } - if (other.hasFilter()) { - bitField0_ |= 0x00000040; - filter_ = other.filter_; - onChanged(); - } - if (other.hasStartTimestamp()) { - setStartTimestamp(other.getStartTimestamp()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object username_ = ""; - - /** - * optional string username = 1; - */ - public boolean hasUsername() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string username = 1; - */ - public java.lang.String getUsername() { - java.lang.Object ref = username_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - username_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string username = 1; - */ - public com.google.protobuf.ByteString getUsernameBytes() { - java.lang.Object ref = username_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - username_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string username = 1; - */ - public Builder setUsername(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - username_ = value; - onChanged(); - return this; - } - - /** - * optional string username = 1; - */ - public Builder clearUsername() { - bitField0_ = (bitField0_ & ~0x00000001); - username_ = getDefaultInstance().getUsername(); - onChanged(); - return this; - } - - /** - * optional string username = 1; - */ - public Builder setUsernameBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - username_ = value; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString password_ = com.google.protobuf.ByteString.EMPTY; - - /** - * optional bytes password = 2; - * - *
      -             * hashed password with seeds from Handshake message
      -             * 
      - */ - public boolean hasPassword() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional bytes password = 2; - * - *
      -             * hashed password with seeds from Handshake message
      -             * 
      - */ - public com.google.protobuf.ByteString getPassword() { - return password_; - } - - /** - * optional bytes password = 2; - * - *
      -             * hashed password with seeds from Handshake message
      -             * 
      - */ - public Builder setPassword(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - password_ = value; - onChanged(); - return this; - } - - /** - * optional bytes password = 2; - * - *
      -             * hashed password with seeds from Handshake message
      -             * 
      - */ - public Builder clearPassword() { - bitField0_ = (bitField0_ & ~0x00000002); - password_ = getDefaultInstance().getPassword(); - onChanged(); - return this; - } - - private int netReadTimeout_; - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public boolean hasNetReadTimeout() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public int getNetReadTimeout() { - return netReadTimeout_; - } - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public Builder setNetReadTimeout(int value) { - bitField0_ |= 0x00000004; - netReadTimeout_ = value; - onChanged(); - return this; - } - - /** - * optional int32 net_read_timeout = 3 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public Builder clearNetReadTimeout() { - bitField0_ = (bitField0_ & ~0x00000004); - netReadTimeout_ = 0; - onChanged(); - return this; - } - - private int netWriteTimeout_; - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public boolean hasNetWriteTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public int getNetWriteTimeout() { - return netWriteTimeout_; - } - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public Builder setNetWriteTimeout(int value) { - bitField0_ |= 0x00000008; - netWriteTimeout_ = value; - onChanged(); - return this; - } - - /** - * optional int32 net_write_timeout = 4 [default = 0]; - * - *
      -             * in seconds
      -             * 
      - */ - public Builder clearNetWriteTimeout() { - bitField0_ = (bitField0_ & ~0x00000008); - netWriteTimeout_ = 0; - onChanged(); - return this; - } - - private java.lang.Object destination_ = ""; - - /** - * optional string destination = 5; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional string destination = 5; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string destination = 5; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string destination = 5; - */ - public Builder setDestination(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000010; - destination_ = value; - onChanged(); - return this; - } - - /** - * optional string destination = 5; - */ - public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000010); - destination_ = getDefaultInstance().getDestination(); - onChanged(); - return this; - } - - /** - * optional string destination = 5; - */ - public Builder setDestinationBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000010; - destination_ = value; - onChanged(); - return this; - } - - private java.lang.Object clientId_ = ""; - - /** - * optional string client_id = 6; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional string client_id = 6; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string client_id = 6; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string client_id = 6; - */ - public Builder setClientId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000020; - clientId_ = value; - onChanged(); - return this; - } - - /** - * optional string client_id = 6; - */ - public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000020); - clientId_ = getDefaultInstance().getClientId(); - onChanged(); - return this; - } - - /** - * optional string client_id = 6; - */ - public Builder setClientIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000020; - clientId_ = value; - onChanged(); - return this; - } - - private java.lang.Object filter_ = ""; - - /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - - /** - * optional string filter = 7; - */ - public java.lang.String getFilter() { - java.lang.Object ref = filter_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string filter = 7; - */ - public com.google.protobuf.ByteString getFilterBytes() { - java.lang.Object ref = filter_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - filter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string filter = 7; - */ - public Builder setFilter(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000040; - filter_ = value; - onChanged(); - return this; - } - - /** - * optional string filter = 7; - */ - public Builder clearFilter() { - bitField0_ = (bitField0_ & ~0x00000040); - filter_ = getDefaultInstance().getFilter(); - onChanged(); - return this; - } - - /** - * optional string filter = 7; - */ - public Builder setFilterBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000040; - filter_ = value; - onChanged(); - return this; - } - - private long startTimestamp_; - - /** - * optional int64 start_timestamp = 8; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - - /** - * optional int64 start_timestamp = 8; - */ - public long getStartTimestamp() { - return startTimestamp_; - } - - /** - * optional int64 start_timestamp = 8; - */ - public Builder setStartTimestamp(long value) { - bitField0_ |= 0x00000080; - startTimestamp_ = value; - onChanged(); - return this; - } - - /** - * optional int64 start_timestamp = 8; - */ - public Builder clearStartTimestamp() { - bitField0_ = (bitField0_ & ~0x00000080); - startTimestamp_ = 0L; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.ClientAuth) + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.Builder.class); + } + + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + username_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + password_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + netReadTimeout_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); + netWriteTimeout_ = 0; + bitField0_ = (bitField0_ & ~0x00000008); + destination_ = ""; + bitField0_ = (bitField0_ & ~0x00000010); + clientId_ = ""; + bitField0_ = (bitField0_ & ~0x00000020); + filter_ = ""; + bitField0_ = (bitField0_ & ~0x00000040); + startTimestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000080); + return this; + } - static { - defaultInstance = new ClientAuth(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth build() { + com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.ClientAuth) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.username_ = username_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.password_ = password_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.netReadTimeout_ = netReadTimeout_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.netWriteTimeout_ = netWriteTimeout_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.destination_ = destination_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + result.clientId_ = clientId_; + if (((from_bitField0_ & 0x00000040) == 0x00000040)) { + to_bitField0_ |= 0x00000040; + } + result.filter_ = filter_; + if (((from_bitField0_ & 0x00000080) == 0x00000080)) { + to_bitField0_ |= 0x00000080; + } + result.startTimestamp_ = startTimestamp_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.getDefaultInstance()) return this; + if (other.hasUsername()) { + bitField0_ |= 0x00000001; + username_ = other.username_; + onChanged(); + } + if (other.hasPassword()) { + setPassword(other.getPassword()); + } + if (other.hasNetReadTimeout()) { + setNetReadTimeout(other.getNetReadTimeout()); + } + if (other.hasNetWriteTimeout()) { + setNetWriteTimeout(other.getNetWriteTimeout()); + } + if (other.hasDestination()) { + bitField0_ |= 0x00000010; + destination_ = other.destination_; + onChanged(); + } + if (other.hasClientId()) { + bitField0_ |= 0x00000020; + clientId_ = other.clientId_; + onChanged(); + } + if (other.hasFilter()) { + bitField0_ |= 0x00000040; + filter_ = other.filter_; + onChanged(); + } + if (other.hasStartTimestamp()) { + setStartTimestamp(other.getStartTimestamp()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object username_ = ""; + /** + * optional string username = 1; + */ + public boolean hasUsername() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string username = 1; + */ + public java.lang.String getUsername() { + java.lang.Object ref = username_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + username_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string username = 1; + */ + public com.google.protobuf.ByteString + getUsernameBytes() { + java.lang.Object ref = username_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + username_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string username = 1; + */ + public Builder setUsername( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + username_ = value; + onChanged(); + return this; + } + /** + * optional string username = 1; + */ + public Builder clearUsername() { + bitField0_ = (bitField0_ & ~0x00000001); + username_ = getDefaultInstance().getUsername(); + onChanged(); + return this; + } + /** + * optional string username = 1; + */ + public Builder setUsernameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + username_ = value; + onChanged(); + return this; + } + + private com.google.protobuf.ByteString password_ = com.google.protobuf.ByteString.EMPTY; + /** + *
      +       * hashed password with seeds from Handshake message
      +       * 
      + * + * optional bytes password = 2; + */ + public boolean hasPassword() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + *
      +       * hashed password with seeds from Handshake message
      +       * 
      + * + * optional bytes password = 2; + */ + public com.google.protobuf.ByteString getPassword() { + return password_; + } + /** + *
      +       * hashed password with seeds from Handshake message
      +       * 
      + * + * optional bytes password = 2; + */ + public Builder setPassword(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + password_ = value; + onChanged(); + return this; + } + /** + *
      +       * hashed password with seeds from Handshake message
      +       * 
      + * + * optional bytes password = 2; + */ + public Builder clearPassword() { + bitField0_ = (bitField0_ & ~0x00000002); + password_ = getDefaultInstance().getPassword(); + onChanged(); + return this; + } + + private int netReadTimeout_ ; + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + public boolean hasNetReadTimeout() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + public int getNetReadTimeout() { + return netReadTimeout_; + } + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + public Builder setNetReadTimeout(int value) { + bitField0_ |= 0x00000004; + netReadTimeout_ = value; + onChanged(); + return this; + } + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_read_timeout = 3 [default = 0]; + */ + public Builder clearNetReadTimeout() { + bitField0_ = (bitField0_ & ~0x00000004); + netReadTimeout_ = 0; + onChanged(); + return this; + } + + private int netWriteTimeout_ ; + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + public boolean hasNetWriteTimeout() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + public int getNetWriteTimeout() { + return netWriteTimeout_; + } + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + public Builder setNetWriteTimeout(int value) { + bitField0_ |= 0x00000008; + netWriteTimeout_ = value; + onChanged(); + return this; + } + /** + *
      +       * in seconds
      +       * 
      + * + * optional int32 net_write_timeout = 4 [default = 0]; + */ + public Builder clearNetWriteTimeout() { + bitField0_ = (bitField0_ & ~0x00000008); + netWriteTimeout_ = 0; + onChanged(); + return this; + } + + private java.lang.Object destination_ = ""; + /** + * optional string destination = 5; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + * optional string destination = 5; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string destination = 5; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string destination = 5; + */ + public Builder setDestination( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + destination_ = value; + onChanged(); + return this; + } + /** + * optional string destination = 5; + */ + public Builder clearDestination() { + bitField0_ = (bitField0_ & ~0x00000010); + destination_ = getDefaultInstance().getDestination(); + onChanged(); + return this; + } + /** + * optional string destination = 5; + */ + public Builder setDestinationBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000010; + destination_ = value; + onChanged(); + return this; + } + + private java.lang.Object clientId_ = ""; + /** + * optional string client_id = 6; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional string client_id = 6; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string client_id = 6; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string client_id = 6; + */ + public Builder setClientId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000020; + clientId_ = value; + onChanged(); + return this; + } + /** + * optional string client_id = 6; + */ + public Builder clearClientId() { + bitField0_ = (bitField0_ & ~0x00000020); + clientId_ = getDefaultInstance().getClientId(); + onChanged(); + return this; + } + /** + * optional string client_id = 6; + */ + public Builder setClientIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000020; + clientId_ = value; + onChanged(); + return this; + } + + private java.lang.Object filter_ = ""; + /** + * optional string filter = 7; + */ + public boolean hasFilter() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional string filter = 7; + */ + public java.lang.String getFilter() { + java.lang.Object ref = filter_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + filter_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string filter = 7; + */ + public com.google.protobuf.ByteString + getFilterBytes() { + java.lang.Object ref = filter_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + filter_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string filter = 7; + */ + public Builder setFilter( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000040; + filter_ = value; + onChanged(); + return this; + } + /** + * optional string filter = 7; + */ + public Builder clearFilter() { + bitField0_ = (bitField0_ & ~0x00000040); + filter_ = getDefaultInstance().getFilter(); + onChanged(); + return this; + } + /** + * optional string filter = 7; + */ + public Builder setFilterBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000040; + filter_ = value; + onChanged(); + return this; + } + + private long startTimestamp_ ; + /** + * optional int64 start_timestamp = 8; + */ + public boolean hasStartTimestamp() { + return ((bitField0_ & 0x00000080) == 0x00000080); + } + /** + * optional int64 start_timestamp = 8; + */ + public long getStartTimestamp() { + return startTimestamp_; + } + /** + * optional int64 start_timestamp = 8; + */ + public Builder setStartTimestamp(long value) { + bitField0_ |= 0x00000080; + startTimestamp_ = value; + onChanged(); + return this; + } + /** + * optional int64 start_timestamp = 8; + */ + public Builder clearStartTimestamp() { + bitField0_ = (bitField0_ & ~0x00000080); + startTimestamp_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.ClientAuth) } - public interface AckOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Ack) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int32 error_code = 1 [default = 0]; - */ - boolean hasErrorCode(); - - /** - * optional int32 error_code = 1 [default = 0]; - */ - int getErrorCode(); - - /** - * optional string error_message = 2; - * - *
      -         * if something like compression is not supported, erorr_message will tell about it.
      -         * 
      - */ - boolean hasErrorMessage(); - - /** - * optional string error_message = 2; - * - *
      -         * if something like compression is not supported, erorr_message will tell about it.
      -         * 
      - */ - java.lang.String getErrorMessage(); - - /** - * optional string error_message = 2; - * - *
      -         * if something like compression is not supported, erorr_message will tell about it.
      -         * 
      - */ - com.google.protobuf.ByteString getErrorMessageBytes(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.ClientAuth) + private static final com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ClientAuth parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ClientAuth(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface AckOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Ack) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int32 error_code = 1 [default = 0]; + */ + boolean hasErrorCode(); + /** + * optional int32 error_code = 1 [default = 0]; + */ + int getErrorCode(); + + /** + *
      +     * if something like compression is not supported, erorr_message will tell about it.
      +     * 
      + * + * optional string error_message = 2; + */ + boolean hasErrorMessage(); + /** + *
      +     * if something like compression is not supported, erorr_message will tell about it.
      +     * 
      + * + * optional string error_message = 2; + */ + java.lang.String getErrorMessage(); + /** + *
      +     * if something like compression is not supported, erorr_message will tell about it.
      +     * 
      + * + * optional string error_message = 2; + */ + com.google.protobuf.ByteString + getErrorMessageBytes(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.Ack} + */ + public static final class Ack extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Ack) + AckOrBuilder { + private static final long serialVersionUID = 0L; + // Use Ack.newBuilder() to construct. + private Ack(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Ack() { + errorCode_ = 0; + errorMessage_ = ""; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Ack( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + bitField0_ |= 0x00000001; + errorCode_ = input.readInt32(); + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + errorMessage_ = bs; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Ack.class, com.alibaba.otter.canal.protocol.CanalPacket.Ack.Builder.class); + } + + private int bitField0_; + public static final int ERROR_CODE_FIELD_NUMBER = 1; + private int errorCode_; + /** + * optional int32 error_code = 1 [default = 0]; + */ + public boolean hasErrorCode() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 error_code = 1 [default = 0]; + */ + public int getErrorCode() { + return errorCode_; + } + + public static final int ERROR_MESSAGE_FIELD_NUMBER = 2; + private volatile java.lang.Object errorMessage_; + /** + *
      +     * if something like compression is not supported, erorr_message will tell about it.
      +     * 
      + * + * optional string error_message = 2; + */ + public boolean hasErrorMessage() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + *
      +     * if something like compression is not supported, erorr_message will tell about it.
      +     * 
      + * + * optional string error_message = 2; + */ + public java.lang.String getErrorMessage() { + java.lang.Object ref = errorMessage_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + errorMessage_ = s; + } + return s; + } + } + /** + *
      +     * if something like compression is not supported, erorr_message will tell about it.
      +     * 
      + * + * optional string error_message = 2; + */ + public com.google.protobuf.ByteString + getErrorMessageBytes() { + java.lang.Object ref = errorMessage_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + errorMessage_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt32(1, errorCode_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, errorMessage_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, errorCode_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, errorMessage_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Ack)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Ack other = (com.alibaba.otter.canal.protocol.CanalPacket.Ack) obj; + + boolean result = true; + result = result && (hasErrorCode() == other.hasErrorCode()); + if (hasErrorCode()) { + result = result && (getErrorCode() + == other.getErrorCode()); + } + result = result && (hasErrorMessage() == other.hasErrorMessage()); + if (hasErrorMessage()) { + result = result && getErrorMessage() + .equals(other.getErrorMessage()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasErrorCode()) { + hash = (37 * hash) + ERROR_CODE_FIELD_NUMBER; + hash = (53 * hash) + getErrorCode(); + } + if (hasErrorMessage()) { + hash = (37 * hash) + ERROR_MESSAGE_FIELD_NUMBER; + hash = (53 * hash) + getErrorMessage().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Ack prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Ack} */ - public static final class Ack extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Ack) - AckOrBuilder { - - // Use Ack.newBuilder() to construct. - private Ack(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Ack(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Ack defaultInstance; - - public static Ack getDefaultInstance() { - return defaultInstance; - } - - public Ack getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Ack(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - errorCode_ = input.readInt32(); - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - errorMessage_ = bs; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Ack.class, - com.alibaba.otter.canal.protocol.CanalPacket.Ack.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Ack parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Ack(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int ERROR_CODE_FIELD_NUMBER = 1; - private int errorCode_; - - /** - * optional int32 error_code = 1 [default = 0]; - */ - public boolean hasErrorCode() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 error_code = 1 [default = 0]; - */ - public int getErrorCode() { - return errorCode_; - } - - public static final int ERROR_MESSAGE_FIELD_NUMBER = 2; - private java.lang.Object errorMessage_; - - /** - * optional string error_message = 2; - * - *
      -         * if something like compression is not supported, erorr_message will tell about it.
      -         * 
      - */ - public boolean hasErrorMessage() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string error_message = 2; - * - *
      -         * if something like compression is not supported, erorr_message will tell about it.
      -         * 
      - */ - public java.lang.String getErrorMessage() { - java.lang.Object ref = errorMessage_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - errorMessage_ = s; - } - return s; - } - } - - /** - * optional string error_message = 2; - * - *
      -         * if something like compression is not supported, erorr_message will tell about it.
      -         * 
      - */ - public com.google.protobuf.ByteString getErrorMessageBytes() { - java.lang.Object ref = errorMessage_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - errorMessage_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - errorCode_ = 0; - errorMessage_ = ""; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt32(1, errorCode_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getErrorMessageBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(1, errorCode_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getErrorMessageBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Ack parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Ack prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Ack} - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Ack) com.alibaba.otter.canal.protocol.CanalPacket.AckOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Ack.class, com.alibaba.otter.canal.protocol.CanalPacket.Ack.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Ack.class, - com.alibaba.otter.canal.protocol.CanalPacket.Ack.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Ack.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Ack.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - errorCode_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); - errorMessage_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Ack getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Ack.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Ack build() { - com.alibaba.otter.canal.protocol.CanalPacket.Ack result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Ack buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Ack result = new com.alibaba.otter.canal.protocol.CanalPacket.Ack(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.errorCode_ = errorCode_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.errorMessage_ = errorMessage_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Ack) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Ack) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Ack other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Ack.getDefaultInstance()) return this; - if (other.hasErrorCode()) { - setErrorCode(other.getErrorCode()); - } - if (other.hasErrorMessage()) { - bitField0_ |= 0x00000002; - errorMessage_ = other.errorMessage_; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Ack parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Ack) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private int errorCode_; - - /** - * optional int32 error_code = 1 [default = 0]; - */ - public boolean hasErrorCode() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int32 error_code = 1 [default = 0]; - */ - public int getErrorCode() { - return errorCode_; - } - - /** - * optional int32 error_code = 1 [default = 0]; - */ - public Builder setErrorCode(int value) { - bitField0_ |= 0x00000001; - errorCode_ = value; - onChanged(); - return this; - } - - /** - * optional int32 error_code = 1 [default = 0]; - */ - public Builder clearErrorCode() { - bitField0_ = (bitField0_ & ~0x00000001); - errorCode_ = 0; - onChanged(); - return this; - } - - private java.lang.Object errorMessage_ = ""; - - /** - * optional string error_message = 2; - * - *
      -             * if something like compression is not supported, erorr_message will tell about it.
      -             * 
      - */ - public boolean hasErrorMessage() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string error_message = 2; - * - *
      -             * if something like compression is not supported, erorr_message will tell about it.
      -             * 
      - */ - public java.lang.String getErrorMessage() { - java.lang.Object ref = errorMessage_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - errorMessage_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string error_message = 2; - * - *
      -             * if something like compression is not supported, erorr_message will tell about it.
      -             * 
      - */ - public com.google.protobuf.ByteString getErrorMessageBytes() { - java.lang.Object ref = errorMessage_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - errorMessage_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string error_message = 2; - * - *
      -             * if something like compression is not supported, erorr_message will tell about it.
      -             * 
      - */ - public Builder setErrorMessage(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - errorMessage_ = value; - onChanged(); - return this; - } - - /** - * optional string error_message = 2; - * - *
      -             * if something like compression is not supported, erorr_message will tell about it.
      -             * 
      - */ - public Builder clearErrorMessage() { - bitField0_ = (bitField0_ & ~0x00000002); - errorMessage_ = getDefaultInstance().getErrorMessage(); - onChanged(); - return this; - } - - /** - * optional string error_message = 2; - * - *
      -             * if something like compression is not supported, erorr_message will tell about it.
      -             * 
      - */ - public Builder setErrorMessageBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - errorMessage_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Ack) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + errorCode_ = 0; + bitField0_ = (bitField0_ & ~0x00000001); + errorMessage_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } - static { - defaultInstance = new Ack(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Ack getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Ack.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Ack build() { + com.alibaba.otter.canal.protocol.CanalPacket.Ack result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Ack) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Ack buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Ack result = new com.alibaba.otter.canal.protocol.CanalPacket.Ack(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.errorCode_ = errorCode_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.errorMessage_ = errorMessage_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Ack) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Ack)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Ack other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Ack.getDefaultInstance()) return this; + if (other.hasErrorCode()) { + setErrorCode(other.getErrorCode()); + } + if (other.hasErrorMessage()) { + bitField0_ |= 0x00000002; + errorMessage_ = other.errorMessage_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Ack parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Ack) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private int errorCode_ ; + /** + * optional int32 error_code = 1 [default = 0]; + */ + public boolean hasErrorCode() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int32 error_code = 1 [default = 0]; + */ + public int getErrorCode() { + return errorCode_; + } + /** + * optional int32 error_code = 1 [default = 0]; + */ + public Builder setErrorCode(int value) { + bitField0_ |= 0x00000001; + errorCode_ = value; + onChanged(); + return this; + } + /** + * optional int32 error_code = 1 [default = 0]; + */ + public Builder clearErrorCode() { + bitField0_ = (bitField0_ & ~0x00000001); + errorCode_ = 0; + onChanged(); + return this; + } + + private java.lang.Object errorMessage_ = ""; + /** + *
      +       * if something like compression is not supported, erorr_message will tell about it.
      +       * 
      + * + * optional string error_message = 2; + */ + public boolean hasErrorMessage() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + *
      +       * if something like compression is not supported, erorr_message will tell about it.
      +       * 
      + * + * optional string error_message = 2; + */ + public java.lang.String getErrorMessage() { + java.lang.Object ref = errorMessage_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + errorMessage_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + *
      +       * if something like compression is not supported, erorr_message will tell about it.
      +       * 
      + * + * optional string error_message = 2; + */ + public com.google.protobuf.ByteString + getErrorMessageBytes() { + java.lang.Object ref = errorMessage_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + errorMessage_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + *
      +       * if something like compression is not supported, erorr_message will tell about it.
      +       * 
      + * + * optional string error_message = 2; + */ + public Builder setErrorMessage( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + errorMessage_ = value; + onChanged(); + return this; + } + /** + *
      +       * if something like compression is not supported, erorr_message will tell about it.
      +       * 
      + * + * optional string error_message = 2; + */ + public Builder clearErrorMessage() { + bitField0_ = (bitField0_ & ~0x00000002); + errorMessage_ = getDefaultInstance().getErrorMessage(); + onChanged(); + return this; + } + /** + *
      +       * if something like compression is not supported, erorr_message will tell about it.
      +       * 
      + * + * optional string error_message = 2; + */ + public Builder setErrorMessageBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + errorMessage_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Ack) } - public interface ClientAckOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.ClientAck) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string destination = 1; - */ - boolean hasDestination(); - - /** - * optional string destination = 1; - */ - java.lang.String getDestination(); - - /** - * optional string destination = 1; - */ - com.google.protobuf.ByteString getDestinationBytes(); - - /** - * optional string client_id = 2; - */ - boolean hasClientId(); - - /** - * optional string client_id = 2; - */ - java.lang.String getClientId(); - - /** - * optional string client_id = 2; - */ - com.google.protobuf.ByteString getClientIdBytes(); - - /** - * optional int64 batch_id = 3; - */ - boolean hasBatchId(); - - /** - * optional int64 batch_id = 3; - */ - long getBatchId(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Ack) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Ack DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Ack(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Ack getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Ack parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Ack(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Ack getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ClientAckOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.ClientAck) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string destination = 1; + */ + boolean hasDestination(); + /** + * optional string destination = 1; + */ + java.lang.String getDestination(); + /** + * optional string destination = 1; + */ + com.google.protobuf.ByteString + getDestinationBytes(); + + /** + * optional string client_id = 2; + */ + boolean hasClientId(); + /** + * optional string client_id = 2; + */ + java.lang.String getClientId(); + /** + * optional string client_id = 2; + */ + com.google.protobuf.ByteString + getClientIdBytes(); + + /** + * optional int64 batch_id = 3; + */ + boolean hasBatchId(); + /** + * optional int64 batch_id = 3; + */ + long getBatchId(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientAck} + */ + public static final class ClientAck extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.ClientAck) + ClientAckOrBuilder { + private static final long serialVersionUID = 0L; + // Use ClientAck.newBuilder() to construct. + private ClientAck(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ClientAck() { + destination_ = ""; + clientId_ = ""; + batchId_ = 0L; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ClientAck( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + destination_ = bs; + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + clientId_ = bs; + break; + } + case 24: { + bitField0_ |= 0x00000004; + batchId_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.Builder.class); + } + + private int bitField0_; + public static final int DESTINATION_FIELD_NUMBER = 1; + private volatile java.lang.Object destination_; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CLIENT_ID_FIELD_NUMBER = 2; + private volatile java.lang.Object clientId_; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int BATCH_ID_FIELD_NUMBER = 3; + private long batchId_; + /** + * optional int64 batch_id = 3; + */ + public boolean hasBatchId() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 batch_id = 3; + */ + public long getBatchId() { + return batchId_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt64(3, batchId_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(3, batchId_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientAck)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.ClientAck other = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAck) obj; + + boolean result = true; + result = result && (hasDestination() == other.hasDestination()); + if (hasDestination()) { + result = result && getDestination() + .equals(other.getDestination()); + } + result = result && (hasClientId() == other.hasClientId()); + if (hasClientId()) { + result = result && getClientId() + .equals(other.getClientId()); + } + result = result && (hasBatchId() == other.hasBatchId()); + if (hasBatchId()) { + result = result && (getBatchId() + == other.getBatchId()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasDestination()) { + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + } + if (hasClientId()) { + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + } + if (hasBatchId()) { + hash = (37 * hash) + BATCH_ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getBatchId()); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.ClientAck prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientAck} */ - public static final class ClientAck extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.ClientAck) - ClientAckOrBuilder { - - // Use ClientAck.newBuilder() to construct. - private ClientAck(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private ClientAck(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final ClientAck defaultInstance; - - public static ClientAck getDefaultInstance() { - return defaultInstance; - } - - public ClientAck getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private ClientAck(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; - break; - } - case 24: { - bitField0_ |= 0x00000004; - batchId_ = input.readInt64(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.class, - com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public ClientAck parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new ClientAck(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int DESTINATION_FIELD_NUMBER = 1; - private java.lang.Object destination_; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int CLIENT_ID_FIELD_NUMBER = 2; - private java.lang.Object clientId_; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int BATCH_ID_FIELD_NUMBER = 3; - private long batchId_; - - /** - * optional int64 batch_id = 3; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 batch_id = 3; - */ - public long getBatchId() { - return batchId_; - } - - private void initFields() { - destination_ = ""; - clientId_ = ""; - batchId_ = 0L; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt64(3, batchId_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(3, batchId_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.ClientAck prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientAck} - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.ClientAck) com.alibaba.otter.canal.protocol.CanalPacket.ClientAckOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.class, - com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - batchId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck build() { - com.alibaba.otter.canal.protocol.CanalPacket.ClientAck result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.ClientAck result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAck(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.batchId_ = batchId_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientAck) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.ClientAck) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientAck other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; - destination_ = other.destination_; - onChanged(); - } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; - clientId_ = other.clientId_; - onChanged(); - } - if (other.hasBatchId()) { - setBatchId(other.getBatchId()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAck) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object destination_ = ""; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string destination = 1; - */ - public Builder setDestination(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); - destination_ = getDefaultInstance().getDestination(); - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder setDestinationBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - private java.lang.Object clientId_ = ""; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string client_id = 2; - */ - public Builder setClientId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); - clientId_ = getDefaultInstance().getClientId(); - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder setClientIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - private long batchId_; - - /** - * optional int64 batch_id = 3; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 batch_id = 3; - */ - public long getBatchId() { - return batchId_; - } - - /** - * optional int64 batch_id = 3; - */ - public Builder setBatchId(long value) { - bitField0_ |= 0x00000004; - batchId_ = value; - onChanged(); - return this; - } - - /** - * optional int64 batch_id = 3; - */ - public Builder clearBatchId() { - bitField0_ = (bitField0_ & ~0x00000004); - batchId_ = 0L; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.ClientAck) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + destination_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + batchId_ = 0L; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } - static { - defaultInstance = new ClientAck(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck build() { + com.alibaba.otter.canal.protocol.CanalPacket.ClientAck result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.ClientAck) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.ClientAck result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAck(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.destination_ = destination_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.clientId_ = clientId_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.batchId_ = batchId_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientAck) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.ClientAck)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientAck other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.getDefaultInstance()) return this; + if (other.hasDestination()) { + bitField0_ |= 0x00000001; + destination_ = other.destination_; + onChanged(); + } + if (other.hasClientId()) { + bitField0_ |= 0x00000002; + clientId_ = other.clientId_; + onChanged(); + } + if (other.hasBatchId()) { + setBatchId(other.getBatchId()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.ClientAck parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAck) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object destination_ = ""; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string destination = 1; + */ + public Builder setDestination( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder clearDestination() { + bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder setDestinationBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + + private java.lang.Object clientId_ = ""; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string client_id = 2; + */ + public Builder setClientId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder clearClientId() { + bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder setClientIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + + private long batchId_ ; + /** + * optional int64 batch_id = 3; + */ + public boolean hasBatchId() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 batch_id = 3; + */ + public long getBatchId() { + return batchId_; + } + /** + * optional int64 batch_id = 3; + */ + public Builder setBatchId(long value) { + bitField0_ |= 0x00000004; + batchId_ = value; + onChanged(); + return this; + } + /** + * optional int64 batch_id = 3; + */ + public Builder clearBatchId() { + bitField0_ = (bitField0_ & ~0x00000004); + batchId_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.ClientAck) } - public interface SubOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Sub) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string destination = 1; - */ - boolean hasDestination(); - - /** - * optional string destination = 1; - */ - java.lang.String getDestination(); - - /** - * optional string destination = 1; - */ - com.google.protobuf.ByteString getDestinationBytes(); - - /** - * optional string client_id = 2; - */ - boolean hasClientId(); - - /** - * optional string client_id = 2; - */ - java.lang.String getClientId(); - - /** - * optional string client_id = 2; - */ - com.google.protobuf.ByteString getClientIdBytes(); - - /** - * optional string filter = 7; - */ - boolean hasFilter(); - - /** - * optional string filter = 7; - */ - java.lang.String getFilter(); - - /** - * optional string filter = 7; - */ - com.google.protobuf.ByteString getFilterBytes(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.ClientAck) + private static final com.alibaba.otter.canal.protocol.CanalPacket.ClientAck DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAck(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientAck getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ClientAck parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ClientAck(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface SubOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Sub) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string destination = 1; + */ + boolean hasDestination(); + /** + * optional string destination = 1; + */ + java.lang.String getDestination(); + /** + * optional string destination = 1; + */ + com.google.protobuf.ByteString + getDestinationBytes(); + + /** + * optional string client_id = 2; + */ + boolean hasClientId(); + /** + * optional string client_id = 2; + */ + java.lang.String getClientId(); + /** + * optional string client_id = 2; + */ + com.google.protobuf.ByteString + getClientIdBytes(); + + /** + * optional string filter = 7; + */ + boolean hasFilter(); + /** + * optional string filter = 7; + */ + java.lang.String getFilter(); + /** + * optional string filter = 7; + */ + com.google.protobuf.ByteString + getFilterBytes(); + } + /** + *
      +   * subscription
      +   * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Sub} + */ + public static final class Sub extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Sub) + SubOrBuilder { + private static final long serialVersionUID = 0L; + // Use Sub.newBuilder() to construct. + private Sub(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Sub() { + destination_ = ""; + clientId_ = ""; + filter_ = ""; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Sub( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + destination_ = bs; + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + clientId_ = bs; + break; + } + case 58: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000004; + filter_ = bs; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Sub.class, com.alibaba.otter.canal.protocol.CanalPacket.Sub.Builder.class); + } + + private int bitField0_; + public static final int DESTINATION_FIELD_NUMBER = 1; + private volatile java.lang.Object destination_; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CLIENT_ID_FIELD_NUMBER = 2; + private volatile java.lang.Object clientId_; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int FILTER_FIELD_NUMBER = 7; + private volatile java.lang.Object filter_; + /** + * optional string filter = 7; + */ + public boolean hasFilter() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string filter = 7; + */ + public java.lang.String getFilter() { + java.lang.Object ref = filter_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + filter_ = s; + } + return s; + } + } + /** + * optional string filter = 7; + */ + public com.google.protobuf.ByteString + getFilterBytes() { + java.lang.Object ref = filter_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + filter_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 7, filter_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, filter_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Sub)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Sub other = (com.alibaba.otter.canal.protocol.CanalPacket.Sub) obj; + + boolean result = true; + result = result && (hasDestination() == other.hasDestination()); + if (hasDestination()) { + result = result && getDestination() + .equals(other.getDestination()); + } + result = result && (hasClientId() == other.hasClientId()); + if (hasClientId()) { + result = result && getClientId() + .equals(other.getClientId()); + } + result = result && (hasFilter() == other.hasFilter()); + if (hasFilter()) { + result = result && getFilter() + .equals(other.getFilter()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasDestination()) { + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + } + if (hasClientId()) { + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + } + if (hasFilter()) { + hash = (37 * hash) + FILTER_FIELD_NUMBER; + hash = (53 * hash) + getFilter().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Sub prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Sub} - * *
            * subscription
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Sub} */ - public static final class Sub extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Sub) - SubOrBuilder { - - // Use Sub.newBuilder() to construct. - private Sub(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Sub(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Sub defaultInstance; - - public static Sub getDefaultInstance() { - return defaultInstance; - } - - public Sub getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Sub(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; - break; - } - case 58: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000004; - filter_ = bs; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Sub.class, - com.alibaba.otter.canal.protocol.CanalPacket.Sub.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Sub parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Sub(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int DESTINATION_FIELD_NUMBER = 1; - private java.lang.Object destination_; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int CLIENT_ID_FIELD_NUMBER = 2; - private java.lang.Object clientId_; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int FILTER_FIELD_NUMBER = 7; - private java.lang.Object filter_; - - /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional string filter = 7; - */ - public java.lang.String getFilter() { - java.lang.Object ref = filter_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } - return s; - } - } - - /** - * optional string filter = 7; - */ - public com.google.protobuf.ByteString getFilterBytes() { - java.lang.Object ref = filter_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - filter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - destination_ = ""; - clientId_ = ""; - filter_ = ""; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(7, getFilterBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(7, getFilterBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Sub parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Sub prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Sub} - * - *
      -         * subscription
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Sub) com.alibaba.otter.canal.protocol.CanalPacket.SubOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Sub.class, com.alibaba.otter.canal.protocol.CanalPacket.Sub.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Sub.class, - com.alibaba.otter.canal.protocol.CanalPacket.Sub.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Sub.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Sub.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - filter_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Sub getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Sub.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Sub build() { - com.alibaba.otter.canal.protocol.CanalPacket.Sub result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Sub buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Sub result = new com.alibaba.otter.canal.protocol.CanalPacket.Sub(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.filter_ = filter_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Sub) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Sub) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Sub other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Sub.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; - destination_ = other.destination_; - onChanged(); - } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; - clientId_ = other.clientId_; - onChanged(); - } - if (other.hasFilter()) { - bitField0_ |= 0x00000004; - filter_ = other.filter_; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Sub parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Sub) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object destination_ = ""; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string destination = 1; - */ - public Builder setDestination(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); - destination_ = getDefaultInstance().getDestination(); - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder setDestinationBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - private java.lang.Object clientId_ = ""; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string client_id = 2; - */ - public Builder setClientId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); - clientId_ = getDefaultInstance().getClientId(); - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder setClientIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - private java.lang.Object filter_ = ""; - - /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional string filter = 7; - */ - public java.lang.String getFilter() { - java.lang.Object ref = filter_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string filter = 7; - */ - public com.google.protobuf.ByteString getFilterBytes() { - java.lang.Object ref = filter_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - filter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string filter = 7; - */ - public Builder setFilter(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - filter_ = value; - onChanged(); - return this; - } - - /** - * optional string filter = 7; - */ - public Builder clearFilter() { - bitField0_ = (bitField0_ & ~0x00000004); - filter_ = getDefaultInstance().getFilter(); - onChanged(); - return this; - } - - /** - * optional string filter = 7; - */ - public Builder setFilterBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - filter_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Sub) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + destination_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + filter_ = ""; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } - static { - defaultInstance = new Sub(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Sub getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Sub.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Sub build() { + com.alibaba.otter.canal.protocol.CanalPacket.Sub result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Sub) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Sub buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Sub result = new com.alibaba.otter.canal.protocol.CanalPacket.Sub(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.destination_ = destination_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.clientId_ = clientId_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.filter_ = filter_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Sub) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Sub)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Sub other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Sub.getDefaultInstance()) return this; + if (other.hasDestination()) { + bitField0_ |= 0x00000001; + destination_ = other.destination_; + onChanged(); + } + if (other.hasClientId()) { + bitField0_ |= 0x00000002; + clientId_ = other.clientId_; + onChanged(); + } + if (other.hasFilter()) { + bitField0_ |= 0x00000004; + filter_ = other.filter_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Sub parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Sub) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object destination_ = ""; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string destination = 1; + */ + public Builder setDestination( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder clearDestination() { + bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder setDestinationBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + + private java.lang.Object clientId_ = ""; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string client_id = 2; + */ + public Builder setClientId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder clearClientId() { + bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder setClientIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + + private java.lang.Object filter_ = ""; + /** + * optional string filter = 7; + */ + public boolean hasFilter() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string filter = 7; + */ + public java.lang.String getFilter() { + java.lang.Object ref = filter_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + filter_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string filter = 7; + */ + public com.google.protobuf.ByteString + getFilterBytes() { + java.lang.Object ref = filter_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + filter_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string filter = 7; + */ + public Builder setFilter( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + filter_ = value; + onChanged(); + return this; + } + /** + * optional string filter = 7; + */ + public Builder clearFilter() { + bitField0_ = (bitField0_ & ~0x00000004); + filter_ = getDefaultInstance().getFilter(); + onChanged(); + return this; + } + /** + * optional string filter = 7; + */ + public Builder setFilterBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + filter_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Sub) } - public interface UnsubOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Unsub) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string destination = 1; - */ - boolean hasDestination(); - - /** - * optional string destination = 1; - */ - java.lang.String getDestination(); - - /** - * optional string destination = 1; - */ - com.google.protobuf.ByteString getDestinationBytes(); - - /** - * optional string client_id = 2; - */ - boolean hasClientId(); - - /** - * optional string client_id = 2; - */ - java.lang.String getClientId(); - - /** - * optional string client_id = 2; - */ - com.google.protobuf.ByteString getClientIdBytes(); - - /** - * optional string filter = 7; - */ - boolean hasFilter(); - - /** - * optional string filter = 7; - */ - java.lang.String getFilter(); - - /** - * optional string filter = 7; - */ - com.google.protobuf.ByteString getFilterBytes(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Sub) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Sub DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Sub(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Sub getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Sub parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Sub(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Sub getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface UnsubOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Unsub) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string destination = 1; + */ + boolean hasDestination(); + /** + * optional string destination = 1; + */ + java.lang.String getDestination(); + /** + * optional string destination = 1; + */ + com.google.protobuf.ByteString + getDestinationBytes(); + + /** + * optional string client_id = 2; + */ + boolean hasClientId(); + /** + * optional string client_id = 2; + */ + java.lang.String getClientId(); + /** + * optional string client_id = 2; + */ + com.google.protobuf.ByteString + getClientIdBytes(); + + /** + * optional string filter = 7; + */ + boolean hasFilter(); + /** + * optional string filter = 7; + */ + java.lang.String getFilter(); + /** + * optional string filter = 7; + */ + com.google.protobuf.ByteString + getFilterBytes(); + } + /** + *
      +   * Unsubscription
      +   * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Unsub} + */ + public static final class Unsub extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Unsub) + UnsubOrBuilder { + private static final long serialVersionUID = 0L; + // Use Unsub.newBuilder() to construct. + private Unsub(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Unsub() { + destination_ = ""; + clientId_ = ""; + filter_ = ""; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Unsub( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + destination_ = bs; + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + clientId_ = bs; + break; + } + case 58: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000004; + filter_ = bs; + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Unsub.class, com.alibaba.otter.canal.protocol.CanalPacket.Unsub.Builder.class); + } + + private int bitField0_; + public static final int DESTINATION_FIELD_NUMBER = 1; + private volatile java.lang.Object destination_; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CLIENT_ID_FIELD_NUMBER = 2; + private volatile java.lang.Object clientId_; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int FILTER_FIELD_NUMBER = 7; + private volatile java.lang.Object filter_; + /** + * optional string filter = 7; + */ + public boolean hasFilter() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string filter = 7; + */ + public java.lang.String getFilter() { + java.lang.Object ref = filter_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + filter_ = s; + } + return s; + } + } + /** + * optional string filter = 7; + */ + public com.google.protobuf.ByteString + getFilterBytes() { + java.lang.Object ref = filter_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + filter_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 7, filter_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, filter_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Unsub)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Unsub other = (com.alibaba.otter.canal.protocol.CanalPacket.Unsub) obj; + + boolean result = true; + result = result && (hasDestination() == other.hasDestination()); + if (hasDestination()) { + result = result && getDestination() + .equals(other.getDestination()); + } + result = result && (hasClientId() == other.hasClientId()); + if (hasClientId()) { + result = result && getClientId() + .equals(other.getClientId()); + } + result = result && (hasFilter() == other.hasFilter()); + if (hasFilter()) { + result = result && getFilter() + .equals(other.getFilter()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasDestination()) { + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + } + if (hasClientId()) { + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + } + if (hasFilter()) { + hash = (37 * hash) + FILTER_FIELD_NUMBER; + hash = (53 * hash) + getFilter().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Unsub prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Unsub} - * *
            * Unsubscription
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Unsub} */ - public static final class Unsub extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Unsub) - UnsubOrBuilder { - - // Use Unsub.newBuilder() to construct. - private Unsub(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Unsub(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Unsub defaultInstance; - - public static Unsub getDefaultInstance() { - return defaultInstance; - } - - public Unsub getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Unsub(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; - break; - } - case 58: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000004; - filter_ = bs; - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Unsub.class, - com.alibaba.otter.canal.protocol.CanalPacket.Unsub.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Unsub parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Unsub(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int DESTINATION_FIELD_NUMBER = 1; - private java.lang.Object destination_; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int CLIENT_ID_FIELD_NUMBER = 2; - private java.lang.Object clientId_; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int FILTER_FIELD_NUMBER = 7; - private java.lang.Object filter_; - - /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional string filter = 7; - */ - public java.lang.String getFilter() { - java.lang.Object ref = filter_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } - return s; - } - } - - /** - * optional string filter = 7; - */ - public com.google.protobuf.ByteString getFilterBytes() { - java.lang.Object ref = filter_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - filter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - private void initFields() { - destination_ = ""; - clientId_ = ""; - filter_ = ""; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(7, getFilterBytes()); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(7, getFilterBytes()); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Unsub prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Unsub} - * - *
      -         * Unsubscription
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Unsub) com.alibaba.otter.canal.protocol.CanalPacket.UnsubOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Unsub.class, com.alibaba.otter.canal.protocol.CanalPacket.Unsub.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Unsub.class, - com.alibaba.otter.canal.protocol.CanalPacket.Unsub.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Unsub.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Unsub.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - filter_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Unsub getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Unsub.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Unsub build() { - com.alibaba.otter.canal.protocol.CanalPacket.Unsub result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Unsub buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Unsub result = new com.alibaba.otter.canal.protocol.CanalPacket.Unsub(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.filter_ = filter_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Unsub) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Unsub) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Unsub other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Unsub.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; - destination_ = other.destination_; - onChanged(); - } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; - clientId_ = other.clientId_; - onChanged(); - } - if (other.hasFilter()) { - bitField0_ |= 0x00000004; - filter_ = other.filter_; - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Unsub parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Unsub) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object destination_ = ""; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string destination = 1; - */ - public Builder setDestination(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); - destination_ = getDefaultInstance().getDestination(); - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder setDestinationBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - private java.lang.Object clientId_ = ""; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string client_id = 2; - */ - public Builder setClientId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); - clientId_ = getDefaultInstance().getClientId(); - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder setClientIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - private java.lang.Object filter_ = ""; - - /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional string filter = 7; - */ - public java.lang.String getFilter() { - java.lang.Object ref = filter_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string filter = 7; - */ - public com.google.protobuf.ByteString getFilterBytes() { - java.lang.Object ref = filter_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - filter_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string filter = 7; - */ - public Builder setFilter(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - filter_ = value; - onChanged(); - return this; - } - - /** - * optional string filter = 7; - */ - public Builder clearFilter() { - bitField0_ = (bitField0_ & ~0x00000004); - filter_ = getDefaultInstance().getFilter(); - onChanged(); - return this; - } - - /** - * optional string filter = 7; - */ - public Builder setFilterBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - filter_ = value; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Unsub) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + destination_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + filter_ = ""; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } - static { - defaultInstance = new Unsub(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Unsub getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Unsub.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Unsub build() { + com.alibaba.otter.canal.protocol.CanalPacket.Unsub result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Unsub) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Unsub buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Unsub result = new com.alibaba.otter.canal.protocol.CanalPacket.Unsub(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.destination_ = destination_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.clientId_ = clientId_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.filter_ = filter_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Unsub) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Unsub)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Unsub other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Unsub.getDefaultInstance()) return this; + if (other.hasDestination()) { + bitField0_ |= 0x00000001; + destination_ = other.destination_; + onChanged(); + } + if (other.hasClientId()) { + bitField0_ |= 0x00000002; + clientId_ = other.clientId_; + onChanged(); + } + if (other.hasFilter()) { + bitField0_ |= 0x00000004; + filter_ = other.filter_; + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Unsub parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Unsub) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object destination_ = ""; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string destination = 1; + */ + public Builder setDestination( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder clearDestination() { + bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder setDestinationBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + + private java.lang.Object clientId_ = ""; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string client_id = 2; + */ + public Builder setClientId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder clearClientId() { + bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder setClientIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + + private java.lang.Object filter_ = ""; + /** + * optional string filter = 7; + */ + public boolean hasFilter() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional string filter = 7; + */ + public java.lang.String getFilter() { + java.lang.Object ref = filter_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + filter_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string filter = 7; + */ + public com.google.protobuf.ByteString + getFilterBytes() { + java.lang.Object ref = filter_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + filter_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string filter = 7; + */ + public Builder setFilter( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + filter_ = value; + onChanged(); + return this; + } + /** + * optional string filter = 7; + */ + public Builder clearFilter() { + bitField0_ = (bitField0_ & ~0x00000004); + filter_ = getDefaultInstance().getFilter(); + onChanged(); + return this; + } + /** + * optional string filter = 7; + */ + public Builder setFilterBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + filter_ = value; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Unsub) } - public interface GetOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Get) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string destination = 1; - */ - boolean hasDestination(); - - /** - * optional string destination = 1; - */ - java.lang.String getDestination(); - - /** - * optional string destination = 1; - */ - com.google.protobuf.ByteString getDestinationBytes(); - - /** - * optional string client_id = 2; - */ - boolean hasClientId(); - - /** - * optional string client_id = 2; - */ - java.lang.String getClientId(); - - /** - * optional string client_id = 2; - */ - com.google.protobuf.ByteString getClientIdBytes(); - - /** - * optional int32 fetch_size = 3; - */ - boolean hasFetchSize(); - - /** - * optional int32 fetch_size = 3; - */ - int getFetchSize(); - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -         * 默认-1时代表不控制
      -         * 
      - */ - boolean hasTimeout(); - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -         * 默认-1时代表不控制
      -         * 
      - */ - long getTimeout(); - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -         * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -         * 
      - */ - boolean hasUnit(); - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -         * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -         * 
      - */ - int getUnit(); - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -         * 是否自动ack
      -         * 
      - */ - boolean hasAutoAck(); - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -         * 是否自动ack
      -         * 
      - */ - boolean getAutoAck(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Unsub) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Unsub DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Unsub(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Unsub getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Unsub parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Unsub(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Unsub getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface GetOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Get) + com.google.protobuf.MessageOrBuilder { + /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Get} - * - *
      -     * PullRequest
      -     * 
      + * optional string destination = 1; */ - public static final class Get extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Get) - GetOrBuilder { + boolean hasDestination(); + /** + * optional string destination = 1; + */ + java.lang.String getDestination(); + /** + * optional string destination = 1; + */ + com.google.protobuf.ByteString + getDestinationBytes(); - // Use Get.newBuilder() to construct. - private Get(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } + /** + * optional string client_id = 2; + */ + boolean hasClientId(); + /** + * optional string client_id = 2; + */ + java.lang.String getClientId(); + /** + * optional string client_id = 2; + */ + com.google.protobuf.ByteString + getClientIdBytes(); - private Get(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } + /** + * optional int32 fetch_size = 3; + */ + boolean hasFetchSize(); + /** + * optional int32 fetch_size = 3; + */ + int getFetchSize(); - private static final Get defaultInstance; + /** + *
      +     * 默认-1时代表不控制
      +     * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + boolean hasTimeout(); + /** + *
      +     * 默认-1时代表不控制
      +     * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + long getTimeout(); - public static Get getDefaultInstance() { - return defaultInstance; - } + /** + *
      +     * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +     * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + boolean hasUnit(); + /** + *
      +     * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +     * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + int getUnit(); - public Get getDefaultInstanceForType() { - return defaultInstance; - } + /** + *
      +     * 是否自动ack
      +     * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + boolean hasAutoAck(); + /** + *
      +     * 是否自动ack
      +     * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + boolean getAutoAck(); + } + /** + *
      +   *  PullRequest
      +   * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Get} + */ + public static final class Get extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Get) + GetOrBuilder { + private static final long serialVersionUID = 0L; + // Use Get.newBuilder() to construct. + private Get(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Get() { + destination_ = ""; + clientId_ = ""; + fetchSize_ = 0; + timeout_ = -1L; + unit_ = 2; + autoAck_ = false; + } - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Get(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; - break; - } - case 24: { - bitField0_ |= 0x00000004; - fetchSize_ = input.readInt32(); - break; - } - case 32: { - bitField0_ |= 0x00000008; - timeout_ = input.readInt64(); - break; - } - case 40: { - bitField0_ |= 0x00000010; - unit_ = input.readInt32(); - break; - } - case 48: { - bitField0_ |= 0x00000020; - autoAck_ = input.readBool(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Get( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + destination_ = bs; + break; } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Get.class, - com.alibaba.otter.canal.protocol.CanalPacket.Get.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Get parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Get(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int DESTINATION_FIELD_NUMBER = 1; - private java.lang.Object destination_; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + clientId_ = bs; + break; } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; + case 24: { + bitField0_ |= 0x00000004; + fetchSize_ = input.readInt32(); + break; } - } - - public static final int CLIENT_ID_FIELD_NUMBER = 2; - private java.lang.Object clientId_; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; + case 32: { + bitField0_ |= 0x00000008; + timeout_ = input.readInt64(); + break; } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; + case 40: { + bitField0_ |= 0x00000010; + unit_ = input.readInt32(); + break; } - } - - public static final int FETCH_SIZE_FIELD_NUMBER = 3; - private int fetchSize_; - - /** - * optional int32 fetch_size = 3; - */ - public boolean hasFetchSize() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int32 fetch_size = 3; - */ - public int getFetchSize() { - return fetchSize_; - } - - public static final int TIMEOUT_FIELD_NUMBER = 4; - private long timeout_; - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -         * 默认-1时代表不控制
      -         * 
      - */ - public boolean hasTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -         * 默认-1时代表不控制
      -         * 
      - */ - public long getTimeout() { - return timeout_; - } - - public static final int UNIT_FIELD_NUMBER = 5; - private int unit_; - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -         * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -         * 
      - */ - public boolean hasUnit() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -         * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -         * 
      - */ - public int getUnit() { - return unit_; - } - - public static final int AUTO_ACK_FIELD_NUMBER = 6; - private boolean autoAck_; - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -         * 是否自动ack
      -         * 
      - */ - public boolean hasAutoAck() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -         * 是否自动ack
      -         * 
      - */ - public boolean getAutoAck() { - return autoAck_; - } - - private void initFields() { - destination_ = ""; - clientId_ = ""; - fetchSize_ = 0; - timeout_ = -1L; - unit_ = 2; - autoAck_ = false; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getDestinationBytes()); + case 48: { + bitField0_ |= 0x00000020; + autoAck_ = input.readBool(); + break; } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getClientIdBytes()); + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt32(3, fetchSize_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeInt64(4, timeout_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeInt32(5, unit_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBool(6, autoAck_); - } - getUnknownFields().writeTo(output); + } } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; + } - private int memoizedSerializedSize = -1; + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Get.class, com.alibaba.otter.canal.protocol.CanalPacket.Get.Builder.class); + } - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(3, fetchSize_); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(4, timeout_); - } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream.computeInt32Size(5, unit_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - size += com.google.protobuf.CodedOutputStream.computeBoolSize(6, autoAck_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; + private int bitField0_; + public static final int DESTINATION_FIELD_NUMBER = 1; + private volatile java.lang.Object destination_; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; } + return s; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); + public static final int CLIENT_ID_FIELD_NUMBER = 2; + private volatile java.lang.Object clientId_; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; } + return s; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } + public static final int FETCH_SIZE_FIELD_NUMBER = 3; + private int fetchSize_; + /** + * optional int32 fetch_size = 3; + */ + public boolean hasFetchSize() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int32 fetch_size = 3; + */ + public int getFetchSize() { + return fetchSize_; + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } + public static final int TIMEOUT_FIELD_NUMBER = 4; + private long timeout_; + /** + *
      +     * 默认-1时代表不控制
      +     * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + public boolean hasTimeout() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + *
      +     * 默认-1时代表不控制
      +     * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + public long getTimeout() { + return timeout_; + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } + public static final int UNIT_FIELD_NUMBER = 5; + private int unit_; + /** + *
      +     * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +     * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + public boolean hasUnit() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + *
      +     * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +     * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + public int getUnit() { + return unit_; + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } + public static final int AUTO_ACK_FIELD_NUMBER = 6; + private boolean autoAck_; + /** + *
      +     * 是否自动ack
      +     * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + public boolean hasAutoAck() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + *
      +     * 是否自动ack
      +     * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + public boolean getAutoAck() { + return autoAck_; + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } + memoizedIsInitialized = 1; + return true; + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt32(3, fetchSize_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeInt64(4, timeout_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeInt32(5, unit_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBool(6, autoAck_); + } + unknownFields.writeTo(output); + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(3, fetchSize_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(4, timeout_); + } + if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(5, unit_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBoolSize(6, autoAck_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } - public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Get)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Get other = (com.alibaba.otter.canal.protocol.CanalPacket.Get) obj; - public static Builder newBuilder() { - return Builder.create(); - } + boolean result = true; + result = result && (hasDestination() == other.hasDestination()); + if (hasDestination()) { + result = result && getDestination() + .equals(other.getDestination()); + } + result = result && (hasClientId() == other.hasClientId()); + if (hasClientId()) { + result = result && getClientId() + .equals(other.getClientId()); + } + result = result && (hasFetchSize() == other.hasFetchSize()); + if (hasFetchSize()) { + result = result && (getFetchSize() + == other.getFetchSize()); + } + result = result && (hasTimeout() == other.hasTimeout()); + if (hasTimeout()) { + result = result && (getTimeout() + == other.getTimeout()); + } + result = result && (hasUnit() == other.hasUnit()); + if (hasUnit()) { + result = result && (getUnit() + == other.getUnit()); + } + result = result && (hasAutoAck() == other.hasAutoAck()); + if (hasAutoAck()) { + result = result && (getAutoAck() + == other.getAutoAck()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } - public Builder newBuilderForType() { - return newBuilder(); - } + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasDestination()) { + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + } + if (hasClientId()) { + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + } + if (hasFetchSize()) { + hash = (37 * hash) + FETCH_SIZE_FIELD_NUMBER; + hash = (53 * hash) + getFetchSize(); + } + if (hasTimeout()) { + hash = (37 * hash) + TIMEOUT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getTimeout()); + } + if (hasUnit()) { + hash = (37 * hash) + UNIT_FIELD_NUMBER; + hash = (53 * hash) + getUnit(); + } + if (hasAutoAck()) { + hash = (37 * hash) + AUTO_ACK_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getAutoAck()); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Get prototype) { - return newBuilder().mergeFrom(prototype); - } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } - public Builder toBuilder() { - return newBuilder(this); - } + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Get prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Get} - * - *
      -         * PullRequest
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
      +     *  PullRequest
      +     * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Get} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Get) com.alibaba.otter.canal.protocol.CanalPacket.GetOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Get.class, com.alibaba.otter.canal.protocol.CanalPacket.Get.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Get.class, - com.alibaba.otter.canal.protocol.CanalPacket.Get.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Get.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Get.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - fetchSize_ = 0; - bitField0_ = (bitField0_ & ~0x00000004); - timeout_ = -1L; - bitField0_ = (bitField0_ & ~0x00000008); - unit_ = 2; - bitField0_ = (bitField0_ & ~0x00000010); - autoAck_ = false; - bitField0_ = (bitField0_ & ~0x00000020); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Get getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Get.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Get build() { - com.alibaba.otter.canal.protocol.CanalPacket.Get result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Get buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Get result = new com.alibaba.otter.canal.protocol.CanalPacket.Get(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.fetchSize_ = fetchSize_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.timeout_ = timeout_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } - result.unit_ = unit_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } - result.autoAck_ = autoAck_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Get) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Get) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Get other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Get.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; - destination_ = other.destination_; - onChanged(); - } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; - clientId_ = other.clientId_; - onChanged(); - } - if (other.hasFetchSize()) { - setFetchSize(other.getFetchSize()); - } - if (other.hasTimeout()) { - setTimeout(other.getTimeout()); - } - if (other.hasUnit()) { - setUnit(other.getUnit()); - } - if (other.hasAutoAck()) { - setAutoAck(other.getAutoAck()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Get parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Get) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object destination_ = ""; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string destination = 1; - */ - public Builder setDestination(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); - destination_ = getDefaultInstance().getDestination(); - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder setDestinationBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - private java.lang.Object clientId_ = ""; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string client_id = 2; - */ - public Builder setClientId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); - clientId_ = getDefaultInstance().getClientId(); - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder setClientIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - private int fetchSize_; - - /** - * optional int32 fetch_size = 3; - */ - public boolean hasFetchSize() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int32 fetch_size = 3; - */ - public int getFetchSize() { - return fetchSize_; - } - - /** - * optional int32 fetch_size = 3; - */ - public Builder setFetchSize(int value) { - bitField0_ |= 0x00000004; - fetchSize_ = value; - onChanged(); - return this; - } - - /** - * optional int32 fetch_size = 3; - */ - public Builder clearFetchSize() { - bitField0_ = (bitField0_ & ~0x00000004); - fetchSize_ = 0; - onChanged(); - return this; - } - - private long timeout_ = -1L; - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -             * 默认-1时代表不控制
      -             * 
      - */ - public boolean hasTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -             * 默认-1时代表不控制
      -             * 
      - */ - public long getTimeout() { - return timeout_; - } - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -             * 默认-1时代表不控制
      -             * 
      - */ - public Builder setTimeout(long value) { - bitField0_ |= 0x00000008; - timeout_ = value; - onChanged(); - return this; - } - - /** - * optional int64 timeout = 4 [default = -1]; - * - *
      -             * 默认-1时代表不控制
      -             * 
      - */ - public Builder clearTimeout() { - bitField0_ = (bitField0_ & ~0x00000008); - timeout_ = -1L; - onChanged(); - return this; - } - - private int unit_ = 2; - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -             * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -             * 
      - */ - public boolean hasUnit() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -             * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -             * 
      - */ - public int getUnit() { - return unit_; - } - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -             * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -             * 
      - */ - public Builder setUnit(int value) { - bitField0_ |= 0x00000010; - unit_ = value; - onChanged(); - return this; - } - - /** - * optional int32 unit = 5 [default = 2]; - * - *
      -             * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -             * 
      - */ - public Builder clearUnit() { - bitField0_ = (bitField0_ & ~0x00000010); - unit_ = 2; - onChanged(); - return this; - } - - private boolean autoAck_; - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -             * 是否自动ack
      -             * 
      - */ - public boolean hasAutoAck() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -             * 是否自动ack
      -             * 
      - */ - public boolean getAutoAck() { - return autoAck_; - } - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -             * 是否自动ack
      -             * 
      - */ - public Builder setAutoAck(boolean value) { - bitField0_ |= 0x00000020; - autoAck_ = value; - onChanged(); - return this; - } - - /** - * optional bool auto_ack = 6 [default = false]; - * - *
      -             * 是否自动ack
      -             * 
      - */ - public Builder clearAutoAck() { - bitField0_ = (bitField0_ & ~0x00000020); - autoAck_ = false; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Get) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + destination_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + fetchSize_ = 0; + bitField0_ = (bitField0_ & ~0x00000004); + timeout_ = -1L; + bitField0_ = (bitField0_ & ~0x00000008); + unit_ = 2; + bitField0_ = (bitField0_ & ~0x00000010); + autoAck_ = false; + bitField0_ = (bitField0_ & ~0x00000020); + return this; + } - static { - defaultInstance = new Get(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Get getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Get.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Get build() { + com.alibaba.otter.canal.protocol.CanalPacket.Get result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Get) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Get buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Get result = new com.alibaba.otter.canal.protocol.CanalPacket.Get(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.destination_ = destination_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.clientId_ = clientId_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.fetchSize_ = fetchSize_; + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + result.timeout_ = timeout_; + if (((from_bitField0_ & 0x00000010) == 0x00000010)) { + to_bitField0_ |= 0x00000010; + } + result.unit_ = unit_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } + result.autoAck_ = autoAck_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Get) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Get)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Get other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Get.getDefaultInstance()) return this; + if (other.hasDestination()) { + bitField0_ |= 0x00000001; + destination_ = other.destination_; + onChanged(); + } + if (other.hasClientId()) { + bitField0_ |= 0x00000002; + clientId_ = other.clientId_; + onChanged(); + } + if (other.hasFetchSize()) { + setFetchSize(other.getFetchSize()); + } + if (other.hasTimeout()) { + setTimeout(other.getTimeout()); + } + if (other.hasUnit()) { + setUnit(other.getUnit()); + } + if (other.hasAutoAck()) { + setAutoAck(other.getAutoAck()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Get parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Get) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object destination_ = ""; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string destination = 1; + */ + public Builder setDestination( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder clearDestination() { + bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder setDestinationBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + + private java.lang.Object clientId_ = ""; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string client_id = 2; + */ + public Builder setClientId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder clearClientId() { + bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder setClientIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + + private int fetchSize_ ; + /** + * optional int32 fetch_size = 3; + */ + public boolean hasFetchSize() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int32 fetch_size = 3; + */ + public int getFetchSize() { + return fetchSize_; + } + /** + * optional int32 fetch_size = 3; + */ + public Builder setFetchSize(int value) { + bitField0_ |= 0x00000004; + fetchSize_ = value; + onChanged(); + return this; + } + /** + * optional int32 fetch_size = 3; + */ + public Builder clearFetchSize() { + bitField0_ = (bitField0_ & ~0x00000004); + fetchSize_ = 0; + onChanged(); + return this; + } + + private long timeout_ = -1L; + /** + *
      +       * 默认-1时代表不控制
      +       * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + public boolean hasTimeout() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + *
      +       * 默认-1时代表不控制
      +       * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + public long getTimeout() { + return timeout_; + } + /** + *
      +       * 默认-1时代表不控制
      +       * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + public Builder setTimeout(long value) { + bitField0_ |= 0x00000008; + timeout_ = value; + onChanged(); + return this; + } + /** + *
      +       * 默认-1时代表不控制
      +       * 
      + * + * optional int64 timeout = 4 [default = -1]; + */ + public Builder clearTimeout() { + bitField0_ = (bitField0_ & ~0x00000008); + timeout_ = -1L; + onChanged(); + return this; + } + + private int unit_ = 2; + /** + *
      +       * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +       * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + public boolean hasUnit() { + return ((bitField0_ & 0x00000010) == 0x00000010); + } + /** + *
      +       * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +       * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + public int getUnit() { + return unit_; + } + /** + *
      +       * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +       * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + public Builder setUnit(int value) { + bitField0_ |= 0x00000010; + unit_ = value; + onChanged(); + return this; + } + /** + *
      +       * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +       * 
      + * + * optional int32 unit = 5 [default = 2]; + */ + public Builder clearUnit() { + bitField0_ = (bitField0_ & ~0x00000010); + unit_ = 2; + onChanged(); + return this; + } + + private boolean autoAck_ ; + /** + *
      +       * 是否自动ack
      +       * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + public boolean hasAutoAck() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + *
      +       * 是否自动ack
      +       * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + public boolean getAutoAck() { + return autoAck_; + } + /** + *
      +       * 是否自动ack
      +       * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + public Builder setAutoAck(boolean value) { + bitField0_ |= 0x00000020; + autoAck_ = value; + onChanged(); + return this; + } + /** + *
      +       * 是否自动ack
      +       * 
      + * + * optional bool auto_ack = 6 [default = false]; + */ + public Builder clearAutoAck() { + bitField0_ = (bitField0_ & ~0x00000020); + autoAck_ = false; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Get) } - public interface MessagesOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Messages) - com.google.protobuf.MessageOrBuilder { - - /** - * optional int64 batch_id = 1; - */ - boolean hasBatchId(); - - /** - * optional int64 batch_id = 1; - */ - long getBatchId(); - - /** - * repeated bytes messages = 2; - */ - java.util.List getMessagesList(); - - /** - * repeated bytes messages = 2; - */ - int getMessagesCount(); - - /** - * repeated bytes messages = 2; - */ - com.google.protobuf.ByteString getMessages(int index); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Get) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Get DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Get(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Get getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Get parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Get(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Get getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface MessagesOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Messages) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int64 batch_id = 1; + */ + boolean hasBatchId(); + /** + * optional int64 batch_id = 1; + */ + long getBatchId(); + + /** + * repeated bytes messages = 2; + */ + java.util.List getMessagesList(); + /** + * repeated bytes messages = 2; + */ + int getMessagesCount(); + /** + * repeated bytes messages = 2; + */ + com.google.protobuf.ByteString getMessages(int index); + } + /** + *
      +   * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Messages} + */ + public static final class Messages extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Messages) + MessagesOrBuilder { + private static final long serialVersionUID = 0L; + // Use Messages.newBuilder() to construct. + private Messages(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Messages() { + batchId_ = 0L; + messages_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Messages( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + bitField0_ |= 0x00000001; + batchId_ = input.readInt64(); + break; + } + case 18: { + if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { + messages_ = new java.util.ArrayList(); + mutable_bitField0_ |= 0x00000002; + } + messages_.add(input.readBytes()); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { + messages_ = java.util.Collections.unmodifiableList(messages_); + } + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Messages.class, com.alibaba.otter.canal.protocol.CanalPacket.Messages.Builder.class); + } + + private int bitField0_; + public static final int BATCH_ID_FIELD_NUMBER = 1; + private long batchId_; + /** + * optional int64 batch_id = 1; + */ + public boolean hasBatchId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 batch_id = 1; + */ + public long getBatchId() { + return batchId_; + } + + public static final int MESSAGES_FIELD_NUMBER = 2; + private java.util.List messages_; + /** + * repeated bytes messages = 2; + */ + public java.util.List + getMessagesList() { + return messages_; + } + /** + * repeated bytes messages = 2; + */ + public int getMessagesCount() { + return messages_.size(); + } + /** + * repeated bytes messages = 2; + */ + public com.google.protobuf.ByteString getMessages(int index) { + return messages_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeInt64(1, batchId_); + } + for (int i = 0; i < messages_.size(); i++) { + output.writeBytes(2, messages_.get(i)); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(1, batchId_); + } + { + int dataSize = 0; + for (int i = 0; i < messages_.size(); i++) { + dataSize += com.google.protobuf.CodedOutputStream + .computeBytesSizeNoTag(messages_.get(i)); + } + size += dataSize; + size += 1 * getMessagesList().size(); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Messages)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Messages other = (com.alibaba.otter.canal.protocol.CanalPacket.Messages) obj; + + boolean result = true; + result = result && (hasBatchId() == other.hasBatchId()); + if (hasBatchId()) { + result = result && (getBatchId() + == other.getBatchId()); + } + result = result && getMessagesList() + .equals(other.getMessagesList()); + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasBatchId()) { + hash = (37 * hash) + BATCH_ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getBatchId()); + } + if (getMessagesCount() > 0) { + hash = (37 * hash) + MESSAGES_FIELD_NUMBER; + hash = (53 * hash) + getMessagesList().hashCode(); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Messages prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Messages} - * *
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Messages} */ - public static final class Messages extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Messages) - MessagesOrBuilder { - - // Use Messages.newBuilder() to construct. - private Messages(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Messages(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Messages defaultInstance; - - public static Messages getDefaultInstance() { - return defaultInstance; - } - - public Messages getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Messages(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 8: { - bitField0_ |= 0x00000001; - batchId_ = input.readInt64(); - break; - } - case 18: { - if (!((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - messages_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000002; - } - messages_.add(input.readBytes()); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { - messages_ = java.util.Collections.unmodifiableList(messages_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Messages.class, - com.alibaba.otter.canal.protocol.CanalPacket.Messages.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Messages parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Messages(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int BATCH_ID_FIELD_NUMBER = 1; - private long batchId_; - - /** - * optional int64 batch_id = 1; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 batch_id = 1; - */ - public long getBatchId() { - return batchId_; - } - - public static final int MESSAGES_FIELD_NUMBER = 2; - private java.util.List messages_; - - /** - * repeated bytes messages = 2; - */ - public java.util.List getMessagesList() { - return messages_; - } - - /** - * repeated bytes messages = 2; - */ - public int getMessagesCount() { - return messages_.size(); - } - - /** - * repeated bytes messages = 2; - */ - public com.google.protobuf.ByteString getMessages(int index) { - return messages_.get(index); - } - - private void initFields() { - batchId_ = 0L; - messages_ = java.util.Collections.emptyList(); - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt64(1, batchId_); - } - for (int i = 0; i < messages_.size(); i++) { - output.writeBytes(2, messages_.get(i)); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(1, batchId_); - } - { - int dataSize = 0; - for (int i = 0; i < messages_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream.computeBytesSizeNoTag(messages_.get(i)); - } - size += dataSize; - size += 1 * getMessagesList().size(); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Messages parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Messages prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Messages} - * - *
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Messages) com.alibaba.otter.canal.protocol.CanalPacket.MessagesOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Messages.class, com.alibaba.otter.canal.protocol.CanalPacket.Messages.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Messages.class, - com.alibaba.otter.canal.protocol.CanalPacket.Messages.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Messages.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Messages.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - batchId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - messages_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Messages getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Messages.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Messages build() { - com.alibaba.otter.canal.protocol.CanalPacket.Messages result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Messages buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Messages result = new com.alibaba.otter.canal.protocol.CanalPacket.Messages(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.batchId_ = batchId_; - if (((bitField0_ & 0x00000002) == 0x00000002)) { - messages_ = java.util.Collections.unmodifiableList(messages_); - bitField0_ = (bitField0_ & ~0x00000002); - } - result.messages_ = messages_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Messages) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Messages) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Messages other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Messages.getDefaultInstance()) return this; - if (other.hasBatchId()) { - setBatchId(other.getBatchId()); - } - if (!other.messages_.isEmpty()) { - if (messages_.isEmpty()) { - messages_ = other.messages_; - bitField0_ = (bitField0_ & ~0x00000002); - } else { - ensureMessagesIsMutable(); - messages_.addAll(other.messages_); - } - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Messages parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Messages) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private long batchId_; - - /** - * optional int64 batch_id = 1; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional int64 batch_id = 1; - */ - public long getBatchId() { - return batchId_; - } - - /** - * optional int64 batch_id = 1; - */ - public Builder setBatchId(long value) { - bitField0_ |= 0x00000001; - batchId_ = value; - onChanged(); - return this; - } - - /** - * optional int64 batch_id = 1; - */ - public Builder clearBatchId() { - bitField0_ = (bitField0_ & ~0x00000001); - batchId_ = 0L; - onChanged(); - return this; - } - - private java.util.List messages_ = java.util.Collections.emptyList(); - - private void ensureMessagesIsMutable() { - if (!((bitField0_ & 0x00000002) == 0x00000002)) { - messages_ = new java.util.ArrayList(messages_); - bitField0_ |= 0x00000002; - } - } - - /** - * repeated bytes messages = 2; - */ - public java.util.List getMessagesList() { - return java.util.Collections.unmodifiableList(messages_); - } - - /** - * repeated bytes messages = 2; - */ - public int getMessagesCount() { - return messages_.size(); - } - - /** - * repeated bytes messages = 2; - */ - public com.google.protobuf.ByteString getMessages(int index) { - return messages_.get(index); - } - - /** - * repeated bytes messages = 2; - */ - public Builder setMessages(int index, com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - ensureMessagesIsMutable(); - messages_.set(index, value); - onChanged(); - return this; - } - - /** - * repeated bytes messages = 2; - */ - public Builder addMessages(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - ensureMessagesIsMutable(); - messages_.add(value); - onChanged(); - return this; - } - - /** - * repeated bytes messages = 2; - */ - public Builder addAllMessages(java.lang.Iterable values) { - ensureMessagesIsMutable(); - com.google.protobuf.AbstractMessageLite.Builder.addAll(values, messages_); - onChanged(); - return this; - } - - /** - * repeated bytes messages = 2; - */ - public Builder clearMessages() { - messages_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000002); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Messages) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + batchId_ = 0L; + bitField0_ = (bitField0_ & ~0x00000001); + messages_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } - static { - defaultInstance = new Messages(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Messages getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Messages.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Messages build() { + com.alibaba.otter.canal.protocol.CanalPacket.Messages result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Messages) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Messages buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Messages result = new com.alibaba.otter.canal.protocol.CanalPacket.Messages(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.batchId_ = batchId_; + if (((bitField0_ & 0x00000002) == 0x00000002)) { + messages_ = java.util.Collections.unmodifiableList(messages_); + bitField0_ = (bitField0_ & ~0x00000002); + } + result.messages_ = messages_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Messages) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Messages)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Messages other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Messages.getDefaultInstance()) return this; + if (other.hasBatchId()) { + setBatchId(other.getBatchId()); + } + if (!other.messages_.isEmpty()) { + if (messages_.isEmpty()) { + messages_ = other.messages_; + bitField0_ = (bitField0_ & ~0x00000002); + } else { + ensureMessagesIsMutable(); + messages_.addAll(other.messages_); + } + onChanged(); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Messages parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Messages) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private long batchId_ ; + /** + * optional int64 batch_id = 1; + */ + public boolean hasBatchId() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional int64 batch_id = 1; + */ + public long getBatchId() { + return batchId_; + } + /** + * optional int64 batch_id = 1; + */ + public Builder setBatchId(long value) { + bitField0_ |= 0x00000001; + batchId_ = value; + onChanged(); + return this; + } + /** + * optional int64 batch_id = 1; + */ + public Builder clearBatchId() { + bitField0_ = (bitField0_ & ~0x00000001); + batchId_ = 0L; + onChanged(); + return this; + } + + private java.util.List messages_ = java.util.Collections.emptyList(); + private void ensureMessagesIsMutable() { + if (!((bitField0_ & 0x00000002) == 0x00000002)) { + messages_ = new java.util.ArrayList(messages_); + bitField0_ |= 0x00000002; + } + } + /** + * repeated bytes messages = 2; + */ + public java.util.List + getMessagesList() { + return java.util.Collections.unmodifiableList(messages_); + } + /** + * repeated bytes messages = 2; + */ + public int getMessagesCount() { + return messages_.size(); + } + /** + * repeated bytes messages = 2; + */ + public com.google.protobuf.ByteString getMessages(int index) { + return messages_.get(index); + } + /** + * repeated bytes messages = 2; + */ + public Builder setMessages( + int index, com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureMessagesIsMutable(); + messages_.set(index, value); + onChanged(); + return this; + } + /** + * repeated bytes messages = 2; + */ + public Builder addMessages(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + ensureMessagesIsMutable(); + messages_.add(value); + onChanged(); + return this; + } + /** + * repeated bytes messages = 2; + */ + public Builder addAllMessages( + java.lang.Iterable values) { + ensureMessagesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, messages_); + onChanged(); + return this; + } + /** + * repeated bytes messages = 2; + */ + public Builder clearMessages() { + messages_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Messages) } - public interface DumpOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Dump) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string journal = 1; - */ - boolean hasJournal(); - - /** - * optional string journal = 1; - */ - java.lang.String getJournal(); - - /** - * optional string journal = 1; - */ - com.google.protobuf.ByteString getJournalBytes(); - - /** - * optional int64 position = 2; - */ - boolean hasPosition(); - - /** - * optional int64 position = 2; - */ - long getPosition(); - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - boolean hasTimestamp(); - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - long getTimestamp(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Messages) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Messages DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Messages(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Messages getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Messages parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Messages(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Messages getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface DumpOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.Dump) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string journal = 1; + */ + boolean hasJournal(); + /** + * optional string journal = 1; + */ + java.lang.String getJournal(); + /** + * optional string journal = 1; + */ + com.google.protobuf.ByteString + getJournalBytes(); + + /** + * optional int64 position = 2; + */ + boolean hasPosition(); + /** + * optional int64 position = 2; + */ + long getPosition(); + + /** + * optional int64 timestamp = 3 [default = 0]; + */ + boolean hasTimestamp(); + /** + * optional int64 timestamp = 3 [default = 0]; + */ + long getTimestamp(); + } + /** + *
      +   * TBD when new packets are required
      +   * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Dump} + */ + public static final class Dump extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Dump) + DumpOrBuilder { + private static final long serialVersionUID = 0L; + // Use Dump.newBuilder() to construct. + private Dump(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Dump() { + journal_ = ""; + position_ = 0L; + timestamp_ = 0L; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private Dump( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + journal_ = bs; + break; + } + case 16: { + bitField0_ |= 0x00000002; + position_ = input.readInt64(); + break; + } + case 24: { + bitField0_ |= 0x00000004; + timestamp_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Dump.class, com.alibaba.otter.canal.protocol.CanalPacket.Dump.Builder.class); + } + + private int bitField0_; + public static final int JOURNAL_FIELD_NUMBER = 1; + private volatile java.lang.Object journal_; + /** + * optional string journal = 1; + */ + public boolean hasJournal() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string journal = 1; + */ + public java.lang.String getJournal() { + java.lang.Object ref = journal_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + journal_ = s; + } + return s; + } + } + /** + * optional string journal = 1; + */ + public com.google.protobuf.ByteString + getJournalBytes() { + java.lang.Object ref = journal_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + journal_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int POSITION_FIELD_NUMBER = 2; + private long position_; + /** + * optional int64 position = 2; + */ + public boolean hasPosition() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int64 position = 2; + */ + public long getPosition() { + return position_; + } + + public static final int TIMESTAMP_FIELD_NUMBER = 3; + private long timestamp_; + /** + * optional int64 timestamp = 3 [default = 0]; + */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 timestamp = 3 [default = 0]; + */ + public long getTimestamp() { + return timestamp_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, journal_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeInt64(2, position_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt64(3, timestamp_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, journal_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(2, position_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(3, timestamp_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.Dump)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.Dump other = (com.alibaba.otter.canal.protocol.CanalPacket.Dump) obj; + + boolean result = true; + result = result && (hasJournal() == other.hasJournal()); + if (hasJournal()) { + result = result && getJournal() + .equals(other.getJournal()); + } + result = result && (hasPosition() == other.hasPosition()); + if (hasPosition()) { + result = result && (getPosition() + == other.getPosition()); + } + result = result && (hasTimestamp() == other.hasTimestamp()); + if (hasTimestamp()) { + result = result && (getTimestamp() + == other.getTimestamp()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasJournal()) { + hash = (37 * hash) + JOURNAL_FIELD_NUMBER; + hash = (53 * hash) + getJournal().hashCode(); + } + if (hasPosition()) { + hash = (37 * hash) + POSITION_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getPosition()); + } + if (hasTimestamp()) { + hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getTimestamp()); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Dump prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Dump} - * *
            * TBD when new packets are required
            * 
      + * + * Protobuf type {@code com.alibaba.otter.canal.protocol.Dump} */ - public static final class Dump extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.Dump) - DumpOrBuilder { - - // Use Dump.newBuilder() to construct. - private Dump(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private Dump(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final Dump defaultInstance; - - public static Dump getDefaultInstance() { - return defaultInstance; - } - - public Dump getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private Dump(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - journal_ = bs; - break; - } - case 16: { - bitField0_ |= 0x00000002; - position_ = input.readInt64(); - break; - } - case 24: { - bitField0_ |= 0x00000004; - timestamp_ = input.readInt64(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Dump.class, - com.alibaba.otter.canal.protocol.CanalPacket.Dump.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public Dump parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new Dump(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int JOURNAL_FIELD_NUMBER = 1; - private java.lang.Object journal_; - - /** - * optional string journal = 1; - */ - public boolean hasJournal() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string journal = 1; - */ - public java.lang.String getJournal() { - java.lang.Object ref = journal_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - journal_ = s; - } - return s; - } - } - - /** - * optional string journal = 1; - */ - public com.google.protobuf.ByteString getJournalBytes() { - java.lang.Object ref = journal_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - journal_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int POSITION_FIELD_NUMBER = 2; - private long position_; - - /** - * optional int64 position = 2; - */ - public boolean hasPosition() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int64 position = 2; - */ - public long getPosition() { - return position_; - } - - public static final int TIMESTAMP_FIELD_NUMBER = 3; - private long timestamp_; - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - public boolean hasTimestamp() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - public long getTimestamp() { - return timestamp_; - } - - private void initFields() { - journal_ = ""; - position_ = 0L; - timestamp_ = 0L; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getJournalBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeInt64(2, position_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt64(3, timestamp_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getJournalBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(2, position_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(3, timestamp_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.Dump parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.Dump prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.Dump} - * - *
      -         * TBD when new packets are required
      -         * 
      - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.Dump) com.alibaba.otter.canal.protocol.CanalPacket.DumpOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.Dump.class, com.alibaba.otter.canal.protocol.CanalPacket.Dump.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.Dump.class, - com.alibaba.otter.canal.protocol.CanalPacket.Dump.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.Dump.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.Dump.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - journal_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - position_ = 0L; - bitField0_ = (bitField0_ & ~0x00000002); - timestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Dump getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.Dump.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Dump build() { - com.alibaba.otter.canal.protocol.CanalPacket.Dump result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.Dump buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.Dump result = new com.alibaba.otter.canal.protocol.CanalPacket.Dump(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.journal_ = journal_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.position_ = position_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.timestamp_ = timestamp_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Dump) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Dump) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Dump other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.Dump.getDefaultInstance()) return this; - if (other.hasJournal()) { - bitField0_ |= 0x00000001; - journal_ = other.journal_; - onChanged(); - } - if (other.hasPosition()) { - setPosition(other.getPosition()); - } - if (other.hasTimestamp()) { - setTimestamp(other.getTimestamp()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.Dump parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Dump) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object journal_ = ""; - - /** - * optional string journal = 1; - */ - public boolean hasJournal() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string journal = 1; - */ - public java.lang.String getJournal() { - java.lang.Object ref = journal_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - journal_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string journal = 1; - */ - public com.google.protobuf.ByteString getJournalBytes() { - java.lang.Object ref = journal_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - journal_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string journal = 1; - */ - public Builder setJournal(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - journal_ = value; - onChanged(); - return this; - } - - /** - * optional string journal = 1; - */ - public Builder clearJournal() { - bitField0_ = (bitField0_ & ~0x00000001); - journal_ = getDefaultInstance().getJournal(); - onChanged(); - return this; - } - - /** - * optional string journal = 1; - */ - public Builder setJournalBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - journal_ = value; - onChanged(); - return this; - } - - private long position_; - - /** - * optional int64 position = 2; - */ - public boolean hasPosition() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional int64 position = 2; - */ - public long getPosition() { - return position_; - } - - /** - * optional int64 position = 2; - */ - public Builder setPosition(long value) { - bitField0_ |= 0x00000002; - position_ = value; - onChanged(); - return this; - } - - /** - * optional int64 position = 2; - */ - public Builder clearPosition() { - bitField0_ = (bitField0_ & ~0x00000002); - position_ = 0L; - onChanged(); - return this; - } - - private long timestamp_; - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - public boolean hasTimestamp() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - public long getTimestamp() { - return timestamp_; - } - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - public Builder setTimestamp(long value) { - bitField0_ |= 0x00000004; - timestamp_ = value; - onChanged(); - return this; - } - - /** - * optional int64 timestamp = 3 [default = 0]; - */ - public Builder clearTimestamp() { - bitField0_ = (bitField0_ & ~0x00000004); - timestamp_ = 0L; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Dump) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + journal_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + position_ = 0L; + bitField0_ = (bitField0_ & ~0x00000002); + timestamp_ = 0L; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } - static { - defaultInstance = new Dump(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Dump getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.Dump.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Dump build() { + com.alibaba.otter.canal.protocol.CanalPacket.Dump result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Dump) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Dump buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.Dump result = new com.alibaba.otter.canal.protocol.CanalPacket.Dump(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.journal_ = journal_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.position_ = position_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.timestamp_ = timestamp_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.Dump) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.Dump)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Dump other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.Dump.getDefaultInstance()) return this; + if (other.hasJournal()) { + bitField0_ |= 0x00000001; + journal_ = other.journal_; + onChanged(); + } + if (other.hasPosition()) { + setPosition(other.getPosition()); + } + if (other.hasTimestamp()) { + setTimestamp(other.getTimestamp()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.Dump parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.Dump) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object journal_ = ""; + /** + * optional string journal = 1; + */ + public boolean hasJournal() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string journal = 1; + */ + public java.lang.String getJournal() { + java.lang.Object ref = journal_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + journal_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string journal = 1; + */ + public com.google.protobuf.ByteString + getJournalBytes() { + java.lang.Object ref = journal_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + journal_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string journal = 1; + */ + public Builder setJournal( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + journal_ = value; + onChanged(); + return this; + } + /** + * optional string journal = 1; + */ + public Builder clearJournal() { + bitField0_ = (bitField0_ & ~0x00000001); + journal_ = getDefaultInstance().getJournal(); + onChanged(); + return this; + } + /** + * optional string journal = 1; + */ + public Builder setJournalBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + journal_ = value; + onChanged(); + return this; + } + + private long position_ ; + /** + * optional int64 position = 2; + */ + public boolean hasPosition() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional int64 position = 2; + */ + public long getPosition() { + return position_; + } + /** + * optional int64 position = 2; + */ + public Builder setPosition(long value) { + bitField0_ |= 0x00000002; + position_ = value; + onChanged(); + return this; + } + /** + * optional int64 position = 2; + */ + public Builder clearPosition() { + bitField0_ = (bitField0_ & ~0x00000002); + position_ = 0L; + onChanged(); + return this; + } + + private long timestamp_ ; + /** + * optional int64 timestamp = 3 [default = 0]; + */ + public boolean hasTimestamp() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 timestamp = 3 [default = 0]; + */ + public long getTimestamp() { + return timestamp_; + } + /** + * optional int64 timestamp = 3 [default = 0]; + */ + public Builder setTimestamp(long value) { + bitField0_ |= 0x00000004; + timestamp_ = value; + onChanged(); + return this; + } + /** + * optional int64 timestamp = 3 [default = 0]; + */ + public Builder clearTimestamp() { + bitField0_ = (bitField0_ & ~0x00000004); + timestamp_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.Dump) } - public interface ClientRollbackOrBuilder extends - // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.ClientRollback) - com.google.protobuf.MessageOrBuilder { - - /** - * optional string destination = 1; - */ - boolean hasDestination(); - - /** - * optional string destination = 1; - */ - java.lang.String getDestination(); - - /** - * optional string destination = 1; - */ - com.google.protobuf.ByteString getDestinationBytes(); - - /** - * optional string client_id = 2; - */ - boolean hasClientId(); - - /** - * optional string client_id = 2; - */ - java.lang.String getClientId(); - - /** - * optional string client_id = 2; - */ - com.google.protobuf.ByteString getClientIdBytes(); - - /** - * optional int64 batch_id = 3; - */ - boolean hasBatchId(); - - /** - * optional int64 batch_id = 3; - */ - long getBatchId(); + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.Dump) + private static final com.alibaba.otter.canal.protocol.CanalPacket.Dump DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.Dump(); } + public static com.alibaba.otter.canal.protocol.CanalPacket.Dump getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Dump parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new Dump(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.Dump getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + public interface ClientRollbackOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.alibaba.otter.canal.protocol.ClientRollback) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string destination = 1; + */ + boolean hasDestination(); + /** + * optional string destination = 1; + */ + java.lang.String getDestination(); + /** + * optional string destination = 1; + */ + com.google.protobuf.ByteString + getDestinationBytes(); + + /** + * optional string client_id = 2; + */ + boolean hasClientId(); + /** + * optional string client_id = 2; + */ + java.lang.String getClientId(); + /** + * optional string client_id = 2; + */ + com.google.protobuf.ByteString + getClientIdBytes(); + + /** + * optional int64 batch_id = 3; + */ + boolean hasBatchId(); + /** + * optional int64 batch_id = 3; + */ + long getBatchId(); + } + /** + * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientRollback} + */ + public static final class ClientRollback extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.ClientRollback) + ClientRollbackOrBuilder { + private static final long serialVersionUID = 0L; + // Use ClientRollback.newBuilder() to construct. + private ClientRollback(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private ClientRollback() { + destination_ = ""; + clientId_ = ""; + batchId_ = 0L; + } + + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private ClientRollback( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + this(); + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000001; + destination_ = bs; + break; + } + case 18: { + com.google.protobuf.ByteString bs = input.readBytes(); + bitField0_ |= 0x00000002; + clientId_ = bs; + break; + } + case 24: { + bitField0_ |= 0x00000004; + batchId_ = input.readInt64(); + break; + } + default: { + if (!parseUnknownField( + input, unknownFields, extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.Builder.class); + } + + private int bitField0_; + public static final int DESTINATION_FIELD_NUMBER = 1; + private volatile java.lang.Object destination_; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int CLIENT_ID_FIELD_NUMBER = 2; + private volatile java.lang.Object clientId_; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int BATCH_ID_FIELD_NUMBER = 3; + private long batchId_; + /** + * optional int64 batch_id = 3; + */ + public boolean hasBatchId() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 batch_id = 3; + */ + public long getBatchId() { + return batchId_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) == 0x00000001)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeInt64(3, batchId_); + } + unknownFields.writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeInt64Size(3, batchId_); + } + size += unknownFields.getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback)) { + return super.equals(obj); + } + com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback other = (com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback) obj; + + boolean result = true; + result = result && (hasDestination() == other.hasDestination()); + if (hasDestination()) { + result = result && getDestination() + .equals(other.getDestination()); + } + result = result && (hasClientId() == other.hasClientId()); + if (hasClientId()) { + result = result && getClientId() + .equals(other.getClientId()); + } + result = result && (hasBatchId() == other.hasBatchId()); + if (hasBatchId()) { + result = result && (getBatchId() + == other.getBatchId()); + } + result = result && unknownFields.equals(other.unknownFields); + return result; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasDestination()) { + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + } + if (hasClientId()) { + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + } + if (hasBatchId()) { + hash = (37 * hash) + BATCH_ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getBatchId()); + } + hash = (29 * hash) + unknownFields.hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientRollback} */ - public static final class ClientRollback extends com.google.protobuf.GeneratedMessage implements - // @@protoc_insertion_point(message_implements:com.alibaba.otter.canal.protocol.ClientRollback) - ClientRollbackOrBuilder { - - // Use ClientRollback.newBuilder() to construct. - private ClientRollback(com.google.protobuf.GeneratedMessage.Builder builder){ - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - - private ClientRollback(boolean noInit){ - this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - private static final ClientRollback defaultInstance; - - public static ClientRollback getDefaultInstance() { - return defaultInstance; - } - - public ClientRollback getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return this.unknownFields; - } - - private ClientRollback(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException{ - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; - break; - } - case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; - break; - } - case 24: { - bitField0_ |= 0x00000004; - batchId_ = input.readInt64(); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this); - } finally { - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.class, - com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { - - public ClientRollback parsePartialFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new ClientRollback(input, - extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - private int bitField0_; - public static final int DESTINATION_FIELD_NUMBER = 1; - private java.lang.Object destination_; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int CLIENT_ID_FIELD_NUMBER = 2; - private java.lang.Object clientId_; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int BATCH_ID_FIELD_NUMBER = 3; - private long batchId_; - - /** - * optional int64 batch_id = 3; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 batch_id = 3; - */ - public long getBatchId() { - return batchId_; - } - - private void initFields() { - destination_ = ""; - clientId_ = ""; - batchId_ = 0L; - } - - private byte memoizedIsInitialized = -1; - - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized == 1) return true; - if (isInitialized == 0) return false; - - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeBytes(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt64(3, batchId_); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(1, getDestinationBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream.computeBytesSize(2, getClientIdBytes()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream.computeInt64Size(3, batchId_); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - - @java.lang.Override - protected java.lang.Object writeReplace() throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseDelimitedFrom(java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parseFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return Builder.create(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder(com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - - /** - * Protobuf type {@code com.alibaba.otter.canal.protocol.ClientRollback} - */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder implements + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements // @@protoc_insertion_point(builder_implements:com.alibaba.otter.canal.protocol.ClientRollback) com.alibaba.otter.canal.protocol.CanalPacket.ClientRollbackOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; + } - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; - } + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.Builder.class); + } - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable internalGetFieldAccessorTable() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable.ensureFieldAccessorsInitialized(com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.class, - com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.Builder.class); - } + // Construct using com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } - // Construct using - // com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.newBuilder() - private Builder(){ - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent){ - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - } - } - - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); - clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); - batchId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback getDefaultInstanceForType() { - return com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.getDefaultInstance(); - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback build() { - com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback buildPartial() { - com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.batchId_ = batchId_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback) { - return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback) other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback other) { - if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; - destination_ = other.destination_; - onChanged(); - } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; - clientId_ = other.clientId_; - onChanged(); - } - if (other.hasBatchId()) { - setBatchId(other.getBatchId()); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - return true; - } - - public Builder mergeFrom(com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - - private int bitField0_; - - private java.lang.Object destination_ = ""; - - /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - - /** - * optional string destination = 1; - */ - public java.lang.String getDestination() { - java.lang.Object ref = destination_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string destination = 1; - */ - public com.google.protobuf.ByteString getDestinationBytes() { - java.lang.Object ref = destination_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - destination_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string destination = 1; - */ - public Builder setDestination(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); - destination_ = getDefaultInstance().getDestination(); - onChanged(); - return this; - } - - /** - * optional string destination = 1; - */ - public Builder setDestinationBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - destination_ = value; - onChanged(); - return this; - } - - private java.lang.Object clientId_ = ""; - - /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - - /** - * optional string client_id = 2; - */ - public java.lang.String getClientId() { - java.lang.Object ref = clientId_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - - /** - * optional string client_id = 2; - */ - public com.google.protobuf.ByteString getClientIdBytes() { - java.lang.Object ref = clientId_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - clientId_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - /** - * optional string client_id = 2; - */ - public Builder setClientId(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); - clientId_ = getDefaultInstance().getClientId(); - onChanged(); - return this; - } - - /** - * optional string client_id = 2; - */ - public Builder setClientIdBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - clientId_ = value; - onChanged(); - return this; - } - - private long batchId_; - - /** - * optional int64 batch_id = 3; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - - /** - * optional int64 batch_id = 3; - */ - public long getBatchId() { - return batchId_; - } - - /** - * optional int64 batch_id = 3; - */ - public Builder setBatchId(long value) { - bitField0_ |= 0x00000004; - batchId_ = value; - onChanged(); - return this; - } - - /** - * optional int64 batch_id = 3; - */ - public Builder clearBatchId() { - bitField0_ = (bitField0_ & ~0x00000004); - batchId_ = 0L; - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.ClientRollback) + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessageV3 + .alwaysUseFieldBuilders) { } + } + @java.lang.Override + public Builder clear() { + super.clear(); + destination_ = ""; + bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + batchId_ = 0L; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } - static { - defaultInstance = new ClientRollback(true); - defaultInstance.initFields(); + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback getDefaultInstanceForType() { + return com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.getDefaultInstance(); + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback build() { + com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); } + return result; + } - // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.ClientRollback) + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback buildPartial() { + com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.destination_ = destination_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.clientId_ = clientId_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.batchId_ = batchId_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + @java.lang.Override + public Builder clone() { + return (Builder) super.clone(); + } + @java.lang.Override + public Builder setField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.setField(field, value); + } + @java.lang.Override + public Builder clearField( + com.google.protobuf.Descriptors.FieldDescriptor field) { + return (Builder) super.clearField(field); + } + @java.lang.Override + public Builder clearOneof( + com.google.protobuf.Descriptors.OneofDescriptor oneof) { + return (Builder) super.clearOneof(oneof); + } + @java.lang.Override + public Builder setRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + int index, java.lang.Object value) { + return (Builder) super.setRepeatedField(field, index, value); + } + @java.lang.Override + public Builder addRepeatedField( + com.google.protobuf.Descriptors.FieldDescriptor field, + java.lang.Object value) { + return (Builder) super.addRepeatedField(field, value); + } + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback) { + return mergeFrom((com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback other) { + if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.getDefaultInstance()) return this; + if (other.hasDestination()) { + bitField0_ |= 0x00000001; + destination_ = other.destination_; + onChanged(); + } + if (other.hasClientId()) { + bitField0_ |= 0x00000002; + clientId_ = other.clientId_; + onChanged(); + } + if (other.hasBatchId()) { + setBatchId(other.getBatchId()); + } + this.mergeUnknownFields(other.unknownFields); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback) e.getUnfinishedMessage(); + throw e.unwrapIOException(); + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + private java.lang.Object destination_ = ""; + /** + * optional string destination = 1; + */ + public boolean hasDestination() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional string destination = 1; + */ + public java.lang.String getDestination() { + java.lang.Object ref = destination_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + destination_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string destination = 1; + */ + public com.google.protobuf.ByteString + getDestinationBytes() { + java.lang.Object ref = destination_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + destination_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string destination = 1; + */ + public Builder setDestination( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder clearDestination() { + bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); + onChanged(); + return this; + } + /** + * optional string destination = 1; + */ + public Builder setDestinationBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + destination_ = value; + onChanged(); + return this; + } + + private java.lang.Object clientId_ = ""; + /** + * optional string client_id = 2; + */ + public boolean hasClientId() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string client_id = 2; + */ + public java.lang.String getClientId() { + java.lang.Object ref = clientId_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + clientId_ = s; + } + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string client_id = 2; + */ + public com.google.protobuf.ByteString + getClientIdBytes() { + java.lang.Object ref = clientId_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + clientId_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string client_id = 2; + */ + public Builder setClientId( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder clearClientId() { + bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); + onChanged(); + return this; + } + /** + * optional string client_id = 2; + */ + public Builder setClientIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + clientId_ = value; + onChanged(); + return this; + } + + private long batchId_ ; + /** + * optional int64 batch_id = 3; + */ + public boolean hasBatchId() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional int64 batch_id = 3; + */ + public long getBatchId() { + return batchId_; + } + /** + * optional int64 batch_id = 3; + */ + public Builder setBatchId(long value) { + bitField0_ |= 0x00000004; + batchId_ = value; + onChanged(); + return this; + } + /** + * optional int64 batch_id = 3; + */ + public Builder clearBatchId() { + bitField0_ = (bitField0_ & ~0x00000004); + batchId_ = 0L; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.alibaba.otter.canal.protocol.ClientRollback) } - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable; - private static final com.google.protobuf.Descriptors.Descriptor internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable; - - public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { - return descriptor; - } - - private static com.google.protobuf.Descriptors.FileDescriptor descriptor; + // @@protoc_insertion_point(class_scope:com.alibaba.otter.canal.protocol.ClientRollback) + private static final com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback DEFAULT_INSTANCE; static { - java.lang.String[] descriptorData = { - "\n\023CanalProtocol.proto\022 com.alibaba.otter" - + ".canal.protocol\"\312\001\n\006Packet\022\030\n\014magic_numb" - + "er\030\001 \001(\005:\00217\022\022\n\007version\030\002 \001(\005:\0011\022:\n\004type" - + "\030\003 \001(\0162,.com.alibaba.otter.canal.protoco" - + "l.PacketType\022H\n\013compression\030\004 \001(\0162-.com." - + "alibaba.otter.canal.protocol.Compression" - + ":\004NONE\022\014\n\004body\030\005 \001(\014\"<\n\tHeartBeat\022\026\n\016sen" - + "d_timestamp\030\001 \001(\003\022\027\n\017start_timestamp\030\002 \001" - + "(\003\"\217\001\n\tHandshake\022$\n\026communication_encodi" - + "ng\030\001 \001(\t:\004utf8\022\r\n\005seeds\030\002 \001(\014\022M\n\026support", - "ed_compressions\030\003 \003(\0162-.com.alibaba.otte" - + "r.canal.protocol.Compression\"\274\001\n\nClientA" - + "uth\022\020\n\010username\030\001 \001(\t\022\020\n\010password\030\002 \001(\014\022" - + "\033\n\020net_read_timeout\030\003 \001(\005:\0010\022\034\n\021net_writ" - + "e_timeout\030\004 \001(\005:\0010\022\023\n\013destination\030\005 \001(\t\022" - + "\021\n\tclient_id\030\006 \001(\t\022\016\n\006filter\030\007 \001(\t\022\027\n\017st" - + "art_timestamp\030\010 \001(\003\"3\n\003Ack\022\025\n\nerror_code" - + "\030\001 \001(\005:\0010\022\025\n\rerror_message\030\002 \001(\t\"E\n\tClie" - + "ntAck\022\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030" - + "\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003\"=\n\003Sub\022\023\n\013destin", - "ation\030\001 \001(\t\022\021\n\tclient_id\030\002 \001(\t\022\016\n\006filter" - + "\030\007 \001(\t\"?\n\005Unsub\022\023\n\013destination\030\001 \001(\t\022\021\n\t" - + "client_id\030\002 \001(\t\022\016\n\006filter\030\007 \001(\t\"\200\001\n\003Get\022" - + "\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030\002 \001(\t\022" - + "\022\n\nfetch_size\030\003 \001(\005\022\023\n\007timeout\030\004 \001(\003:\002-1" - + "\022\017\n\004unit\030\005 \001(\005:\0012\022\027\n\010auto_ack\030\006 \001(\010:\005fal" - + "se\".\n\010Messages\022\020\n\010batch_id\030\001 \001(\003\022\020\n\010mess" - + "ages\030\002 \003(\014\"?\n\004Dump\022\017\n\007journal\030\001 \001(\t\022\020\n\010p" - + "osition\030\002 \001(\003\022\024\n\ttimestamp\030\003 \001(\003:\0010\"J\n\016C" - + "lientRollback\022\023\n\013destination\030\001 \001(\t\022\021\n\tcl", - "ient_id\030\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003*4\n\013Compr" - + "ession\022\010\n\004NONE\020\001\022\010\n\004ZLIB\020\002\022\010\n\004GZIP\020\003\022\007\n\003" - + "LZF\020\004*\305\001\n\nPacketType\022\r\n\tHANDSHAKE\020\001\022\030\n\024C" - + "LIENTAUTHENTICATION\020\002\022\007\n\003ACK\020\003\022\020\n\014SUBSCR" - + "IPTION\020\004\022\022\n\016UNSUBSCRIPTION\020\005\022\007\n\003GET\020\006\022\014\n" - + "\010MESSAGES\020\007\022\r\n\tCLIENTACK\020\010\022\014\n\010SHUTDOWN\020\t" - + "\022\010\n\004DUMP\020\n\022\r\n\tHEARTBEAT\020\013\022\022\n\016CLIENTROLLB" - + "ACK\020\014B1\n com.alibaba.otter.canal.protoco" + "lB\013CanalPacketH\001" }; - com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { - - public com.google.protobuf.ExtensionRegistry assignDescriptors(com.google.protobuf.Descriptors.FileDescriptor root) { - descriptor = root; - return null; - } - }; - com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom(descriptorData, - new com.google.protobuf.Descriptors.FileDescriptor[] {}, - assigner); - internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor = getDescriptor().getMessageTypes().get(0); - internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor, - new java.lang.String[] { "MagicNumber", "Version", "Type", "Compression", "Body", }); - internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor = getDescriptor().getMessageTypes() - .get(1); - internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor, - new java.lang.String[] { "SendTimestamp", "StartTimestamp", }); - internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor = getDescriptor().getMessageTypes() - .get(2); - internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor, - new java.lang.String[] { "CommunicationEncoding", "Seeds", "SupportedCompressions", }); - internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor = getDescriptor().getMessageTypes() - .get(3); - internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor, - new java.lang.String[] { "Username", "Password", "NetReadTimeout", "NetWriteTimeout", "Destination", - "ClientId", "Filter", "StartTimestamp", }); - internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor = getDescriptor().getMessageTypes().get(4); - internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor, - new java.lang.String[] { "ErrorCode", "ErrorMessage", }); - internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor = getDescriptor().getMessageTypes() - .get(5); - internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor, - new java.lang.String[] { "Destination", "ClientId", "BatchId", }); - internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor = getDescriptor().getMessageTypes().get(6); - internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor, - new java.lang.String[] { "Destination", "ClientId", "Filter", }); - internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor = getDescriptor().getMessageTypes().get(7); - internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor, - new java.lang.String[] { "Destination", "ClientId", "Filter", }); - internal_static_com_alibaba_otter_canal_protocol_Get_descriptor = getDescriptor().getMessageTypes().get(8); - internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Get_descriptor, - new java.lang.String[] { "Destination", "ClientId", "FetchSize", "Timeout", "Unit", "AutoAck", }); - internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor = getDescriptor().getMessageTypes().get(9); - internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor, - new java.lang.String[] { "BatchId", "Messages", }); - internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor = getDescriptor().getMessageTypes().get(10); - internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor, - new java.lang.String[] { "Journal", "Position", "Timestamp", }); - internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor = getDescriptor().getMessageTypes() - .get(11); - internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable(internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor, - new java.lang.String[] { "Destination", "ClientId", "BatchId", }); + DEFAULT_INSTANCE = new com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback(); } - // @@protoc_insertion_point(outer_class_scope) + public static com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + @java.lang.Deprecated public static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public ClientRollback parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new ClientRollback(input, extensionRegistry); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Get_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable; + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\023CanalProtocol.proto\022 com.alibaba.otter" + + ".canal.protocol\"\312\001\n\006Packet\022\030\n\014magic_numb" + + "er\030\001 \001(\005:\00217\022\022\n\007version\030\002 \001(\005:\0011\022:\n\004type" + + "\030\003 \001(\0162,.com.alibaba.otter.canal.protoco" + + "l.PacketType\022H\n\013compression\030\004 \001(\0162-.com." + + "alibaba.otter.canal.protocol.Compression" + + ":\004NONE\022\014\n\004body\030\005 \001(\014\"<\n\tHeartBeat\022\026\n\016sen" + + "d_timestamp\030\001 \001(\003\022\027\n\017start_timestamp\030\002 \001" + + "(\003\"\217\001\n\tHandshake\022$\n\026communication_encodi" + + "ng\030\001 \001(\t:\004utf8\022\r\n\005seeds\030\002 \001(\014\022M\n\026support" + + "ed_compressions\030\003 \003(\0162-.com.alibaba.otte" + + "r.canal.protocol.Compression\"\274\001\n\nClientA" + + "uth\022\020\n\010username\030\001 \001(\t\022\020\n\010password\030\002 \001(\014\022" + + "\033\n\020net_read_timeout\030\003 \001(\005:\0010\022\034\n\021net_writ" + + "e_timeout\030\004 \001(\005:\0010\022\023\n\013destination\030\005 \001(\t\022" + + "\021\n\tclient_id\030\006 \001(\t\022\016\n\006filter\030\007 \001(\t\022\027\n\017st" + + "art_timestamp\030\010 \001(\003\"3\n\003Ack\022\025\n\nerror_code" + + "\030\001 \001(\005:\0010\022\025\n\rerror_message\030\002 \001(\t\"E\n\tClie" + + "ntAck\022\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030" + + "\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003\"=\n\003Sub\022\023\n\013destin" + + "ation\030\001 \001(\t\022\021\n\tclient_id\030\002 \001(\t\022\016\n\006filter" + + "\030\007 \001(\t\"?\n\005Unsub\022\023\n\013destination\030\001 \001(\t\022\021\n\t" + + "client_id\030\002 \001(\t\022\016\n\006filter\030\007 \001(\t\"\200\001\n\003Get\022" + + "\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030\002 \001(\t\022" + + "\022\n\nfetch_size\030\003 \001(\005\022\023\n\007timeout\030\004 \001(\003:\002-1" + + "\022\017\n\004unit\030\005 \001(\005:\0012\022\027\n\010auto_ack\030\006 \001(\010:\005fal" + + "se\".\n\010Messages\022\020\n\010batch_id\030\001 \001(\003\022\020\n\010mess" + + "ages\030\002 \003(\014\"?\n\004Dump\022\017\n\007journal\030\001 \001(\t\022\020\n\010p" + + "osition\030\002 \001(\003\022\024\n\ttimestamp\030\003 \001(\003:\0010\"J\n\016C" + + "lientRollback\022\023\n\013destination\030\001 \001(\t\022\021\n\tcl" + + "ient_id\030\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003*4\n\013Compr" + + "ession\022\010\n\004NONE\020\001\022\010\n\004ZLIB\020\002\022\010\n\004GZIP\020\003\022\007\n\003" + + "LZF\020\004*\305\001\n\nPacketType\022\r\n\tHANDSHAKE\020\001\022\030\n\024C" + + "LIENTAUTHENTICATION\020\002\022\007\n\003ACK\020\003\022\020\n\014SUBSCR" + + "IPTION\020\004\022\022\n\016UNSUBSCRIPTION\020\005\022\007\n\003GET\020\006\022\014\n" + + "\010MESSAGES\020\007\022\r\n\tCLIENTACK\020\010\022\014\n\010SHUTDOWN\020\t" + + "\022\010\n\004DUMP\020\n\022\r\n\tHEARTBEAT\020\013\022\022\n\016CLIENTROLLB" + + "ACK\020\014B1\n com.alibaba.otter.canal.protoco" + + "lB\013CanalPacketH\001" + }; + com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = + new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { + public com.google.protobuf.ExtensionRegistry assignDescriptors( + com.google.protobuf.Descriptors.FileDescriptor root) { + descriptor = root; + return null; + } + }; + com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }, assigner); + internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor, + new java.lang.String[] { "MagicNumber", "Version", "Type", "Compression", "Body", }); + internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor, + new java.lang.String[] { "SendTimestamp", "StartTimestamp", }); + internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor = + getDescriptor().getMessageTypes().get(2); + internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor, + new java.lang.String[] { "CommunicationEncoding", "Seeds", "SupportedCompressions", }); + internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor = + getDescriptor().getMessageTypes().get(3); + internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor, + new java.lang.String[] { "Username", "Password", "NetReadTimeout", "NetWriteTimeout", "Destination", "ClientId", "Filter", "StartTimestamp", }); + internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor = + getDescriptor().getMessageTypes().get(4); + internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor, + new java.lang.String[] { "ErrorCode", "ErrorMessage", }); + internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor = + getDescriptor().getMessageTypes().get(5); + internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor, + new java.lang.String[] { "Destination", "ClientId", "BatchId", }); + internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor = + getDescriptor().getMessageTypes().get(6); + internal_static_com_alibaba_otter_canal_protocol_Sub_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Sub_descriptor, + new java.lang.String[] { "Destination", "ClientId", "Filter", }); + internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor = + getDescriptor().getMessageTypes().get(7); + internal_static_com_alibaba_otter_canal_protocol_Unsub_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Unsub_descriptor, + new java.lang.String[] { "Destination", "ClientId", "Filter", }); + internal_static_com_alibaba_otter_canal_protocol_Get_descriptor = + getDescriptor().getMessageTypes().get(8); + internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Get_descriptor, + new java.lang.String[] { "Destination", "ClientId", "FetchSize", "Timeout", "Unit", "AutoAck", }); + internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor = + getDescriptor().getMessageTypes().get(9); + internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor, + new java.lang.String[] { "BatchId", "Messages", }); + internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor = + getDescriptor().getMessageTypes().get(10); + internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor, + new java.lang.String[] { "Journal", "Position", "Timestamp", }); + internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor = + getDescriptor().getMessageTypes().get(11); + internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor, + new java.lang.String[] { "Destination", "ClientId", "BatchId", }); + } + + // @@protoc_insertion_point(outer_class_scope) } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto index 80f03439..9de91150 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto @@ -1,3 +1,4 @@ +syntax = "proto2"; package com.alibaba.otter.canal.protocol; option java_package = "com.alibaba.otter.canal.protocol"; diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto b/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto index 526db41f..c755921d 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto @@ -1,3 +1,4 @@ +syntax = "proto2"; package com.alibaba.otter.canal.protocol; option java_package = "com.alibaba.otter.canal.protocol"; diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java index 923001d1..e4793640 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/listener/ChannelFutureAggregator.java @@ -2,7 +2,7 @@ package com.alibaba.otter.canal.server.netty.listener; import com.alibaba.otter.canal.protocol.CanalPacket; import com.google.common.base.Preconditions; -import com.google.protobuf.GeneratedMessage; +import com.google.protobuf.GeneratedMessageV3; import org.jboss.netty.channel.ChannelFuture; import org.jboss.netty.channel.ChannelFutureListener; @@ -16,19 +16,19 @@ public class ChannelFutureAggregator implements ChannelFutureListener { private ClientRequestResult result; - public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, boolean empty) { + public ChannelFutureAggregator(String destination, GeneratedMessageV3 request, CanalPacket.PacketType type, int amount, long latency, boolean empty) { this(destination, request, type, amount, latency, empty, (short) 0); } - public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency) { + public ChannelFutureAggregator(String destination, GeneratedMessageV3 request, CanalPacket.PacketType type, int amount, long latency) { this(destination, request, type, amount, latency, false, (short) 0); } - public ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, short errorCode) { + public ChannelFutureAggregator(String destination, GeneratedMessageV3 request, CanalPacket.PacketType type, int amount, long latency, short errorCode) { this(destination, request, type, amount, latency, false, errorCode); } - private ChannelFutureAggregator(String destination, GeneratedMessage request, CanalPacket.PacketType type, int amount, long latency, boolean empty, short errorCode) { + private ChannelFutureAggregator(String destination, GeneratedMessageV3 request, CanalPacket.PacketType type, int amount, long latency, boolean empty, short errorCode) { this.result = new ClientRequestResult.Builder() .destination(destination) .type(type) @@ -56,7 +56,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { private String destination; private CanalPacket.PacketType type; - private GeneratedMessage request; + private GeneratedMessageV3 request; private int amount; private long latency; private short errorCode; @@ -81,7 +81,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { private String destination; private CanalPacket.PacketType type; - private GeneratedMessage request; + private GeneratedMessageV3 request; private int amount; private long latency; private short errorCode; @@ -98,7 +98,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { return this; } - Builder request(GeneratedMessage request) { + Builder request(GeneratedMessageV3 request) { this.request = request; return this; } @@ -153,7 +153,7 @@ public class ChannelFutureAggregator implements ChannelFutureListener { return type; } - public GeneratedMessage getRequest() { + public GeneratedMessageV3 getRequest() { return request; } From e450c3f83fb6dabba3197214dc32ea29c7809f23 Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 11 Sep 2018 11:35:47 +0800 Subject: [PATCH 130/360] =?UTF-8?q?fix=20#912=20=E9=80=9A=E8=BF=87maven?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E5=B0=86guava=E4=BF=AE=E6=94=B9=E5=8C=85?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E6=89=93=E5=8C=85=E8=BF=9Bcanal.client?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loader/CanalAdapterKafkaWorker.java | 2 +- client/pom.xml | 157 +++++++++++++++++- 2 files changed, 156 insertions(+), 3 deletions(-) diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java index 5cf86611..4a7bd7a3 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java @@ -33,7 +33,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { this.topic = topic; this.canalDestination = topic; connector = KafkaCanalConnectors.newKafkaConnector(zkServers, bootstrapServers, topic, null, groupId); - // connector.setSessionTimeout(5L, TimeUnit.MINUTES); + connector.setSessionTimeout(5L, TimeUnit.MINUTES); // super.initSwitcher(topic); } diff --git a/client/pom.xml b/client/pom.xml index 2e48f9c5..02b0f66a 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -15,7 +15,93 @@ com.alibaba.otter canal.protocol ${project.version} + true + + + com.google.protobuf + protobuf-java + + + + + io.netty + netty-all + + + + org.apache.zookeeper + zookeeper + + + com.101tec + zkclient + + + + commons-io + commons-io + + + commons-lang + commons-lang + + + commons-codec + commons-codec + + + com.alibaba + fastjson + + + com.google.guava + guava + + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.slf4j + jcl-over-slf4j + + + org.slf4j + slf4j-api + + + org.springframework + spring-core + + + org.springframework + spring-aop + + + org.springframework + spring-context + + + org.springframework + spring-jdbc + + + org.springframework + spring-orm + + + org.springframework + spring-test + ${spring_version} + test + + org.apache.kafka @@ -28,9 +114,76 @@ junit junit + test - + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.1 + + false + + + org.apache.zookeeper:zookeeper + com.101tec:zkclient + commons-io:commons-io + commons-lang:commons-lang + commons-codec:commons-codec + com.alibaba:fastjson + ch.qos.logback:logback-core + ch.qos.logback:logback-classic + org.slf4j:jcl-over-slf4j + org.slf4j:slf4j-api + org.springframework:* + org.jboss.netty:netty + commons-logging:commons-logging + aopalliance:aopalliance + com.google.protobuf:protobuf-java + io.netty:* + junit:junit + + + + + + package + + shade + + + + + com.google.guava + com.alibaba.google.guava + + + org.joda + com.alibaba.google.joda + + + com.google.common + com.alibaba.google.common + + + com.google.thirdparty + com.alibaba.google.thirdparty + + + + + + + + + + + + dev @@ -98,7 +251,7 @@ ${project.build.directory}/apidocs true scm:git:git@github.com:alibaba/canal.git - gh-pages + gh-pages From 8ae8f526a6e6b10896864ef56216c58886c23889 Mon Sep 17 00:00:00 2001 From: machey Date: Tue, 11 Sep 2018 22:47:53 +0800 Subject: [PATCH 131/360] =?UTF-8?q?fix=20#904:=20=E7=A9=BA=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E6=97=B6sleep=201s=E9=98=B2=E6=AD=A2=E5=BE=AA?= =?UTF-8?q?=E7=8E=AFget=E6=95=B0=E6=8D=AE=E8=BF=87=E5=BF=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/alibaba/otter/canal/kafka/CanalKafkaStarter.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index 717a4525..0632417c 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -147,6 +147,12 @@ public class CanalKafkaStarter implements CanalServerStarter { server.rollback(clientIdentity, batchId); } }); // 发送message到topic + } else { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // ignore + } } } catch (Exception e) { From a6798c61df15741bc5ae4d46e93718db445bbcc1 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 12 Sep 2018 09:03:43 +0800 Subject: [PATCH 132/360] =?UTF-8?q?=E7=A9=BA=E6=95=B0=E6=8D=AEsleep=20100m?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index 0632417c..5b73bf3b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -149,7 +149,7 @@ public class CanalKafkaStarter implements CanalServerStarter { }); // 发送message到topic } else { try { - Thread.sleep(1000); + Thread.sleep(100); } catch (InterruptedException e) { // ignore } From ca61be5f5ad0cb73e42c5c4fb2b3c4278ad4e352 Mon Sep 17 00:00:00 2001 From: WithLin Date: Thu, 13 Sep 2018 18:36:36 +0800 Subject: [PATCH 133/360] fix upgrade proto2 to proto3 --- .../client/impl/SimpleCanalConnector.java | 2 +- .../client/running/ClientRunningTest.java | 188 +- .../otter/canal/protocol/CanalEntry.java | 4068 ++++++--------- .../otter/canal/protocol/CanalPacket.java | 4574 ++++++++--------- .../otter/canal/protocol/CanalProtocol.proto | 125 +- .../otter/canal/protocol/EntryProtocol.proto | 191 +- .../server/CanalServerWithNettyTest.java | 4 +- 7 files changed, 4125 insertions(+), 5027 deletions(-) diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java index 0f29794d..b7951912 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java @@ -159,7 +159,7 @@ public class SimpleCanalConnector implements CanalConnector { } // Handshake handshake = Handshake.parseFrom(p.getBody()); - supportedCompressions.addAll(handshake.getSupportedCompressionsList()); + supportedCompressions.add(handshake.getSupportedCompressions()); // ClientAuth ca = ClientAuth.newBuilder() .setUsername(username != null ? username : "") diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/ClientRunningTest.java b/client/src/test/java/com/alibaba/otter/canal/client/running/ClientRunningTest.java index cbdfd0ea..40755671 100644 --- a/client/src/test/java/com/alibaba/otter/canal/client/running/ClientRunningTest.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/ClientRunningTest.java @@ -20,117 +20,117 @@ import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; public class ClientRunningTest extends AbstractZkTest { - private ZkClientx zkclientx = new ZkClientx(cluster1 + ";" + cluster2); - private short clientId = 1001; + private ZkClientx zkclientx = new ZkClientx(cluster1 + ";" + cluster2); + private short clientId = 1001; - @Before - public void setUp() { - String path = ZookeeperPathUtils.getDestinationPath(destination); - zkclientx.deleteRecursive(path); + @Before + public void setUp() { + String path = ZookeeperPathUtils.getDestinationPath(destination); + zkclientx.deleteRecursive(path); - zkclientx.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientId), true); - } + zkclientx.createPersistent(ZookeeperPathUtils.getClientIdNodePath(this.destination, clientId), true); + } - @After - public void tearDown() { - String path = ZookeeperPathUtils.getDestinationPath(destination); - zkclientx.deleteRecursive(path); - } + @After + public void tearDown() { + String path = ZookeeperPathUtils.getDestinationPath(destination); + zkclientx.deleteRecursive(path); + } - @Test - public void testOneServer() { - final CountDownLatch countLatch = new CountDownLatch(2); - ClientRunningMonitor runningMonitor = buildClientRunning(countLatch, clientId, 2088); - runningMonitor.start(); - sleep(2000L); - runningMonitor.stop(); - sleep(2000L); + @Test + public void testOneServer() { + final CountDownLatch countLatch = new CountDownLatch(2); + ClientRunningMonitor runningMonitor = buildClientRunning(countLatch, clientId, 2088); + runningMonitor.start(); + sleep(2000L); + runningMonitor.stop(); + sleep(2000L); - if (countLatch.getCount() != 0) { - Assert.fail(); - } - } + if (countLatch.getCount() != 0) { + Assert.fail(); + } + } - @Test - public void testMultiServer() { - final CountDownLatch countLatch = new CountDownLatch(30); - final ClientRunningMonitor runningMonitor1 = buildClientRunning(countLatch, clientId, 2088); - final ClientRunningMonitor runningMonitor2 = buildClientRunning(countLatch, clientId, 2089); - final ClientRunningMonitor runningMonitor3 = buildClientRunning(countLatch, clientId, 2090); - final ExecutorService executor = Executors.newFixedThreadPool(3); - executor.submit(new Runnable() { + @Test + public void testMultiServer() { + final CountDownLatch countLatch = new CountDownLatch(30); + final ClientRunningMonitor runningMonitor1 = buildClientRunning(countLatch, clientId, 2088); + final ClientRunningMonitor runningMonitor2 = buildClientRunning(countLatch, clientId, 2089); + final ClientRunningMonitor runningMonitor3 = buildClientRunning(countLatch, clientId, 2090); + final ExecutorService executor = Executors.newFixedThreadPool(3); + executor.submit(new Runnable() { - public void run() { - for (int i = 0; i < 10; i++) { - if (!runningMonitor1.isStart()) { - runningMonitor1.start(); - } - sleep(2000L + RandomUtils.nextInt(500)); - runningMonitor1.stop(); - sleep(2000L + RandomUtils.nextInt(500)); - } - } + public void run() { + for (int i = 0; i < 10; i++) { + if (!runningMonitor1.isStart()) { + runningMonitor1.start(); + } + sleep(2000L + RandomUtils.nextInt(500)); + runningMonitor1.stop(); + sleep(2000L + RandomUtils.nextInt(500)); + } + } - }); + }); - executor.submit(new Runnable() { + executor.submit(new Runnable() { - public void run() { - for (int i = 0; i < 10; i++) { - if (!runningMonitor2.isStart()) { - runningMonitor2.start(); - } - sleep(2000L + RandomUtils.nextInt(500)); - runningMonitor2.stop(); - sleep(2000L + RandomUtils.nextInt(500)); - } - } + public void run() { + for (int i = 0; i < 10; i++) { + if (!runningMonitor2.isStart()) { + runningMonitor2.start(); + } + sleep(2000L + RandomUtils.nextInt(500)); + runningMonitor2.stop(); + sleep(2000L + RandomUtils.nextInt(500)); + } + } - }); + }); - executor.submit(new Runnable() { + executor.submit(new Runnable() { - public void run() { - for (int i = 0; i < 10; i++) { - if (!runningMonitor3.isStart()) { - runningMonitor3.start(); - } - sleep(2000L + RandomUtils.nextInt(500)); - runningMonitor3.stop(); - sleep(2000L + RandomUtils.nextInt(500)); - } - } + public void run() { + for (int i = 0; i < 10; i++) { + if (!runningMonitor3.isStart()) { + runningMonitor3.start(); + } + sleep(2000L + RandomUtils.nextInt(500)); + runningMonitor3.stop(); + sleep(2000L + RandomUtils.nextInt(500)); + } + } - }); + }); - sleep(30000L); - } + sleep(30000L); + } - private ClientRunningMonitor buildClientRunning(final CountDownLatch countLatch, final short clientId, - final int port) { - ClientRunningData clientData = new ClientRunningData(); - clientData.setClientId(clientId); - clientData.setAddress(AddressUtils.getHostIp()); + private ClientRunningMonitor buildClientRunning(final CountDownLatch countLatch, final short clientId, + final int port) { + ClientRunningData clientData = new ClientRunningData(); + clientData.setClientId(clientId); + clientData.setAddress(AddressUtils.getHostIp()); - ClientRunningMonitor runningMonitor = new ClientRunningMonitor(); - runningMonitor.setDestination(destination); - runningMonitor.setZkClient(zkclientx); - runningMonitor.setClientData(clientData); - runningMonitor.setListener(new ClientRunningListener() { + ClientRunningMonitor runningMonitor = new ClientRunningMonitor(); + runningMonitor.setDestination(destination); + runningMonitor.setZkClient(zkclientx); + runningMonitor.setClientData(clientData); + runningMonitor.setListener(new ClientRunningListener() { - public InetSocketAddress processActiveEnter() { - System.out.println(String.format("clientId:%s port:%s has start", clientId, port)); - countLatch.countDown(); - return new InetSocketAddress(AddressUtils.getHostIp(), port); - } + public InetSocketAddress processActiveEnter() { + System.out.println(String.format("clientId:%s port:%s has start", clientId, port)); + countLatch.countDown(); + return new InetSocketAddress(AddressUtils.getHostIp(), port); + } - public void processActiveExit() { - countLatch.countDown(); - System.out.println(String.format("clientId:%s port:%s has stop", clientId, port)); - } + public void processActiveExit() { + countLatch.countDown(); + System.out.println(String.format("clientId:%s port:%s has stop", clientId, port)); + } - }); - runningMonitor.setDelayTime(1); - return runningMonitor; - } -} + }); + runningMonitor.setDelayTime(1); + return runningMonitor; + } +} \ No newline at end of file diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java index 8807b1fd..997f76c5 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalEntry.java @@ -22,7 +22,11 @@ public final class CanalEntry { * Protobuf enum {@code com.alibaba.otter.canal.protocol.EntryType} */ public enum EntryType - implements com.google.protobuf.ProtocolMessageEnum { + implements com.google.protobuf.ProtocolMessageEnum { + /** + * ENTRYTYPECOMPATIBLEPROTO2 = 0; + */ + ENTRYTYPECOMPATIBLEPROTO2(0), /** * TRANSACTIONBEGIN = 1; */ @@ -47,8 +51,13 @@ public final class CanalEntry { * GTIDLOG = 5; */ GTIDLOG(5), + UNRECOGNIZED(-1), ; + /** + * ENTRYTYPECOMPATIBLEPROTO2 = 0; + */ + public static final int ENTRYTYPECOMPATIBLEPROTO2_VALUE = 0; /** * TRANSACTIONBEGIN = 1; */ @@ -76,6 +85,10 @@ public final class CanalEntry { public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } return value; } @@ -89,6 +102,7 @@ public final class CanalEntry { public static EntryType forNumber(int value) { switch (value) { + case 0: return ENTRYTYPECOMPATIBLEPROTO2; case 1: return TRANSACTIONBEGIN; case 2: return ROWDATA; case 3: return TRANSACTIONEND; @@ -99,37 +113,40 @@ public final class CanalEntry { } public static com.google.protobuf.Internal.EnumLiteMap - internalGetValueMap() { + internalGetValueMap() { return internalValueMap; } private static final com.google.protobuf.Internal.EnumLiteMap< - EntryType> internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public EntryType findValueByNumber(int number) { - return EntryType.forNumber(number); - } - }; + EntryType> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public EntryType findValueByNumber(int number) { + return EntryType.forNumber(number); + } + }; public final com.google.protobuf.Descriptors.EnumValueDescriptor - getValueDescriptor() { + getValueDescriptor() { return getDescriptor().getValues().get(ordinal()); } public final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptorForType() { + getDescriptorForType() { return getDescriptor(); } public static final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptor() { + getDescriptor() { return com.alibaba.otter.canal.protocol.CanalEntry.getDescriptor().getEnumTypes().get(0); } private static final EntryType[] VALUES = values(); public static EntryType valueOf( - com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { if (desc.getType() != getDescriptor()) { throw new java.lang.IllegalArgumentException( - "EnumValueDescriptor is not for this type."); + "EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; } return VALUES[desc.getIndex()]; } @@ -152,6 +169,10 @@ public final class CanalEntry { */ public enum EventType implements com.google.protobuf.ProtocolMessageEnum { + /** + * EVENTTYPECOMPATIBLEPROTO2 = 0; + */ + EVENTTYPECOMPATIBLEPROTO2(0), /** * INSERT = 1; */ @@ -224,8 +245,13 @@ public final class CanalEntry { * MHEARTBEAT = 15; */ MHEARTBEAT(15), + UNRECOGNIZED(-1), ; + /** + * EVENTTYPECOMPATIBLEPROTO2 = 0; + */ + public static final int EVENTTYPECOMPATIBLEPROTO2_VALUE = 0; /** * INSERT = 1; */ @@ -301,6 +327,10 @@ public final class CanalEntry { public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } return value; } @@ -314,6 +344,7 @@ public final class CanalEntry { public static EventType forNumber(int value) { switch (value) { + case 0: return EVENTTYPECOMPATIBLEPROTO2; case 1: return INSERT; case 2: return UPDATE; case 3: return DELETE; @@ -366,6 +397,9 @@ public final class CanalEntry { throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } return VALUES[desc.getIndex()]; } @@ -387,6 +421,10 @@ public final class CanalEntry { */ public enum Type implements com.google.protobuf.ProtocolMessageEnum { + /** + * TYPECOMPATIBLEPROTO2 = 0; + */ + TYPECOMPATIBLEPROTO2(0), /** * ORACLE = 1; */ @@ -399,8 +437,13 @@ public final class CanalEntry { * PGSQL = 3; */ PGSQL(3), + UNRECOGNIZED(-1), ; + /** + * TYPECOMPATIBLEPROTO2 = 0; + */ + public static final int TYPECOMPATIBLEPROTO2_VALUE = 0; /** * ORACLE = 1; */ @@ -416,6 +459,10 @@ public final class CanalEntry { public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } return value; } @@ -429,6 +476,7 @@ public final class CanalEntry { public static Type forNumber(int value) { switch (value) { + case 0: return TYPECOMPATIBLEPROTO2; case 1: return ORACLE; case 2: return MYSQL; case 3: return PGSQL; @@ -469,6 +517,9 @@ public final class CanalEntry { throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } return VALUES[desc.getIndex()]; } @@ -490,7 +541,7 @@ public final class CanalEntry { **协议头部信息* *
      * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ boolean hasHeader(); /** @@ -498,7 +549,7 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader(); /** @@ -506,24 +557,16 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder(); /** - *
      -     **打散后的事件类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ - boolean hasEntryType(); + int getEntryTypeValue(); /** - *
      -     **打散后的事件类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType(); @@ -532,17 +575,11 @@ public final class CanalEntry { **传输的二进制数组* * * - * optional bytes storeValue = 3; - */ - boolean hasStoreValue(); - /** - *
      -     **传输的二进制数组*
      -     * 
      - * - * optional bytes storeValue = 3; + * bytes storeValue = 3; */ com.google.protobuf.ByteString getStoreValue(); + + public com.alibaba.otter.canal.protocol.CanalEntry.Entry.EntryTypePresentCase getEntryTypePresentCase(); } /** *
      @@ -564,7 +601,6 @@ public final class CanalEntry {
             super(builder);
           }
           private Entry() {
      -      entryType_ = 2;
             storeValue_ = com.google.protobuf.ByteString.EMPTY;
           }
       
      @@ -594,36 +630,30 @@ public final class CanalEntry {
                     break;
                   case 10: {
                     com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder subBuilder = null;
      -              if (((bitField0_ & 0x00000001) == 0x00000001)) {
      +              if (header_ != null) {
                       subBuilder = header_.toBuilder();
                     }
      -              header_ = input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Header.PARSER, extensionRegistry);
      +              header_ = input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Header.parser(), extensionRegistry);
                     if (subBuilder != null) {
                       subBuilder.mergeFrom(header_);
                       header_ = subBuilder.buildPartial();
                     }
      -              bitField0_ |= 0x00000001;
      +
                     break;
                   }
                   case 16: {
                     int rawValue = input.readEnum();
      -                @SuppressWarnings("deprecation")
      -              com.alibaba.otter.canal.protocol.CanalEntry.EntryType value = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf(rawValue);
      -              if (value == null) {
      -                unknownFields.mergeVarintField(2, rawValue);
      -              } else {
      -                bitField0_ |= 0x00000002;
      -                entryType_ = rawValue;
      -              }
      +              entryTypePresentCase_ = 2;
      +              entryTypePresent_ = rawValue;
                     break;
                   }
                   case 26: {
      -              bitField0_ |= 0x00000004;
      +
                     storeValue_ = input.readBytes();
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -654,7 +684,42 @@ public final class CanalEntry {
                     com.alibaba.otter.canal.protocol.CanalEntry.Entry.class, com.alibaba.otter.canal.protocol.CanalEntry.Entry.Builder.class);
           }
       
      -    private int bitField0_;
      +    private int entryTypePresentCase_ = 0;
      +    private java.lang.Object entryTypePresent_;
      +    public enum EntryTypePresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      ENTRYTYPE(2),
      +      ENTRYTYPEPRESENT_NOT_SET(0);
      +      private final int value;
      +      private EntryTypePresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static EntryTypePresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static EntryTypePresentCase forNumber(int value) {
      +        switch (value) {
      +          case 2: return ENTRYTYPE;
      +          case 0: return ENTRYTYPEPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public EntryTypePresentCase
      +    getEntryTypePresentCase() {
      +      return EntryTypePresentCase.forNumber(
      +          entryTypePresentCase_);
      +    }
      +
           public static final int HEADER_FIELD_NUMBER = 1;
           private com.alibaba.otter.canal.protocol.CanalEntry.Header header_;
           /**
      @@ -662,17 +727,17 @@ public final class CanalEntry {
            **协议头部信息*
            * 
      * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public boolean hasHeader() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return header_ != null; } /** *
            **协议头部信息*
            * 
      * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader() { return header_ == null ? com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance() : header_; @@ -682,35 +747,33 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder() { - return header_ == null ? com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance() : header_; + return getHeader(); } public static final int ENTRYTYPE_FIELD_NUMBER = 2; - private int entryType_; /** - *
      -     **打散后的事件类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ - public boolean hasEntryType() { - return ((bitField0_ & 0x00000002) == 0x00000002); + public int getEntryTypeValue() { + if (entryTypePresentCase_ == 2) { + return (java.lang.Integer) entryTypePresent_; + } + return EntryType.ROWDATA_VALUE; } /** - *
      -     **打散后的事件类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ public com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.EntryType result = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf(entryType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA : result; + if (entryTypePresentCase_ == 2) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EntryType result = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf( + (java.lang.Integer) entryTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EntryType.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalEntry.EntryType.TRANSACTIONBEGIN; } public static final int STOREVALUE_FIELD_NUMBER = 3; @@ -720,17 +783,7 @@ public final class CanalEntry { **传输的二进制数组* * * - * optional bytes storeValue = 3; - */ - public boolean hasStoreValue() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -     **传输的二进制数组*
      -     * 
      - * - * optional bytes storeValue = 3; + * bytes storeValue = 3; */ public com.google.protobuf.ByteString getStoreValue() { return storeValue_; @@ -750,13 +803,13 @@ public final class CanalEntry { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (header_ != null) { output.writeMessage(1, getHeader()); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, entryType_); + if (entryTypePresentCase_ == 2) { + output.writeEnum(2, ((java.lang.Integer) entryTypePresent_)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!storeValue_.isEmpty()) { output.writeBytes(3, storeValue_); } unknownFields.writeTo(output); @@ -768,15 +821,15 @@ public final class CanalEntry { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (header_ != null) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(1, getHeader()); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (entryTypePresentCase_ == 2) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(2, entryType_); + .computeEnumSize(2, ((java.lang.Integer) entryTypePresent_)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!storeValue_.isEmpty()) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(3, storeValue_); } @@ -801,14 +854,18 @@ public final class CanalEntry { result = result && getHeader() .equals(other.getHeader()); } - result = result && (hasEntryType() == other.hasEntryType()); - if (hasEntryType()) { - result = result && entryType_ == other.entryType_; - } - result = result && (hasStoreValue() == other.hasStoreValue()); - if (hasStoreValue()) { - result = result && getStoreValue() - .equals(other.getStoreValue()); + result = result && getStoreValue() + .equals(other.getStoreValue()); + result = result && getEntryTypePresentCase().equals( + other.getEntryTypePresentCase()); + if (!result) return false; + switch (entryTypePresentCase_) { + case 2: + result = result && getEntryTypeValue() + == other.getEntryTypeValue(); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -825,13 +882,15 @@ public final class CanalEntry { hash = (37 * hash) + HEADER_FIELD_NUMBER; hash = (53 * hash) + getHeader().hashCode(); } - if (hasEntryType()) { - hash = (37 * hash) + ENTRYTYPE_FIELD_NUMBER; - hash = (53 * hash) + entryType_; - } - if (hasStoreValue()) { - hash = (37 * hash) + STOREVALUE_FIELD_NUMBER; - hash = (53 * hash) + getStoreValue().hashCode(); + hash = (37 * hash) + STOREVALUE_FIELD_NUMBER; + hash = (53 * hash) + getStoreValue().hashCode(); + switch (entryTypePresentCase_) { + case 2: + hash = (37 * hash) + ENTRYTYPE_FIELD_NUMBER; + hash = (53 * hash) + getEntryTypeValue(); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -968,7 +1027,6 @@ public final class CanalEntry { private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessageV3 .alwaysUseFieldBuilders) { - getHeaderFieldBuilder(); } } @java.lang.Override @@ -977,13 +1035,13 @@ public final class CanalEntry { if (headerBuilder_ == null) { header_ = null; } else { - headerBuilder_.clear(); + header_ = null; + headerBuilder_ = null; } - bitField0_ = (bitField0_ & ~0x00000001); - entryType_ = 2; - bitField0_ = (bitField0_ & ~0x00000002); storeValue_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000004); + + entryTypePresentCase_ = 0; + entryTypePresent_ = null; return this; } @@ -1010,25 +1068,16 @@ public final class CanalEntry { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalEntry.Entry buildPartial() { com.alibaba.otter.canal.protocol.CanalEntry.Entry result = new com.alibaba.otter.canal.protocol.CanalEntry.Entry(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } if (headerBuilder_ == null) { result.header_ = header_; } else { result.header_ = headerBuilder_.build(); } - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.entryType_ = entryType_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; + if (entryTypePresentCase_ == 2) { + result.entryTypePresent_ = entryTypePresent_; } result.storeValue_ = storeValue_; - result.bitField0_ = to_bitField0_; + result.entryTypePresentCase_ = entryTypePresentCase_; onBuilt(); return result; } @@ -1080,12 +1129,18 @@ public final class CanalEntry { if (other.hasHeader()) { mergeHeader(other.getHeader()); } - if (other.hasEntryType()) { - setEntryType(other.getEntryType()); - } - if (other.hasStoreValue()) { + if (other.getStoreValue() != com.google.protobuf.ByteString.EMPTY) { setStoreValue(other.getStoreValue()); } + switch (other.getEntryTypePresentCase()) { + case ENTRYTYPE: { + setEntryTypeValue(other.getEntryTypeValue()); + break; + } + case ENTRYTYPEPRESENT_NOT_SET: { + break; + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -1114,7 +1169,21 @@ public final class CanalEntry { } return this; } - private int bitField0_; + private int entryTypePresentCase_ = 0; + private java.lang.Object entryTypePresent_; + public EntryTypePresentCase + getEntryTypePresentCase() { + return EntryTypePresentCase.forNumber( + entryTypePresentCase_); + } + + public Builder clearEntryTypePresent() { + entryTypePresentCase_ = 0; + entryTypePresent_ = null; + onChanged(); + return this; + } + private com.alibaba.otter.canal.protocol.CanalEntry.Header header_ = null; private com.google.protobuf.SingleFieldBuilderV3< @@ -1124,17 +1193,17 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public boolean hasHeader() { - return ((bitField0_ & 0x00000001) == 0x00000001); + return headerBuilder_ != null || header_ != null; } /** *
              **协议头部信息*
              * 
      * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public com.alibaba.otter.canal.protocol.CanalEntry.Header getHeader() { if (headerBuilder_ == null) { @@ -1148,7 +1217,7 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public Builder setHeader(com.alibaba.otter.canal.protocol.CanalEntry.Header value) { if (headerBuilder_ == null) { @@ -1160,7 +1229,7 @@ public final class CanalEntry { } else { headerBuilder_.setMessage(value); } - bitField0_ |= 0x00000001; + return this; } /** @@ -1168,7 +1237,7 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public Builder setHeader( com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder builderForValue) { @@ -1178,7 +1247,7 @@ public final class CanalEntry { } else { headerBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000001; + return this; } /** @@ -1186,13 +1255,11 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public Builder mergeHeader(com.alibaba.otter.canal.protocol.CanalEntry.Header value) { if (headerBuilder_ == null) { - if (((bitField0_ & 0x00000001) == 0x00000001) && - header_ != null && - header_ != com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance()) { + if (header_ != null) { header_ = com.alibaba.otter.canal.protocol.CanalEntry.Header.newBuilder(header_).mergeFrom(value).buildPartial(); } else { @@ -1202,7 +1269,7 @@ public final class CanalEntry { } else { headerBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000001; + return this; } /** @@ -1210,16 +1277,17 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public Builder clearHeader() { if (headerBuilder_ == null) { header_ = null; onChanged(); } else { - headerBuilder_.clear(); + header_ = null; + headerBuilder_ = null; } - bitField0_ = (bitField0_ & ~0x00000001); + return this; } /** @@ -1227,10 +1295,10 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder getHeaderBuilder() { - bitField0_ |= 0x00000001; + onChanged(); return getHeaderFieldBuilder().getBuilder(); } @@ -1239,7 +1307,7 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ public com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder getHeaderOrBuilder() { if (headerBuilder_ != null) { @@ -1254,7 +1322,7 @@ public final class CanalEntry { **协议头部信息* * * - * optional .com.alibaba.otter.canal.protocol.Header header = 1; + * .com.alibaba.otter.canal.protocol.Header header = 1; */ private com.google.protobuf.SingleFieldBuilderV3< com.alibaba.otter.canal.protocol.CanalEntry.Header, com.alibaba.otter.canal.protocol.CanalEntry.Header.Builder, com.alibaba.otter.canal.protocol.CanalEntry.HeaderOrBuilder> @@ -1270,56 +1338,57 @@ public final class CanalEntry { return headerBuilder_; } - private int entryType_ = 2; /** - *
      -       **打散后的事件类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ - public boolean hasEntryType() { - return ((bitField0_ & 0x00000002) == 0x00000002); + public int getEntryTypeValue() { + if (entryTypePresentCase_ == 2) { + return ((java.lang.Integer) entryTypePresent_).intValue(); + } + return 0; } /** - *
      -       **打散后的事件类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; + */ + public Builder setEntryTypeValue(int value) { + entryTypePresentCase_ = 2; + entryTypePresent_ = value; + onChanged(); + return this; + } + /** + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ public com.alibaba.otter.canal.protocol.CanalEntry.EntryType getEntryType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.EntryType result = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf(entryType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EntryType.ROWDATA : result; + if (entryTypePresentCase_ == 2) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EntryType result = com.alibaba.otter.canal.protocol.CanalEntry.EntryType.valueOf( + (java.lang.Integer) entryTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EntryType.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalEntry.EntryType.TRANSACTIONBEGIN; } /** - *
      -       **打散后的事件类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ public Builder setEntryType(com.alibaba.otter.canal.protocol.CanalEntry.EntryType value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; - entryType_ = value.getNumber(); + entryTypePresentCase_ = 2; + entryTypePresent_ = value.getNumber(); onChanged(); return this; } /** - *
      -       **打散后的事件类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EntryType entryType = 2 [default = ROWDATA]; + * .com.alibaba.otter.canal.protocol.EntryType entryType = 2; */ public Builder clearEntryType() { - bitField0_ = (bitField0_ & ~0x00000002); - entryType_ = 2; - onChanged(); + if (entryTypePresentCase_ == 2) { + entryTypePresentCase_ = 0; + entryTypePresent_ = null; + onChanged(); + } return this; } @@ -1329,17 +1398,7 @@ public final class CanalEntry { **传输的二进制数组* * * - * optional bytes storeValue = 3; - */ - public boolean hasStoreValue() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -       **传输的二进制数组*
      -       * 
      - * - * optional bytes storeValue = 3; + * bytes storeValue = 3; */ public com.google.protobuf.ByteString getStoreValue() { return storeValue_; @@ -1349,13 +1408,13 @@ public final class CanalEntry { **传输的二进制数组* * * - * optional bytes storeValue = 3; + * bytes storeValue = 3; */ public Builder setStoreValue(com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + storeValue_ = value; onChanged(); return this; @@ -1365,10 +1424,10 @@ public final class CanalEntry { **传输的二进制数组* * * - * optional bytes storeValue = 3; + * bytes storeValue = 3; */ public Builder clearStoreValue() { - bitField0_ = (bitField0_ & ~0x00000004); + storeValue_ = getDefaultInstance().getStoreValue(); onChanged(); return this; @@ -1376,7 +1435,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -1399,7 +1458,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Entry parsePartialFrom( @@ -1431,19 +1490,7 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - *
      -     **协议的版本号*
      -     * 
      - * - * optional int32 version = 1 [default = 1]; - */ - boolean hasVersion(); - /** - *
      -     **协议的版本号*
      -     * 
      - * - * optional int32 version = 1 [default = 1]; + * int32 version = 1; */ int getVersion(); @@ -1452,15 +1499,7 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; - */ - boolean hasLogfileName(); - /** - *
      -     **binlog/redolog 文件名*
      -     * 
      - * - * optional string logfileName = 2; + * string logfileName = 2; */ java.lang.String getLogfileName(); /** @@ -1468,7 +1507,7 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; + * string logfileName = 2; */ com.google.protobuf.ByteString getLogfileNameBytes(); @@ -1478,15 +1517,7 @@ public final class CanalEntry { **binlog/redolog 文件的偏移位置* * * - * optional int64 logfileOffset = 3; - */ - boolean hasLogfileOffset(); - /** - *
      -     **binlog/redolog 文件的偏移位置*
      -     * 
      - * - * optional int64 logfileOffset = 3; + * int64 logfileOffset = 3; */ long getLogfileOffset(); @@ -1495,15 +1526,7 @@ public final class CanalEntry { **服务端serverId* * * - * optional int64 serverId = 4; - */ - boolean hasServerId(); - /** - *
      -     **服务端serverId*
      -     * 
      - * - * optional int64 serverId = 4; + * int64 serverId = 4; */ long getServerId(); @@ -1512,15 +1535,7 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; - */ - boolean hasServerenCode(); - /** - *
      -     ** 变更数据的编码 *
      -     * 
      - * - * optional string serverenCode = 5; + * string serverenCode = 5; */ java.lang.String getServerenCode(); /** @@ -1528,7 +1543,7 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; + * string serverenCode = 5; */ com.google.protobuf.ByteString getServerenCodeBytes(); @@ -1538,32 +1553,16 @@ public final class CanalEntry { **变更数据的执行时间 * * * - * optional int64 executeTime = 6; - */ - boolean hasExecuteTime(); - /** - *
      -     **变更数据的执行时间 *
      -     * 
      - * - * optional int64 executeTime = 6; + * int64 executeTime = 6; */ long getExecuteTime(); /** - *
      -     ** 变更数据的来源*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ - boolean hasSourceType(); + int getSourceTypeValue(); /** - *
      -     ** 变更数据的来源*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType(); @@ -1572,15 +1571,7 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; - */ - boolean hasSchemaName(); - /** - *
      -     ** 变更数据的schemaname*
      -     * 
      - * - * optional string schemaName = 8; + * string schemaName = 8; */ java.lang.String getSchemaName(); /** @@ -1588,7 +1579,7 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; + * string schemaName = 8; */ com.google.protobuf.ByteString getSchemaNameBytes(); @@ -1598,15 +1589,7 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; - */ - boolean hasTableName(); - /** - *
      -     **变更数据的tablename*
      -     * 
      - * - * optional string tableName = 9; + * string tableName = 9; */ java.lang.String getTableName(); /** @@ -1614,7 +1597,7 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; + * string tableName = 9; */ com.google.protobuf.ByteString getTableNameBytes(); @@ -1624,32 +1607,16 @@ public final class CanalEntry { **每个event的长度* * * - * optional int64 eventLength = 10; - */ - boolean hasEventLength(); - /** - *
      -     **每个event的长度*
      -     * 
      - * - * optional int64 eventLength = 10; + * int64 eventLength = 10; */ long getEventLength(); /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ - boolean hasEventType(); + int getEventTypeValue(); /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType(); @@ -1702,15 +1669,7 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; - */ - boolean hasGtid(); - /** - *
      -     **当前事务的gitd*
      -     * 
      - * - * optional string gtid = 13; + * string gtid = 13; */ java.lang.String getGtid(); /** @@ -1718,10 +1677,16 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; + * string gtid = 13; */ com.google.protobuf.ByteString getGtidBytes(); + + public com.alibaba.otter.canal.protocol.CanalEntry.Header.VersionPresentCase getVersionPresentCase(); + + public com.alibaba.otter.canal.protocol.CanalEntry.Header.SourceTypePresentCase getSourceTypePresentCase(); + + public com.alibaba.otter.canal.protocol.CanalEntry.Header.EventTypePresentCase getEventTypePresentCase(); } /** *
      @@ -1740,17 +1705,14 @@ public final class CanalEntry {
             super(builder);
           }
           private Header() {
      -      version_ = 1;
             logfileName_ = "";
             logfileOffset_ = 0L;
             serverId_ = 0L;
             serverenCode_ = "";
             executeTime_ = 0L;
      -      sourceType_ = 2;
             schemaName_ = "";
             tableName_ = "";
             eventLength_ = 0L;
      -      eventType_ = 2;
             props_ = java.util.Collections.emptyList();
             gtid_ = "";
           }
      @@ -1780,76 +1742,64 @@ public final class CanalEntry {
                     done = true;
                     break;
                   case 8: {
      -              bitField0_ |= 0x00000001;
      -              version_ = input.readInt32();
      +              versionPresentCase_ = 1;
      +              versionPresent_ = input.readInt32();
                     break;
                   }
                   case 18: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000002;
      -              logfileName_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              logfileName_ = s;
                     break;
                   }
                   case 24: {
      -              bitField0_ |= 0x00000004;
      +
                     logfileOffset_ = input.readInt64();
                     break;
                   }
                   case 32: {
      -              bitField0_ |= 0x00000008;
      +
                     serverId_ = input.readInt64();
                     break;
                   }
                   case 42: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000010;
      -              serverenCode_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              serverenCode_ = s;
                     break;
                   }
                   case 48: {
      -              bitField0_ |= 0x00000020;
      +
                     executeTime_ = input.readInt64();
                     break;
                   }
                   case 56: {
                     int rawValue = input.readEnum();
      -                @SuppressWarnings("deprecation")
      -              com.alibaba.otter.canal.protocol.CanalEntry.Type value = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf(rawValue);
      -              if (value == null) {
      -                unknownFields.mergeVarintField(7, rawValue);
      -              } else {
      -                bitField0_ |= 0x00000040;
      -                sourceType_ = rawValue;
      -              }
      +              sourceTypePresentCase_ = 7;
      +              sourceTypePresent_ = rawValue;
                     break;
                   }
                   case 66: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000080;
      -              schemaName_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              schemaName_ = s;
                     break;
                   }
                   case 74: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000100;
      -              tableName_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              tableName_ = s;
                     break;
                   }
                   case 80: {
      -              bitField0_ |= 0x00000200;
      +
                     eventLength_ = input.readInt64();
                     break;
                   }
                   case 88: {
                     int rawValue = input.readEnum();
      -                @SuppressWarnings("deprecation")
      -              com.alibaba.otter.canal.protocol.CanalEntry.EventType value = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(rawValue);
      -              if (value == null) {
      -                unknownFields.mergeVarintField(11, rawValue);
      -              } else {
      -                bitField0_ |= 0x00000400;
      -                eventType_ = rawValue;
      -              }
      +              eventTypePresentCase_ = 11;
      +              eventTypePresent_ = rawValue;
                     break;
                   }
                   case 98: {
      @@ -1858,17 +1808,17 @@ public final class CanalEntry {
                       mutable_bitField0_ |= 0x00000800;
                     }
                     props_.add(
      -                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry));
      +                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.parser(), extensionRegistry));
                     break;
                   }
                   case 106: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000800;
      -              gtid_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              gtid_ = s;
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -1903,27 +1853,123 @@ public final class CanalEntry {
           }
       
           private int bitField0_;
      -    public static final int VERSION_FIELD_NUMBER = 1;
      -    private int version_;
      -    /**
      -     * 
      -     **协议的版本号*
      -     * 
      - * - * optional int32 version = 1 [default = 1]; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000001) == 0x00000001); + private int versionPresentCase_ = 0; + private java.lang.Object versionPresent_; + public enum VersionPresentCase + implements com.google.protobuf.Internal.EnumLite { + VERSION(1), + VERSIONPRESENT_NOT_SET(0); + private final int value; + private VersionPresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static VersionPresentCase valueOf(int value) { + return forNumber(value); + } + + public static VersionPresentCase forNumber(int value) { + switch (value) { + case 1: return VERSION; + case 0: return VERSIONPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public VersionPresentCase + getVersionPresentCase() { + return VersionPresentCase.forNumber( + versionPresentCase_); } + + private int sourceTypePresentCase_ = 0; + private java.lang.Object sourceTypePresent_; + public enum SourceTypePresentCase + implements com.google.protobuf.Internal.EnumLite { + SOURCETYPE(7), + SOURCETYPEPRESENT_NOT_SET(0); + private final int value; + private SourceTypePresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static SourceTypePresentCase valueOf(int value) { + return forNumber(value); + } + + public static SourceTypePresentCase forNumber(int value) { + switch (value) { + case 7: return SOURCETYPE; + case 0: return SOURCETYPEPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public SourceTypePresentCase + getSourceTypePresentCase() { + return SourceTypePresentCase.forNumber( + sourceTypePresentCase_); + } + + private int eventTypePresentCase_ = 0; + private java.lang.Object eventTypePresent_; + public enum EventTypePresentCase + implements com.google.protobuf.Internal.EnumLite { + EVENTTYPE(11), + EVENTTYPEPRESENT_NOT_SET(0); + private final int value; + private EventTypePresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static EventTypePresentCase valueOf(int value) { + return forNumber(value); + } + + public static EventTypePresentCase forNumber(int value) { + switch (value) { + case 11: return EVENTTYPE; + case 0: return EVENTTYPEPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public EventTypePresentCase + getEventTypePresentCase() { + return EventTypePresentCase.forNumber( + eventTypePresentCase_); + } + + public static final int VERSION_FIELD_NUMBER = 1; /** - *
      -     **协议的版本号*
      -     * 
      - * - * optional int32 version = 1 [default = 1]; + * int32 version = 1; */ public int getVersion() { - return version_; + if (versionPresentCase_ == 1) { + return (java.lang.Integer) versionPresent_; + } + return 1; } public static final int LOGFILENAME_FIELD_NUMBER = 2; @@ -1933,17 +1979,7 @@ public final class CanalEntry { **binlog/redolog 文件名* *
      * - * optional string logfileName = 2; - */ - public boolean hasLogfileName() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -     **binlog/redolog 文件名*
      -     * 
      - * - * optional string logfileName = 2; + * string logfileName = 2; */ public java.lang.String getLogfileName() { java.lang.Object ref = logfileName_; @@ -1953,9 +1989,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - logfileName_ = s; - } + logfileName_ = s; return s; } } @@ -1964,7 +1998,7 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; + * string logfileName = 2; */ public com.google.protobuf.ByteString getLogfileNameBytes() { @@ -1987,17 +2021,7 @@ public final class CanalEntry { **binlog/redolog 文件的偏移位置* * * - * optional int64 logfileOffset = 3; - */ - public boolean hasLogfileOffset() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -     **binlog/redolog 文件的偏移位置*
      -     * 
      - * - * optional int64 logfileOffset = 3; + * int64 logfileOffset = 3; */ public long getLogfileOffset() { return logfileOffset_; @@ -2010,17 +2034,7 @@ public final class CanalEntry { **服务端serverId* * * - * optional int64 serverId = 4; - */ - public boolean hasServerId() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -     **服务端serverId*
      -     * 
      - * - * optional int64 serverId = 4; + * int64 serverId = 4; */ public long getServerId() { return serverId_; @@ -2033,17 +2047,7 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; - */ - public boolean hasServerenCode() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - *
      -     ** 变更数据的编码 *
      -     * 
      - * - * optional string serverenCode = 5; + * string serverenCode = 5; */ public java.lang.String getServerenCode() { java.lang.Object ref = serverenCode_; @@ -2053,9 +2057,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - serverenCode_ = s; - } + serverenCode_ = s; return s; } } @@ -2064,7 +2066,7 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; + * string serverenCode = 5; */ public com.google.protobuf.ByteString getServerenCodeBytes() { @@ -2087,45 +2089,33 @@ public final class CanalEntry { **变更数据的执行时间 * * * - * optional int64 executeTime = 6; - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - *
      -     **变更数据的执行时间 *
      -     * 
      - * - * optional int64 executeTime = 6; + * int64 executeTime = 6; */ public long getExecuteTime() { return executeTime_; } public static final int SOURCETYPE_FIELD_NUMBER = 7; - private int sourceType_; /** - *
      -     ** 变更数据的来源*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ - public boolean hasSourceType() { - return ((bitField0_ & 0x00000040) == 0x00000040); + public int getSourceTypeValue() { + if (sourceTypePresentCase_ == 7) { + return (java.lang.Integer) sourceTypePresent_; + } + return 0; } /** - *
      -     ** 变更数据的来源*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ public com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.Type result = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf(sourceType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL : result; + if (sourceTypePresentCase_ == 7) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.Type result = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf( + (java.lang.Integer) sourceTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.Type.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalEntry.Type.TYPECOMPATIBLEPROTO2; } public static final int SCHEMANAME_FIELD_NUMBER = 8; @@ -2135,17 +2125,7 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; - */ - public boolean hasSchemaName() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - *
      -     ** 变更数据的schemaname*
      -     * 
      - * - * optional string schemaName = 8; + * string schemaName = 8; */ public java.lang.String getSchemaName() { java.lang.Object ref = schemaName_; @@ -2155,9 +2135,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - schemaName_ = s; - } + schemaName_ = s; return s; } } @@ -2166,7 +2144,7 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; + * string schemaName = 8; */ public com.google.protobuf.ByteString getSchemaNameBytes() { @@ -2189,17 +2167,7 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; - */ - public boolean hasTableName() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - /** - *
      -     **变更数据的tablename*
      -     * 
      - * - * optional string tableName = 9; + * string tableName = 9; */ public java.lang.String getTableName() { java.lang.Object ref = tableName_; @@ -2209,9 +2177,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - tableName_ = s; - } + tableName_ = s; return s; } } @@ -2220,7 +2186,7 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; + * string tableName = 9; */ public com.google.protobuf.ByteString getTableNameBytes() { @@ -2243,45 +2209,33 @@ public final class CanalEntry { **每个event的长度* * * - * optional int64 eventLength = 10; - */ - public boolean hasEventLength() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - /** - *
      -     **每个event的长度*
      -     * 
      - * - * optional int64 eventLength = 10; + * int64 eventLength = 10; */ public long getEventLength() { return eventLength_; } public static final int EVENTTYPE_FIELD_NUMBER = 11; - private int eventType_; /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000400) == 0x00000400); + public int getEventTypeValue() { + if (eventTypePresentCase_ == 11) { + return (java.lang.Integer) eventTypePresent_; + } + return 0; } /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; + if (eventTypePresentCase_ == 11) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf( + (java.lang.Integer) eventTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalEntry.EventType.EVENTTYPECOMPATIBLEPROTO2; } public static final int PROPS_FIELD_NUMBER = 12; @@ -2346,17 +2300,7 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; - */ - public boolean hasGtid() { - return ((bitField0_ & 0x00000800) == 0x00000800); - } - /** - *
      -     **当前事务的gitd*
      -     * 
      - * - * optional string gtid = 13; + * string gtid = 13; */ public java.lang.String getGtid() { java.lang.Object ref = gtid_; @@ -2366,9 +2310,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - gtid_ = s; - } + gtid_ = s; return s; } } @@ -2377,7 +2319,7 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; + * string gtid = 13; */ public com.google.protobuf.ByteString getGtidBytes() { @@ -2407,43 +2349,44 @@ public final class CanalEntry { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt32(1, version_); + if (versionPresentCase_ == 1) { + output.writeInt32( + 1, (int)((java.lang.Integer) versionPresent_)); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getLogfileNameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, logfileName_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (logfileOffset_ != 0L) { output.writeInt64(3, logfileOffset_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (serverId_ != 0L) { output.writeInt64(4, serverId_); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!getServerenCodeBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 5, serverenCode_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (executeTime_ != 0L) { output.writeInt64(6, executeTime_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { - output.writeEnum(7, sourceType_); + if (sourceTypePresentCase_ == 7) { + output.writeEnum(7, ((java.lang.Integer) sourceTypePresent_)); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (!getSchemaNameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 8, schemaName_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (!getTableNameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 9, tableName_); } - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (eventLength_ != 0L) { output.writeInt64(10, eventLength_); } - if (((bitField0_ & 0x00000400) == 0x00000400)) { - output.writeEnum(11, eventType_); + if (eventTypePresentCase_ == 11) { + output.writeEnum(11, ((java.lang.Integer) eventTypePresent_)); } for (int i = 0; i < props_.size(); i++) { output.writeMessage(12, props_.get(i)); } - if (((bitField0_ & 0x00000800) == 0x00000800)) { + if (!getGtidBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 13, gtid_); } unknownFields.writeTo(output); @@ -2455,51 +2398,52 @@ public final class CanalEntry { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (versionPresentCase_ == 1) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(1, version_); + .computeInt32Size( + 1, (int)((java.lang.Integer) versionPresent_)); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getLogfileNameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, logfileName_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (logfileOffset_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(3, logfileOffset_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (serverId_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(4, serverId_); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!getServerenCodeBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, serverenCode_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (executeTime_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(6, executeTime_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (sourceTypePresentCase_ == 7) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(7, sourceType_); + .computeEnumSize(7, ((java.lang.Integer) sourceTypePresent_)); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (!getSchemaNameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, schemaName_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (!getTableNameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(9, tableName_); } - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (eventLength_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(10, eventLength_); } - if (((bitField0_ & 0x00000400) == 0x00000400)) { + if (eventTypePresentCase_ == 11) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(11, eventType_); + .computeEnumSize(11, ((java.lang.Integer) eventTypePresent_)); } for (int i = 0; i < props_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(12, props_.get(i)); } - if (((bitField0_ & 0x00000800) == 0x00000800)) { + if (!getGtidBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(13, gtid_); } size += unknownFields.getSerializedSize(); @@ -2518,65 +2462,58 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.Header other = (com.alibaba.otter.canal.protocol.CanalEntry.Header) obj; boolean result = true; - result = result && (hasVersion() == other.hasVersion()); - if (hasVersion()) { - result = result && (getVersion() - == other.getVersion()); - } - result = result && (hasLogfileName() == other.hasLogfileName()); - if (hasLogfileName()) { - result = result && getLogfileName() - .equals(other.getLogfileName()); - } - result = result && (hasLogfileOffset() == other.hasLogfileOffset()); - if (hasLogfileOffset()) { - result = result && (getLogfileOffset() - == other.getLogfileOffset()); - } - result = result && (hasServerId() == other.hasServerId()); - if (hasServerId()) { - result = result && (getServerId() - == other.getServerId()); - } - result = result && (hasServerenCode() == other.hasServerenCode()); - if (hasServerenCode()) { - result = result && getServerenCode() - .equals(other.getServerenCode()); - } - result = result && (hasExecuteTime() == other.hasExecuteTime()); - if (hasExecuteTime()) { - result = result && (getExecuteTime() - == other.getExecuteTime()); - } - result = result && (hasSourceType() == other.hasSourceType()); - if (hasSourceType()) { - result = result && sourceType_ == other.sourceType_; - } - result = result && (hasSchemaName() == other.hasSchemaName()); - if (hasSchemaName()) { - result = result && getSchemaName() - .equals(other.getSchemaName()); - } - result = result && (hasTableName() == other.hasTableName()); - if (hasTableName()) { - result = result && getTableName() - .equals(other.getTableName()); - } - result = result && (hasEventLength() == other.hasEventLength()); - if (hasEventLength()) { - result = result && (getEventLength() - == other.getEventLength()); - } - result = result && (hasEventType() == other.hasEventType()); - if (hasEventType()) { - result = result && eventType_ == other.eventType_; - } + result = result && getLogfileName() + .equals(other.getLogfileName()); + result = result && (getLogfileOffset() + == other.getLogfileOffset()); + result = result && (getServerId() + == other.getServerId()); + result = result && getServerenCode() + .equals(other.getServerenCode()); + result = result && (getExecuteTime() + == other.getExecuteTime()); + result = result && getSchemaName() + .equals(other.getSchemaName()); + result = result && getTableName() + .equals(other.getTableName()); + result = result && (getEventLength() + == other.getEventLength()); result = result && getPropsList() .equals(other.getPropsList()); - result = result && (hasGtid() == other.hasGtid()); - if (hasGtid()) { - result = result && getGtid() - .equals(other.getGtid()); + result = result && getGtid() + .equals(other.getGtid()); + result = result && getVersionPresentCase().equals( + other.getVersionPresentCase()); + if (!result) return false; + switch (versionPresentCase_) { + case 1: + result = result && (getVersion() + == other.getVersion()); + break; + case 0: + default: + } + result = result && getSourceTypePresentCase().equals( + other.getSourceTypePresentCase()); + if (!result) return false; + switch (sourceTypePresentCase_) { + case 7: + result = result && getSourceTypeValue() + == other.getSourceTypeValue(); + break; + case 0: + default: + } + result = result && getEventTypePresentCase().equals( + other.getEventTypePresentCase()); + if (!result) return false; + switch (eventTypePresentCase_) { + case 11: + result = result && getEventTypeValue() + == other.getEventTypeValue(); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -2589,61 +2526,55 @@ public final class CanalEntry { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasVersion()) { - hash = (37 * hash) + VERSION_FIELD_NUMBER; - hash = (53 * hash) + getVersion(); - } - if (hasLogfileName()) { - hash = (37 * hash) + LOGFILENAME_FIELD_NUMBER; - hash = (53 * hash) + getLogfileName().hashCode(); - } - if (hasLogfileOffset()) { - hash = (37 * hash) + LOGFILEOFFSET_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getLogfileOffset()); - } - if (hasServerId()) { - hash = (37 * hash) + SERVERID_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getServerId()); - } - if (hasServerenCode()) { - hash = (37 * hash) + SERVERENCODE_FIELD_NUMBER; - hash = (53 * hash) + getServerenCode().hashCode(); - } - if (hasExecuteTime()) { - hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getExecuteTime()); - } - if (hasSourceType()) { - hash = (37 * hash) + SOURCETYPE_FIELD_NUMBER; - hash = (53 * hash) + sourceType_; - } - if (hasSchemaName()) { - hash = (37 * hash) + SCHEMANAME_FIELD_NUMBER; - hash = (53 * hash) + getSchemaName().hashCode(); - } - if (hasTableName()) { - hash = (37 * hash) + TABLENAME_FIELD_NUMBER; - hash = (53 * hash) + getTableName().hashCode(); - } - if (hasEventLength()) { - hash = (37 * hash) + EVENTLENGTH_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getEventLength()); - } - if (hasEventType()) { - hash = (37 * hash) + EVENTTYPE_FIELD_NUMBER; - hash = (53 * hash) + eventType_; - } + hash = (37 * hash) + LOGFILENAME_FIELD_NUMBER; + hash = (53 * hash) + getLogfileName().hashCode(); + hash = (37 * hash) + LOGFILEOFFSET_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getLogfileOffset()); + hash = (37 * hash) + SERVERID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getServerId()); + hash = (37 * hash) + SERVERENCODE_FIELD_NUMBER; + hash = (53 * hash) + getServerenCode().hashCode(); + hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getExecuteTime()); + hash = (37 * hash) + SCHEMANAME_FIELD_NUMBER; + hash = (53 * hash) + getSchemaName().hashCode(); + hash = (37 * hash) + TABLENAME_FIELD_NUMBER; + hash = (53 * hash) + getTableName().hashCode(); + hash = (37 * hash) + EVENTLENGTH_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getEventLength()); if (getPropsCount() > 0) { hash = (37 * hash) + PROPS_FIELD_NUMBER; hash = (53 * hash) + getPropsList().hashCode(); } - if (hasGtid()) { - hash = (37 * hash) + GTID_FIELD_NUMBER; - hash = (53 * hash) + getGtid().hashCode(); + hash = (37 * hash) + GTID_FIELD_NUMBER; + hash = (53 * hash) + getGtid().hashCode(); + switch (versionPresentCase_) { + case 1: + hash = (37 * hash) + VERSION_FIELD_NUMBER; + hash = (53 * hash) + getVersion(); + break; + case 0: + default: + } + switch (sourceTypePresentCase_) { + case 7: + hash = (37 * hash) + SOURCETYPE_FIELD_NUMBER; + hash = (53 * hash) + getSourceTypeValue(); + break; + case 0: + default: + } + switch (eventTypePresentCase_) { + case 11: + hash = (37 * hash) + EVENTTYPE_FIELD_NUMBER; + hash = (53 * hash) + getEventTypeValue(); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -2783,28 +2714,22 @@ public final class CanalEntry { @java.lang.Override public Builder clear() { super.clear(); - version_ = 1; - bitField0_ = (bitField0_ & ~0x00000001); logfileName_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + logfileOffset_ = 0L; - bitField0_ = (bitField0_ & ~0x00000004); + serverId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000008); + serverenCode_ = ""; - bitField0_ = (bitField0_ & ~0x00000010); + executeTime_ = 0L; - bitField0_ = (bitField0_ & ~0x00000020); - sourceType_ = 2; - bitField0_ = (bitField0_ & ~0x00000040); + schemaName_ = ""; - bitField0_ = (bitField0_ & ~0x00000080); + tableName_ = ""; - bitField0_ = (bitField0_ & ~0x00000100); + eventLength_ = 0L; - bitField0_ = (bitField0_ & ~0x00000200); - eventType_ = 2; - bitField0_ = (bitField0_ & ~0x00000400); + if (propsBuilder_ == null) { props_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000800); @@ -2812,7 +2737,13 @@ public final class CanalEntry { propsBuilder_.clear(); } gtid_ = ""; - bitField0_ = (bitField0_ & ~0x00001000); + + versionPresentCase_ = 0; + versionPresent_ = null; + sourceTypePresentCase_ = 0; + sourceTypePresent_ = null; + eventTypePresentCase_ = 0; + eventTypePresent_ = null; return this; } @@ -2841,50 +2772,23 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.Header result = new com.alibaba.otter.canal.protocol.CanalEntry.Header(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.version_ = version_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; + if (versionPresentCase_ == 1) { + result.versionPresent_ = versionPresent_; } result.logfileName_ = logfileName_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } result.logfileOffset_ = logfileOffset_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } result.serverId_ = serverId_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } result.serverenCode_ = serverenCode_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } result.executeTime_ = executeTime_; - if (((from_bitField0_ & 0x00000040) == 0x00000040)) { - to_bitField0_ |= 0x00000040; - } - result.sourceType_ = sourceType_; - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000080; + if (sourceTypePresentCase_ == 7) { + result.sourceTypePresent_ = sourceTypePresent_; } result.schemaName_ = schemaName_; - if (((from_bitField0_ & 0x00000100) == 0x00000100)) { - to_bitField0_ |= 0x00000100; - } result.tableName_ = tableName_; - if (((from_bitField0_ & 0x00000200) == 0x00000200)) { - to_bitField0_ |= 0x00000200; - } result.eventLength_ = eventLength_; - if (((from_bitField0_ & 0x00000400) == 0x00000400)) { - to_bitField0_ |= 0x00000400; + if (eventTypePresentCase_ == 11) { + result.eventTypePresent_ = eventTypePresent_; } - result.eventType_ = eventType_; if (propsBuilder_ == null) { if (((bitField0_ & 0x00000800) == 0x00000800)) { props_ = java.util.Collections.unmodifiableList(props_); @@ -2894,11 +2798,11 @@ public final class CanalEntry { } else { result.props_ = propsBuilder_.build(); } - if (((from_bitField0_ & 0x00001000) == 0x00001000)) { - to_bitField0_ |= 0x00000800; - } result.gtid_ = gtid_; result.bitField0_ = to_bitField0_; + result.versionPresentCase_ = versionPresentCase_; + result.sourceTypePresentCase_ = sourceTypePresentCase_; + result.eventTypePresentCase_ = eventTypePresentCase_; onBuilt(); return result; } @@ -2947,47 +2851,34 @@ public final class CanalEntry { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Header other) { if (other == com.alibaba.otter.canal.protocol.CanalEntry.Header.getDefaultInstance()) return this; - if (other.hasVersion()) { - setVersion(other.getVersion()); - } - if (other.hasLogfileName()) { - bitField0_ |= 0x00000002; + if (!other.getLogfileName().isEmpty()) { logfileName_ = other.logfileName_; onChanged(); } - if (other.hasLogfileOffset()) { + if (other.getLogfileOffset() != 0L) { setLogfileOffset(other.getLogfileOffset()); } - if (other.hasServerId()) { + if (other.getServerId() != 0L) { setServerId(other.getServerId()); } - if (other.hasServerenCode()) { - bitField0_ |= 0x00000010; + if (!other.getServerenCode().isEmpty()) { serverenCode_ = other.serverenCode_; onChanged(); } - if (other.hasExecuteTime()) { + if (other.getExecuteTime() != 0L) { setExecuteTime(other.getExecuteTime()); } - if (other.hasSourceType()) { - setSourceType(other.getSourceType()); - } - if (other.hasSchemaName()) { - bitField0_ |= 0x00000080; + if (!other.getSchemaName().isEmpty()) { schemaName_ = other.schemaName_; onChanged(); } - if (other.hasTableName()) { - bitField0_ |= 0x00000100; + if (!other.getTableName().isEmpty()) { tableName_ = other.tableName_; onChanged(); } - if (other.hasEventLength()) { + if (other.getEventLength() != 0L) { setEventLength(other.getEventLength()); } - if (other.hasEventType()) { - setEventType(other.getEventType()); - } if (propsBuilder_ == null) { if (!other.props_.isEmpty()) { if (props_.isEmpty()) { @@ -3014,11 +2905,37 @@ public final class CanalEntry { } } } - if (other.hasGtid()) { - bitField0_ |= 0x00001000; + if (!other.getGtid().isEmpty()) { gtid_ = other.gtid_; onChanged(); } + switch (other.getVersionPresentCase()) { + case VERSION: { + setVersion(other.getVersion()); + break; + } + case VERSIONPRESENT_NOT_SET: { + break; + } + } + switch (other.getSourceTypePresentCase()) { + case SOURCETYPE: { + setSourceTypeValue(other.getSourceTypeValue()); + break; + } + case SOURCETYPEPRESENT_NOT_SET: { + break; + } + } + switch (other.getEventTypePresentCase()) { + case EVENTTYPE: { + setEventTypeValue(other.getEventTypeValue()); + break; + } + case EVENTTYPEPRESENT_NOT_SET: { + break; + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -3047,53 +2964,80 @@ public final class CanalEntry { } return this; } + private int versionPresentCase_ = 0; + private java.lang.Object versionPresent_; + public VersionPresentCase + getVersionPresentCase() { + return VersionPresentCase.forNumber( + versionPresentCase_); + } + + public Builder clearVersionPresent() { + versionPresentCase_ = 0; + versionPresent_ = null; + onChanged(); + return this; + } + + private int sourceTypePresentCase_ = 0; + private java.lang.Object sourceTypePresent_; + public SourceTypePresentCase + getSourceTypePresentCase() { + return SourceTypePresentCase.forNumber( + sourceTypePresentCase_); + } + + public Builder clearSourceTypePresent() { + sourceTypePresentCase_ = 0; + sourceTypePresent_ = null; + onChanged(); + return this; + } + + private int eventTypePresentCase_ = 0; + private java.lang.Object eventTypePresent_; + public EventTypePresentCase + getEventTypePresentCase() { + return EventTypePresentCase.forNumber( + eventTypePresentCase_); + } + + public Builder clearEventTypePresent() { + eventTypePresentCase_ = 0; + eventTypePresent_ = null; + onChanged(); + return this; + } + private int bitField0_; - private int version_ = 1; /** - *
      -       **协议的版本号*
      -       * 
      - * - * optional int32 version = 1 [default = 1]; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -       **协议的版本号*
      -       * 
      - * - * optional int32 version = 1 [default = 1]; + * int32 version = 1; */ public int getVersion() { - return version_; + if (versionPresentCase_ == 1) { + return (java.lang.Integer) versionPresent_; + } + return 0; } /** - *
      -       **协议的版本号*
      -       * 
      - * - * optional int32 version = 1 [default = 1]; + * int32 version = 1; */ public Builder setVersion(int value) { - bitField0_ |= 0x00000001; - version_ = value; + versionPresentCase_ = 1; + versionPresent_ = value; onChanged(); return this; } /** - *
      -       **协议的版本号*
      -       * 
      - * - * optional int32 version = 1 [default = 1]; + * int32 version = 1; */ public Builder clearVersion() { - bitField0_ = (bitField0_ & ~0x00000001); - version_ = 1; - onChanged(); + if (versionPresentCase_ == 1) { + versionPresentCase_ = 0; + versionPresent_ = null; + onChanged(); + } return this; } @@ -3103,17 +3047,7 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; - */ - public boolean hasLogfileName() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -       **binlog/redolog 文件名*
      -       * 
      - * - * optional string logfileName = 2; + * string logfileName = 2; */ public java.lang.String getLogfileName() { java.lang.Object ref = logfileName_; @@ -3121,9 +3055,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - logfileName_ = s; - } + logfileName_ = s; return s; } else { return (java.lang.String) ref; @@ -3134,7 +3066,7 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; + * string logfileName = 2; */ public com.google.protobuf.ByteString getLogfileNameBytes() { @@ -3154,14 +3086,14 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; + * string logfileName = 2; */ public Builder setLogfileName( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + logfileName_ = value; onChanged(); return this; @@ -3171,10 +3103,10 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; + * string logfileName = 2; */ public Builder clearLogfileName() { - bitField0_ = (bitField0_ & ~0x00000002); + logfileName_ = getDefaultInstance().getLogfileName(); onChanged(); return this; @@ -3184,14 +3116,15 @@ public final class CanalEntry { **binlog/redolog 文件名* * * - * optional string logfileName = 2; + * string logfileName = 2; */ public Builder setLogfileNameBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + logfileName_ = value; onChanged(); return this; @@ -3203,17 +3136,7 @@ public final class CanalEntry { **binlog/redolog 文件的偏移位置* * * - * optional int64 logfileOffset = 3; - */ - public boolean hasLogfileOffset() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -       **binlog/redolog 文件的偏移位置*
      -       * 
      - * - * optional int64 logfileOffset = 3; + * int64 logfileOffset = 3; */ public long getLogfileOffset() { return logfileOffset_; @@ -3223,10 +3146,10 @@ public final class CanalEntry { **binlog/redolog 文件的偏移位置* * * - * optional int64 logfileOffset = 3; + * int64 logfileOffset = 3; */ public Builder setLogfileOffset(long value) { - bitField0_ |= 0x00000004; + logfileOffset_ = value; onChanged(); return this; @@ -3236,10 +3159,10 @@ public final class CanalEntry { **binlog/redolog 文件的偏移位置* * * - * optional int64 logfileOffset = 3; + * int64 logfileOffset = 3; */ public Builder clearLogfileOffset() { - bitField0_ = (bitField0_ & ~0x00000004); + logfileOffset_ = 0L; onChanged(); return this; @@ -3251,17 +3174,7 @@ public final class CanalEntry { **服务端serverId* * * - * optional int64 serverId = 4; - */ - public boolean hasServerId() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -       **服务端serverId*
      -       * 
      - * - * optional int64 serverId = 4; + * int64 serverId = 4; */ public long getServerId() { return serverId_; @@ -3271,10 +3184,10 @@ public final class CanalEntry { **服务端serverId* * * - * optional int64 serverId = 4; + * int64 serverId = 4; */ public Builder setServerId(long value) { - bitField0_ |= 0x00000008; + serverId_ = value; onChanged(); return this; @@ -3284,10 +3197,10 @@ public final class CanalEntry { **服务端serverId* * * - * optional int64 serverId = 4; + * int64 serverId = 4; */ public Builder clearServerId() { - bitField0_ = (bitField0_ & ~0x00000008); + serverId_ = 0L; onChanged(); return this; @@ -3299,17 +3212,7 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; - */ - public boolean hasServerenCode() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - *
      -       ** 变更数据的编码 *
      -       * 
      - * - * optional string serverenCode = 5; + * string serverenCode = 5; */ public java.lang.String getServerenCode() { java.lang.Object ref = serverenCode_; @@ -3317,9 +3220,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - serverenCode_ = s; - } + serverenCode_ = s; return s; } else { return (java.lang.String) ref; @@ -3330,7 +3231,7 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; + * string serverenCode = 5; */ public com.google.protobuf.ByteString getServerenCodeBytes() { @@ -3350,14 +3251,14 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; + * string serverenCode = 5; */ public Builder setServerenCode( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000010; + serverenCode_ = value; onChanged(); return this; @@ -3367,10 +3268,10 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; + * string serverenCode = 5; */ public Builder clearServerenCode() { - bitField0_ = (bitField0_ & ~0x00000010); + serverenCode_ = getDefaultInstance().getServerenCode(); onChanged(); return this; @@ -3380,14 +3281,15 @@ public final class CanalEntry { ** 变更数据的编码 * * * - * optional string serverenCode = 5; + * string serverenCode = 5; */ public Builder setServerenCodeBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000010; + checkByteStringIsUtf8(value); + serverenCode_ = value; onChanged(); return this; @@ -3399,17 +3301,7 @@ public final class CanalEntry { **变更数据的执行时间 * * * - * optional int64 executeTime = 6; - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - *
      -       **变更数据的执行时间 *
      -       * 
      - * - * optional int64 executeTime = 6; + * int64 executeTime = 6; */ public long getExecuteTime() { return executeTime_; @@ -3419,10 +3311,10 @@ public final class CanalEntry { **变更数据的执行时间 * * * - * optional int64 executeTime = 6; + * int64 executeTime = 6; */ public Builder setExecuteTime(long value) { - bitField0_ |= 0x00000020; + executeTime_ = value; onChanged(); return this; @@ -3432,65 +3324,66 @@ public final class CanalEntry { **变更数据的执行时间 * * * - * optional int64 executeTime = 6; + * int64 executeTime = 6; */ public Builder clearExecuteTime() { - bitField0_ = (bitField0_ & ~0x00000020); + executeTime_ = 0L; onChanged(); return this; } - private int sourceType_ = 2; /** - *
      -       ** 变更数据的来源*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ - public boolean hasSourceType() { - return ((bitField0_ & 0x00000040) == 0x00000040); + public int getSourceTypeValue() { + if (sourceTypePresentCase_ == 7) { + return ((java.lang.Integer) sourceTypePresent_).intValue(); + } + return Type.MYSQL_VALUE; } /** - *
      -       ** 变更数据的来源*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; + */ + public Builder setSourceTypeValue(int value) { + sourceTypePresentCase_ = 7; + sourceTypePresent_ = value; + onChanged(); + return this; + } + /** + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ public com.alibaba.otter.canal.protocol.CanalEntry.Type getSourceType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.Type result = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf(sourceType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.Type.MYSQL : result; + if (sourceTypePresentCase_ == 7) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.Type result = com.alibaba.otter.canal.protocol.CanalEntry.Type.valueOf( + (java.lang.Integer) sourceTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.Type.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalEntry.Type.TYPECOMPATIBLEPROTO2; } /** - *
      -       ** 变更数据的来源*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ public Builder setSourceType(com.alibaba.otter.canal.protocol.CanalEntry.Type value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000040; - sourceType_ = value.getNumber(); + sourceTypePresentCase_ = 7; + sourceTypePresent_ = value.getNumber(); onChanged(); return this; } /** - *
      -       ** 变更数据的来源*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.Type sourceType = 7 [default = MYSQL]; + * .com.alibaba.otter.canal.protocol.Type sourceType = 7; */ public Builder clearSourceType() { - bitField0_ = (bitField0_ & ~0x00000040); - sourceType_ = 2; - onChanged(); + if (sourceTypePresentCase_ == 7) { + sourceTypePresentCase_ = 0; + sourceTypePresent_ = null; + onChanged(); + } return this; } @@ -3500,17 +3393,7 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; - */ - public boolean hasSchemaName() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - *
      -       ** 变更数据的schemaname*
      -       * 
      - * - * optional string schemaName = 8; + * string schemaName = 8; */ public java.lang.String getSchemaName() { java.lang.Object ref = schemaName_; @@ -3518,9 +3401,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - schemaName_ = s; - } + schemaName_ = s; return s; } else { return (java.lang.String) ref; @@ -3531,7 +3412,7 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; + * string schemaName = 8; */ public com.google.protobuf.ByteString getSchemaNameBytes() { @@ -3551,14 +3432,14 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; + * string schemaName = 8; */ public Builder setSchemaName( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000080; + schemaName_ = value; onChanged(); return this; @@ -3568,10 +3449,10 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; + * string schemaName = 8; */ public Builder clearSchemaName() { - bitField0_ = (bitField0_ & ~0x00000080); + schemaName_ = getDefaultInstance().getSchemaName(); onChanged(); return this; @@ -3581,14 +3462,15 @@ public final class CanalEntry { ** 变更数据的schemaname* * * - * optional string schemaName = 8; + * string schemaName = 8; */ public Builder setSchemaNameBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000080; + checkByteStringIsUtf8(value); + schemaName_ = value; onChanged(); return this; @@ -3600,17 +3482,7 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; - */ - public boolean hasTableName() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - /** - *
      -       **变更数据的tablename*
      -       * 
      - * - * optional string tableName = 9; + * string tableName = 9; */ public java.lang.String getTableName() { java.lang.Object ref = tableName_; @@ -3618,9 +3490,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - tableName_ = s; - } + tableName_ = s; return s; } else { return (java.lang.String) ref; @@ -3631,7 +3501,7 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; + * string tableName = 9; */ public com.google.protobuf.ByteString getTableNameBytes() { @@ -3651,14 +3521,14 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; + * string tableName = 9; */ public Builder setTableName( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000100; + tableName_ = value; onChanged(); return this; @@ -3668,10 +3538,10 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; + * string tableName = 9; */ public Builder clearTableName() { - bitField0_ = (bitField0_ & ~0x00000100); + tableName_ = getDefaultInstance().getTableName(); onChanged(); return this; @@ -3681,14 +3551,15 @@ public final class CanalEntry { **变更数据的tablename* * * - * optional string tableName = 9; + * string tableName = 9; */ public Builder setTableNameBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000100; + checkByteStringIsUtf8(value); + tableName_ = value; onChanged(); return this; @@ -3700,17 +3571,7 @@ public final class CanalEntry { **每个event的长度* * * - * optional int64 eventLength = 10; - */ - public boolean hasEventLength() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - /** - *
      -       **每个event的长度*
      -       * 
      - * - * optional int64 eventLength = 10; + * int64 eventLength = 10; */ public long getEventLength() { return eventLength_; @@ -3720,10 +3581,10 @@ public final class CanalEntry { **每个event的长度* * * - * optional int64 eventLength = 10; + * int64 eventLength = 10; */ public Builder setEventLength(long value) { - bitField0_ |= 0x00000200; + eventLength_ = value; onChanged(); return this; @@ -3733,65 +3594,66 @@ public final class CanalEntry { **每个event的长度* * * - * optional int64 eventLength = 10; + * int64 eventLength = 10; */ public Builder clearEventLength() { - bitField0_ = (bitField0_ & ~0x00000200); + eventLength_ = 0L; onChanged(); return this; } - private int eventType_ = 2; /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000400) == 0x00000400); + public int getEventTypeValue() { + if (eventTypePresentCase_ == 11) { + return ((java.lang.Integer) eventTypePresent_).intValue(); + } + return EventType.UPDATE_VALUE; } /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; + */ + public Builder setEventTypeValue(int value) { + eventTypePresentCase_ = 11; + eventTypePresent_ = value; + onChanged(); + return this; + } + /** + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; + if (eventTypePresentCase_ == 11) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf( + (java.lang.Integer) eventTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalEntry.EventType.EVENTTYPECOMPATIBLEPROTO2; } /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ public Builder setEventType(com.alibaba.otter.canal.protocol.CanalEntry.EventType value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000400; - eventType_ = value.getNumber(); + eventTypePresentCase_ = 11; + eventTypePresent_ = value.getNumber(); onChanged(); return this; } /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 11 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 11; */ public Builder clearEventType() { - bitField0_ = (bitField0_ & ~0x00000400); - eventType_ = 2; - onChanged(); + if (eventTypePresentCase_ == 11) { + eventTypePresentCase_ = 0; + eventTypePresent_ = null; + onChanged(); + } return this; } @@ -4113,17 +3975,7 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; - */ - public boolean hasGtid() { - return ((bitField0_ & 0x00001000) == 0x00001000); - } - /** - *
      -       **当前事务的gitd*
      -       * 
      - * - * optional string gtid = 13; + * string gtid = 13; */ public java.lang.String getGtid() { java.lang.Object ref = gtid_; @@ -4131,9 +3983,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - gtid_ = s; - } + gtid_ = s; return s; } else { return (java.lang.String) ref; @@ -4144,7 +3994,7 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; + * string gtid = 13; */ public com.google.protobuf.ByteString getGtidBytes() { @@ -4164,14 +4014,14 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; + * string gtid = 13; */ public Builder setGtid( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00001000; + gtid_ = value; onChanged(); return this; @@ -4181,10 +4031,10 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; + * string gtid = 13; */ public Builder clearGtid() { - bitField0_ = (bitField0_ & ~0x00001000); + gtid_ = getDefaultInstance().getGtid(); onChanged(); return this; @@ -4194,14 +4044,15 @@ public final class CanalEntry { **当前事务的gitd* * * - * optional string gtid = 13; + * string gtid = 13; */ public Builder setGtidBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00001000; + checkByteStringIsUtf8(value); + gtid_ = value; onChanged(); return this; @@ -4209,7 +4060,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -4232,7 +4083,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser
      + private static final com.google.protobuf.Parser
      PARSER = new com.google.protobuf.AbstractParser
      () { @java.lang.Override public Header parsePartialFrom( @@ -4268,15 +4119,7 @@ public final class CanalEntry { **字段下标* * * - * optional int32 index = 1; - */ - boolean hasIndex(); - /** - *
      -     **字段下标*
      -     * 
      - * - * optional int32 index = 1; + * int32 index = 1; */ int getIndex(); @@ -4285,15 +4128,7 @@ public final class CanalEntry { **字段java中类型* * * - * optional int32 sqlType = 2; - */ - boolean hasSqlType(); - /** - *
      -     **字段java中类型*
      -     * 
      - * - * optional int32 sqlType = 2; + * int32 sqlType = 2; */ int getSqlType(); @@ -4302,15 +4137,7 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; - */ - boolean hasName(); - /** - *
      -     **字段名称(忽略大小写),在mysql中是没有的*
      -     * 
      - * - * optional string name = 3; + * string name = 3; */ java.lang.String getName(); /** @@ -4318,7 +4145,7 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; + * string name = 3; */ com.google.protobuf.ByteString getNameBytes(); @@ -4328,15 +4155,7 @@ public final class CanalEntry { **是否是主键* * * - * optional bool isKey = 4; - */ - boolean hasIsKey(); - /** - *
      -     **是否是主键*
      -     * 
      - * - * optional bool isKey = 4; + * bool isKey = 4; */ boolean getIsKey(); @@ -4345,32 +4164,12 @@ public final class CanalEntry { **如果EventType=UPDATE,用于标识这个字段值是否有修改* * * - * optional bool updated = 5; - */ - boolean hasUpdated(); - /** - *
      -     **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -     * 
      - * - * optional bool updated = 5; + * bool updated = 5; */ boolean getUpdated(); /** - *
      -     ** 标识是否为空  *
      -     * 
      - * - * optional bool isNull = 6 [default = false]; - */ - boolean hasIsNull(); - /** - *
      -     ** 标识是否为空  *
      -     * 
      - * - * optional bool isNull = 6 [default = false]; + * bool isNull = 6; */ boolean getIsNull(); @@ -4423,15 +4222,7 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; - */ - boolean hasValue(); - /** - *
      -     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      -     * 
      - * - * optional string value = 8; + * string value = 8; */ java.lang.String getValue(); /** @@ -4439,7 +4230,7 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; + * string value = 8; */ com.google.protobuf.ByteString getValueBytes(); @@ -4449,15 +4240,7 @@ public final class CanalEntry { ** 对应数据对象原始长度 * * * - * optional int32 length = 9; - */ - boolean hasLength(); - /** - *
      -     ** 对应数据对象原始长度 *
      -     * 
      - * - * optional int32 length = 9; + * int32 length = 9; */ int getLength(); @@ -4466,15 +4249,7 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; - */ - boolean hasMysqlType(); - /** - *
      -     **字段mysql类型*
      -     * 
      - * - * optional string mysqlType = 10; + * string mysqlType = 10; */ java.lang.String getMysqlType(); /** @@ -4482,10 +4257,12 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; + * string mysqlType = 10; */ com.google.protobuf.ByteString getMysqlTypeBytes(); + + public com.alibaba.otter.canal.protocol.CanalEntry.Column.IsNullPresentCase getIsNullPresentCase(); } /** *
      @@ -4509,7 +4286,6 @@ public final class CanalEntry {
             name_ = "";
             isKey_ = false;
             updated_ = false;
      -      isNull_ = false;
             props_ = java.util.Collections.emptyList();
             value_ = "";
             length_ = 0;
      @@ -4541,34 +4317,34 @@ public final class CanalEntry {
                     done = true;
                     break;
                   case 8: {
      -              bitField0_ |= 0x00000001;
      +
                     index_ = input.readInt32();
                     break;
                   }
                   case 16: {
      -              bitField0_ |= 0x00000002;
      +
                     sqlType_ = input.readInt32();
                     break;
                   }
                   case 26: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000004;
      -              name_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              name_ = s;
                     break;
                   }
                   case 32: {
      -              bitField0_ |= 0x00000008;
      +
                     isKey_ = input.readBool();
                     break;
                   }
                   case 40: {
      -              bitField0_ |= 0x00000010;
      +
                     updated_ = input.readBool();
                     break;
                   }
                   case 48: {
      -              bitField0_ |= 0x00000020;
      -              isNull_ = input.readBool();
      +              isNullPresentCase_ = 6;
      +              isNullPresent_ = input.readBool();
                     break;
                   }
                   case 58: {
      @@ -4577,28 +4353,28 @@ public final class CanalEntry {
                       mutable_bitField0_ |= 0x00000040;
                     }
                     props_.add(
      -                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry));
      +                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.parser(), extensionRegistry));
                     break;
                   }
                   case 66: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000040;
      -              value_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              value_ = s;
                     break;
                   }
                   case 72: {
      -              bitField0_ |= 0x00000080;
      +
                     length_ = input.readInt32();
                     break;
                   }
                   case 82: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000100;
      -              mysqlType_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              mysqlType_ = s;
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -4633,6 +4409,42 @@ public final class CanalEntry {
           }
       
           private int bitField0_;
      +    private int isNullPresentCase_ = 0;
      +    private java.lang.Object isNullPresent_;
      +    public enum IsNullPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      ISNULL(6),
      +      ISNULLPRESENT_NOT_SET(0);
      +      private final int value;
      +      private IsNullPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static IsNullPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static IsNullPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 6: return ISNULL;
      +          case 0: return ISNULLPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public IsNullPresentCase
      +    getIsNullPresentCase() {
      +      return IsNullPresentCase.forNumber(
      +          isNullPresentCase_);
      +    }
      +
           public static final int INDEX_FIELD_NUMBER = 1;
           private int index_;
           /**
      @@ -4640,17 +4452,7 @@ public final class CanalEntry {
            **字段下标*
            * 
      * - * optional int32 index = 1; - */ - public boolean hasIndex() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -     **字段下标*
      -     * 
      - * - * optional int32 index = 1; + * int32 index = 1; */ public int getIndex() { return index_; @@ -4663,17 +4465,7 @@ public final class CanalEntry { **字段java中类型* * * - * optional int32 sqlType = 2; - */ - public boolean hasSqlType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -     **字段java中类型*
      -     * 
      - * - * optional int32 sqlType = 2; + * int32 sqlType = 2; */ public int getSqlType() { return sqlType_; @@ -4686,17 +4478,7 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; - */ - public boolean hasName() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -     **字段名称(忽略大小写),在mysql中是没有的*
      -     * 
      - * - * optional string name = 3; + * string name = 3; */ public java.lang.String getName() { java.lang.Object ref = name_; @@ -4706,9 +4488,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } + name_ = s; return s; } } @@ -4717,7 +4497,7 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; + * string name = 3; */ public com.google.protobuf.ByteString getNameBytes() { @@ -4740,17 +4520,7 @@ public final class CanalEntry { **是否是主键* * * - * optional bool isKey = 4; - */ - public boolean hasIsKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -     **是否是主键*
      -     * 
      - * - * optional bool isKey = 4; + * bool isKey = 4; */ public boolean getIsKey() { return isKey_; @@ -4763,43 +4533,21 @@ public final class CanalEntry { **如果EventType=UPDATE,用于标识这个字段值是否有修改* * * - * optional bool updated = 5; - */ - public boolean hasUpdated() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - *
      -     **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -     * 
      - * - * optional bool updated = 5; + * bool updated = 5; */ public boolean getUpdated() { return updated_; } public static final int ISNULL_FIELD_NUMBER = 6; - private boolean isNull_; /** - *
      -     ** 标识是否为空  *
      -     * 
      - * - * optional bool isNull = 6 [default = false]; - */ - public boolean hasIsNull() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - *
      -     ** 标识是否为空  *
      -     * 
      - * - * optional bool isNull = 6 [default = false]; + * bool isNull = 6; */ public boolean getIsNull() { - return isNull_; + if (isNullPresentCase_ == 6) { + return (java.lang.Boolean) isNullPresent_; + } + return false; } public static final int PROPS_FIELD_NUMBER = 7; @@ -4864,17 +4612,7 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - /** - *
      -     ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      -     * 
      - * - * optional string value = 8; + * string value = 8; */ public java.lang.String getValue() { java.lang.Object ref = value_; @@ -4884,9 +4622,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } + value_ = s; return s; } } @@ -4895,7 +4631,7 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; + * string value = 8; */ public com.google.protobuf.ByteString getValueBytes() { @@ -4918,17 +4654,7 @@ public final class CanalEntry { ** 对应数据对象原始长度 * * * - * optional int32 length = 9; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - *
      -     ** 对应数据对象原始长度 *
      -     * 
      - * - * optional int32 length = 9; + * int32 length = 9; */ public int getLength() { return length_; @@ -4941,17 +4667,7 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; - */ - public boolean hasMysqlType() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - /** - *
      -     **字段mysql类型*
      -     * 
      - * - * optional string mysqlType = 10; + * string mysqlType = 10; */ public java.lang.String getMysqlType() { java.lang.Object ref = mysqlType_; @@ -4961,9 +4677,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - mysqlType_ = s; - } + mysqlType_ = s; return s; } } @@ -4972,7 +4686,7 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; + * string mysqlType = 10; */ public com.google.protobuf.ByteString getMysqlTypeBytes() { @@ -5002,34 +4716,35 @@ public final class CanalEntry { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (index_ != 0) { output.writeInt32(1, index_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (sqlType_ != 0) { output.writeInt32(2, sqlType_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!getNameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 3, name_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (isKey_ != false) { output.writeBool(4, isKey_); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (updated_ != false) { output.writeBool(5, updated_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBool(6, isNull_); + if (isNullPresentCase_ == 6) { + output.writeBool( + 6, (boolean)((java.lang.Boolean) isNullPresent_)); } for (int i = 0; i < props_.size(); i++) { output.writeMessage(7, props_.get(i)); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (!getValueBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 8, value_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (length_ != 0) { output.writeInt32(9, length_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (!getMysqlTypeBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 10, mysqlType_); } unknownFields.writeTo(output); @@ -5041,41 +4756,42 @@ public final class CanalEntry { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (index_ != 0) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(1, index_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (sqlType_ != 0) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(2, sqlType_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!getNameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, name_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (isKey_ != false) { size += com.google.protobuf.CodedOutputStream .computeBoolSize(4, isKey_); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (updated_ != false) { size += com.google.protobuf.CodedOutputStream .computeBoolSize(5, updated_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (isNullPresentCase_ == 6) { size += com.google.protobuf.CodedOutputStream - .computeBoolSize(6, isNull_); + .computeBoolSize( + 6, (boolean)((java.lang.Boolean) isNullPresent_)); } for (int i = 0; i < props_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(7, props_.get(i)); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (!getValueBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(8, value_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (length_ != 0) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(9, length_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (!getMysqlTypeBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(10, mysqlType_); } size += unknownFields.getSerializedSize(); @@ -5094,52 +4810,34 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.Column other = (com.alibaba.otter.canal.protocol.CanalEntry.Column) obj; boolean result = true; - result = result && (hasIndex() == other.hasIndex()); - if (hasIndex()) { - result = result && (getIndex() - == other.getIndex()); - } - result = result && (hasSqlType() == other.hasSqlType()); - if (hasSqlType()) { - result = result && (getSqlType() - == other.getSqlType()); - } - result = result && (hasName() == other.hasName()); - if (hasName()) { - result = result && getName() - .equals(other.getName()); - } - result = result && (hasIsKey() == other.hasIsKey()); - if (hasIsKey()) { - result = result && (getIsKey() - == other.getIsKey()); - } - result = result && (hasUpdated() == other.hasUpdated()); - if (hasUpdated()) { - result = result && (getUpdated() - == other.getUpdated()); - } - result = result && (hasIsNull() == other.hasIsNull()); - if (hasIsNull()) { - result = result && (getIsNull() - == other.getIsNull()); - } + result = result && (getIndex() + == other.getIndex()); + result = result && (getSqlType() + == other.getSqlType()); + result = result && getName() + .equals(other.getName()); + result = result && (getIsKey() + == other.getIsKey()); + result = result && (getUpdated() + == other.getUpdated()); result = result && getPropsList() .equals(other.getPropsList()); - result = result && (hasValue() == other.hasValue()); - if (hasValue()) { - result = result && getValue() - .equals(other.getValue()); - } - result = result && (hasLength() == other.hasLength()); - if (hasLength()) { - result = result && (getLength() - == other.getLength()); - } - result = result && (hasMysqlType() == other.hasMysqlType()); - if (hasMysqlType()) { - result = result && getMysqlType() - .equals(other.getMysqlType()); + result = result && getValue() + .equals(other.getValue()); + result = result && (getLength() + == other.getLength()); + result = result && getMysqlType() + .equals(other.getMysqlType()); + result = result && getIsNullPresentCase().equals( + other.getIsNullPresentCase()); + if (!result) return false; + switch (isNullPresentCase_) { + case 6: + result = result && (getIsNull() + == other.getIsNull()); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -5152,48 +4850,36 @@ public final class CanalEntry { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasIndex()) { - hash = (37 * hash) + INDEX_FIELD_NUMBER; - hash = (53 * hash) + getIndex(); - } - if (hasSqlType()) { - hash = (37 * hash) + SQLTYPE_FIELD_NUMBER; - hash = (53 * hash) + getSqlType(); - } - if (hasName()) { - hash = (37 * hash) + NAME_FIELD_NUMBER; - hash = (53 * hash) + getName().hashCode(); - } - if (hasIsKey()) { - hash = (37 * hash) + ISKEY_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsKey()); - } - if (hasUpdated()) { - hash = (37 * hash) + UPDATED_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getUpdated()); - } - if (hasIsNull()) { - hash = (37 * hash) + ISNULL_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsNull()); - } + hash = (37 * hash) + INDEX_FIELD_NUMBER; + hash = (53 * hash) + getIndex(); + hash = (37 * hash) + SQLTYPE_FIELD_NUMBER; + hash = (53 * hash) + getSqlType(); + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + hash = (37 * hash) + ISKEY_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getIsKey()); + hash = (37 * hash) + UPDATED_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getUpdated()); if (getPropsCount() > 0) { hash = (37 * hash) + PROPS_FIELD_NUMBER; hash = (53 * hash) + getPropsList().hashCode(); } - if (hasValue()) { - hash = (37 * hash) + VALUE_FIELD_NUMBER; - hash = (53 * hash) + getValue().hashCode(); - } - if (hasLength()) { - hash = (37 * hash) + LENGTH_FIELD_NUMBER; - hash = (53 * hash) + getLength(); - } - if (hasMysqlType()) { - hash = (37 * hash) + MYSQLTYPE_FIELD_NUMBER; - hash = (53 * hash) + getMysqlType().hashCode(); + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); + hash = (37 * hash) + LENGTH_FIELD_NUMBER; + hash = (53 * hash) + getLength(); + hash = (37 * hash) + MYSQLTYPE_FIELD_NUMBER; + hash = (53 * hash) + getMysqlType().hashCode(); + switch (isNullPresentCase_) { + case 6: + hash = (37 * hash) + ISNULL_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getIsNull()); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -5334,17 +5020,15 @@ public final class CanalEntry { public Builder clear() { super.clear(); index_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); + sqlType_ = 0; - bitField0_ = (bitField0_ & ~0x00000002); + name_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); + isKey_ = false; - bitField0_ = (bitField0_ & ~0x00000008); + updated_ = false; - bitField0_ = (bitField0_ & ~0x00000010); - isNull_ = false; - bitField0_ = (bitField0_ & ~0x00000020); + if (propsBuilder_ == null) { props_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000040); @@ -5352,11 +5036,13 @@ public final class CanalEntry { propsBuilder_.clear(); } value_ = ""; - bitField0_ = (bitField0_ & ~0x00000080); + length_ = 0; - bitField0_ = (bitField0_ & ~0x00000100); + mysqlType_ = ""; - bitField0_ = (bitField0_ & ~0x00000200); + + isNullPresentCase_ = 0; + isNullPresent_ = null; return this; } @@ -5385,30 +5071,14 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.Column result = new com.alibaba.otter.canal.protocol.CanalEntry.Column(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.index_ = index_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.sqlType_ = sqlType_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } result.name_ = name_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } result.isKey_ = isKey_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; - } result.updated_ = updated_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; + if (isNullPresentCase_ == 6) { + result.isNullPresent_ = isNullPresent_; } - result.isNull_ = isNull_; if (propsBuilder_ == null) { if (((bitField0_ & 0x00000040) == 0x00000040)) { props_ = java.util.Collections.unmodifiableList(props_); @@ -5418,19 +5088,11 @@ public final class CanalEntry { } else { result.props_ = propsBuilder_.build(); } - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000040; - } result.value_ = value_; - if (((from_bitField0_ & 0x00000100) == 0x00000100)) { - to_bitField0_ |= 0x00000080; - } result.length_ = length_; - if (((from_bitField0_ & 0x00000200) == 0x00000200)) { - to_bitField0_ |= 0x00000100; - } result.mysqlType_ = mysqlType_; result.bitField0_ = to_bitField0_; + result.isNullPresentCase_ = isNullPresentCase_; onBuilt(); return result; } @@ -5479,26 +5141,22 @@ public final class CanalEntry { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Column other) { if (other == com.alibaba.otter.canal.protocol.CanalEntry.Column.getDefaultInstance()) return this; - if (other.hasIndex()) { + if (other.getIndex() != 0) { setIndex(other.getIndex()); } - if (other.hasSqlType()) { + if (other.getSqlType() != 0) { setSqlType(other.getSqlType()); } - if (other.hasName()) { - bitField0_ |= 0x00000004; + if (!other.getName().isEmpty()) { name_ = other.name_; onChanged(); } - if (other.hasIsKey()) { + if (other.getIsKey() != false) { setIsKey(other.getIsKey()); } - if (other.hasUpdated()) { + if (other.getUpdated() != false) { setUpdated(other.getUpdated()); } - if (other.hasIsNull()) { - setIsNull(other.getIsNull()); - } if (propsBuilder_ == null) { if (!other.props_.isEmpty()) { if (props_.isEmpty()) { @@ -5525,19 +5183,26 @@ public final class CanalEntry { } } } - if (other.hasValue()) { - bitField0_ |= 0x00000080; + if (!other.getValue().isEmpty()) { value_ = other.value_; onChanged(); } - if (other.hasLength()) { + if (other.getLength() != 0) { setLength(other.getLength()); } - if (other.hasMysqlType()) { - bitField0_ |= 0x00000200; + if (!other.getMysqlType().isEmpty()) { mysqlType_ = other.mysqlType_; onChanged(); } + switch (other.getIsNullPresentCase()) { + case ISNULL: { + setIsNull(other.getIsNull()); + break; + } + case ISNULLPRESENT_NOT_SET: { + break; + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -5566,6 +5231,21 @@ public final class CanalEntry { } return this; } + private int isNullPresentCase_ = 0; + private java.lang.Object isNullPresent_; + public IsNullPresentCase + getIsNullPresentCase() { + return IsNullPresentCase.forNumber( + isNullPresentCase_); + } + + public Builder clearIsNullPresent() { + isNullPresentCase_ = 0; + isNullPresent_ = null; + onChanged(); + return this; + } + private int bitField0_; private int index_ ; @@ -5574,17 +5254,7 @@ public final class CanalEntry { **字段下标* * * - * optional int32 index = 1; - */ - public boolean hasIndex() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -       **字段下标*
      -       * 
      - * - * optional int32 index = 1; + * int32 index = 1; */ public int getIndex() { return index_; @@ -5594,10 +5264,10 @@ public final class CanalEntry { **字段下标* * * - * optional int32 index = 1; + * int32 index = 1; */ public Builder setIndex(int value) { - bitField0_ |= 0x00000001; + index_ = value; onChanged(); return this; @@ -5607,10 +5277,10 @@ public final class CanalEntry { **字段下标* * * - * optional int32 index = 1; + * int32 index = 1; */ public Builder clearIndex() { - bitField0_ = (bitField0_ & ~0x00000001); + index_ = 0; onChanged(); return this; @@ -5622,17 +5292,7 @@ public final class CanalEntry { **字段java中类型* * * - * optional int32 sqlType = 2; - */ - public boolean hasSqlType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -       **字段java中类型*
      -       * 
      - * - * optional int32 sqlType = 2; + * int32 sqlType = 2; */ public int getSqlType() { return sqlType_; @@ -5642,10 +5302,10 @@ public final class CanalEntry { **字段java中类型* * * - * optional int32 sqlType = 2; + * int32 sqlType = 2; */ public Builder setSqlType(int value) { - bitField0_ |= 0x00000002; + sqlType_ = value; onChanged(); return this; @@ -5655,10 +5315,10 @@ public final class CanalEntry { **字段java中类型* * * - * optional int32 sqlType = 2; + * int32 sqlType = 2; */ public Builder clearSqlType() { - bitField0_ = (bitField0_ & ~0x00000002); + sqlType_ = 0; onChanged(); return this; @@ -5670,17 +5330,7 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; - */ - public boolean hasName() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -       **字段名称(忽略大小写),在mysql中是没有的*
      -       * 
      - * - * optional string name = 3; + * string name = 3; */ public java.lang.String getName() { java.lang.Object ref = name_; @@ -5688,9 +5338,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } + name_ = s; return s; } else { return (java.lang.String) ref; @@ -5701,7 +5349,7 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; + * string name = 3; */ public com.google.protobuf.ByteString getNameBytes() { @@ -5721,14 +5369,14 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; + * string name = 3; */ public Builder setName( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + name_ = value; onChanged(); return this; @@ -5738,10 +5386,10 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; + * string name = 3; */ public Builder clearName() { - bitField0_ = (bitField0_ & ~0x00000004); + name_ = getDefaultInstance().getName(); onChanged(); return this; @@ -5751,14 +5399,15 @@ public final class CanalEntry { **字段名称(忽略大小写),在mysql中是没有的* * * - * optional string name = 3; + * string name = 3; */ public Builder setNameBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + checkByteStringIsUtf8(value); + name_ = value; onChanged(); return this; @@ -5770,17 +5419,7 @@ public final class CanalEntry { **是否是主键* * * - * optional bool isKey = 4; - */ - public boolean hasIsKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -       **是否是主键*
      -       * 
      - * - * optional bool isKey = 4; + * bool isKey = 4; */ public boolean getIsKey() { return isKey_; @@ -5790,10 +5429,10 @@ public final class CanalEntry { **是否是主键* * * - * optional bool isKey = 4; + * bool isKey = 4; */ public Builder setIsKey(boolean value) { - bitField0_ |= 0x00000008; + isKey_ = value; onChanged(); return this; @@ -5803,10 +5442,10 @@ public final class CanalEntry { **是否是主键* * * - * optional bool isKey = 4; + * bool isKey = 4; */ public Builder clearIsKey() { - bitField0_ = (bitField0_ & ~0x00000008); + isKey_ = false; onChanged(); return this; @@ -5818,17 +5457,7 @@ public final class CanalEntry { **如果EventType=UPDATE,用于标识这个字段值是否有修改* * * - * optional bool updated = 5; - */ - public boolean hasUpdated() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - *
      -       **如果EventType=UPDATE,用于标识这个字段值是否有修改*
      -       * 
      - * - * optional bool updated = 5; + * bool updated = 5; */ public boolean getUpdated() { return updated_; @@ -5838,10 +5467,10 @@ public final class CanalEntry { **如果EventType=UPDATE,用于标识这个字段值是否有修改* * * - * optional bool updated = 5; + * bool updated = 5; */ public Builder setUpdated(boolean value) { - bitField0_ |= 0x00000010; + updated_ = value; onChanged(); return this; @@ -5851,60 +5480,42 @@ public final class CanalEntry { **如果EventType=UPDATE,用于标识这个字段值是否有修改* * * - * optional bool updated = 5; + * bool updated = 5; */ public Builder clearUpdated() { - bitField0_ = (bitField0_ & ~0x00000010); + updated_ = false; onChanged(); return this; } - private boolean isNull_ ; /** - *
      -       ** 标识是否为空  *
      -       * 
      - * - * optional bool isNull = 6 [default = false]; - */ - public boolean hasIsNull() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - *
      -       ** 标识是否为空  *
      -       * 
      - * - * optional bool isNull = 6 [default = false]; + * bool isNull = 6; */ public boolean getIsNull() { - return isNull_; + if (isNullPresentCase_ == 6) { + return (java.lang.Boolean) isNullPresent_; + } + return false; } /** - *
      -       ** 标识是否为空  *
      -       * 
      - * - * optional bool isNull = 6 [default = false]; + * bool isNull = 6; */ public Builder setIsNull(boolean value) { - bitField0_ |= 0x00000020; - isNull_ = value; + isNullPresentCase_ = 6; + isNullPresent_ = value; onChanged(); return this; } /** - *
      -       ** 标识是否为空  *
      -       * 
      - * - * optional bool isNull = 6 [default = false]; + * bool isNull = 6; */ public Builder clearIsNull() { - bitField0_ = (bitField0_ & ~0x00000020); - isNull_ = false; - onChanged(); + if (isNullPresentCase_ == 6) { + isNullPresentCase_ = 0; + isNullPresent_ = null; + onChanged(); + } return this; } @@ -6226,17 +5837,7 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - *
      -       ** 字段值,timestamp,Datetime是一个时间格式的文本 *
      -       * 
      - * - * optional string value = 8; + * string value = 8; */ public java.lang.String getValue() { java.lang.Object ref = value_; @@ -6244,9 +5845,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } + value_ = s; return s; } else { return (java.lang.String) ref; @@ -6257,7 +5856,7 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; + * string value = 8; */ public com.google.protobuf.ByteString getValueBytes() { @@ -6277,14 +5876,14 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; + * string value = 8; */ public Builder setValue( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000080; + value_ = value; onChanged(); return this; @@ -6294,10 +5893,10 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; + * string value = 8; */ public Builder clearValue() { - bitField0_ = (bitField0_ & ~0x00000080); + value_ = getDefaultInstance().getValue(); onChanged(); return this; @@ -6307,14 +5906,15 @@ public final class CanalEntry { ** 字段值,timestamp,Datetime是一个时间格式的文本 * * * - * optional string value = 8; + * string value = 8; */ public Builder setValueBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000080; + checkByteStringIsUtf8(value); + value_ = value; onChanged(); return this; @@ -6326,17 +5926,7 @@ public final class CanalEntry { ** 对应数据对象原始长度 * * * - * optional int32 length = 9; - */ - public boolean hasLength() { - return ((bitField0_ & 0x00000100) == 0x00000100); - } - /** - *
      -       ** 对应数据对象原始长度 *
      -       * 
      - * - * optional int32 length = 9; + * int32 length = 9; */ public int getLength() { return length_; @@ -6346,10 +5936,10 @@ public final class CanalEntry { ** 对应数据对象原始长度 * * * - * optional int32 length = 9; + * int32 length = 9; */ public Builder setLength(int value) { - bitField0_ |= 0x00000100; + length_ = value; onChanged(); return this; @@ -6359,10 +5949,10 @@ public final class CanalEntry { ** 对应数据对象原始长度 * * * - * optional int32 length = 9; + * int32 length = 9; */ public Builder clearLength() { - bitField0_ = (bitField0_ & ~0x00000100); + length_ = 0; onChanged(); return this; @@ -6374,17 +5964,7 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; - */ - public boolean hasMysqlType() { - return ((bitField0_ & 0x00000200) == 0x00000200); - } - /** - *
      -       **字段mysql类型*
      -       * 
      - * - * optional string mysqlType = 10; + * string mysqlType = 10; */ public java.lang.String getMysqlType() { java.lang.Object ref = mysqlType_; @@ -6392,9 +5972,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - mysqlType_ = s; - } + mysqlType_ = s; return s; } else { return (java.lang.String) ref; @@ -6405,7 +5983,7 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; + * string mysqlType = 10; */ public com.google.protobuf.ByteString getMysqlTypeBytes() { @@ -6425,14 +6003,14 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; + * string mysqlType = 10; */ public Builder setMysqlType( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000200; + mysqlType_ = value; onChanged(); return this; @@ -6442,10 +6020,10 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; + * string mysqlType = 10; */ public Builder clearMysqlType() { - bitField0_ = (bitField0_ & ~0x00000200); + mysqlType_ = getDefaultInstance().getMysqlType(); onChanged(); return this; @@ -6455,14 +6033,15 @@ public final class CanalEntry { **字段mysql类型* * * - * optional string mysqlType = 10; + * string mysqlType = 10; */ public Builder setMysqlTypeBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000200; + checkByteStringIsUtf8(value); + mysqlType_ = value; onChanged(); return this; @@ -6470,7 +6049,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -6493,7 +6072,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Column parsePartialFrom( @@ -6704,7 +6283,7 @@ public final class CanalEntry { mutable_bitField0_ |= 0x00000001; } beforeColumns_.add( - input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.PARSER, extensionRegistry)); + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.parser(), extensionRegistry)); break; } case 18: { @@ -6713,7 +6292,7 @@ public final class CanalEntry { mutable_bitField0_ |= 0x00000002; } afterColumns_.add( - input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.PARSER, extensionRegistry)); + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Column.parser(), extensionRegistry)); break; } case 26: { @@ -6722,11 +6301,11 @@ public final class CanalEntry { mutable_bitField0_ |= 0x00000004; } props_.add( - input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.parser(), extensionRegistry)); break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -8322,7 +7901,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -8345,7 +7924,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public RowData parsePartialFrom( @@ -8381,49 +7960,21 @@ public final class CanalEntry { **tableId,由数据库产生* * * - * optional int64 tableId = 1; - */ - boolean hasTableId(); - /** - *
      -     **tableId,由数据库产生*
      -     * 
      - * - * optional int64 tableId = 1; + * int64 tableId = 1; */ long getTableId(); /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ - boolean hasEventType(); + int getEventTypeValue(); /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType(); /** - *
      -     ** 标识是否是ddl语句  *
      -     * 
      - * - * optional bool isDdl = 10 [default = false]; - */ - boolean hasIsDdl(); - /** - *
      -     ** 标识是否是ddl语句  *
      -     * 
      - * - * optional bool isDdl = 10 [default = false]; + * bool isDdl = 10; */ boolean getIsDdl(); @@ -8432,15 +7983,7 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; - */ - boolean hasSql(); - /** - *
      -     ** ddl/query的sql语句  *
      -     * 
      - * - * optional string sql = 11; + * string sql = 11; */ java.lang.String getSql(); /** @@ -8448,7 +7991,7 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; + * string sql = 11; */ com.google.protobuf.ByteString getSqlBytes(); @@ -8546,15 +8089,7 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; - */ - boolean hasDdlSchemaName(); - /** - *
      -     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -     * 
      - * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ java.lang.String getDdlSchemaName(); /** @@ -8562,10 +8097,14 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ com.google.protobuf.ByteString getDdlSchemaNameBytes(); + + public com.alibaba.otter.canal.protocol.CanalEntry.RowChange.EventTypePresentCase getEventTypePresentCase(); + + public com.alibaba.otter.canal.protocol.CanalEntry.RowChange.IsDdlPresentCase getIsDdlPresentCase(); } /** *
      @@ -8585,8 +8124,6 @@ public final class CanalEntry {
           }
           private RowChange() {
             tableId_ = 0L;
      -      eventType_ = 2;
      -      isDdl_ = false;
             sql_ = "";
             rowDatas_ = java.util.Collections.emptyList();
             props_ = java.util.Collections.emptyList();
      @@ -8618,31 +8155,25 @@ public final class CanalEntry {
                     done = true;
                     break;
                   case 8: {
      -              bitField0_ |= 0x00000001;
      +
                     tableId_ = input.readInt64();
                     break;
                   }
                   case 16: {
                     int rawValue = input.readEnum();
      -                @SuppressWarnings("deprecation")
      -              com.alibaba.otter.canal.protocol.CanalEntry.EventType value = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(rawValue);
      -              if (value == null) {
      -                unknownFields.mergeVarintField(2, rawValue);
      -              } else {
      -                bitField0_ |= 0x00000002;
      -                eventType_ = rawValue;
      -              }
      +              eventTypePresentCase_ = 2;
      +              eventTypePresent_ = rawValue;
                     break;
                   }
                   case 80: {
      -              bitField0_ |= 0x00000004;
      -              isDdl_ = input.readBool();
      +              isDdlPresentCase_ = 10;
      +              isDdlPresent_ = input.readBool();
                     break;
                   }
                   case 90: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000008;
      -              sql_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              sql_ = s;
                     break;
                   }
                   case 98: {
      @@ -8651,7 +8182,7 @@ public final class CanalEntry {
                       mutable_bitField0_ |= 0x00000010;
                     }
                     rowDatas_.add(
      -                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.RowData.PARSER, extensionRegistry));
      +                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.RowData.parser(), extensionRegistry));
                     break;
                   }
                   case 106: {
      @@ -8660,17 +8191,17 @@ public final class CanalEntry {
                       mutable_bitField0_ |= 0x00000020;
                     }
                     props_.add(
      -                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry));
      +                  input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.parser(), extensionRegistry));
                     break;
                   }
                   case 114: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000010;
      -              ddlSchemaName_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              ddlSchemaName_ = s;
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -8708,6 +8239,78 @@ public final class CanalEntry {
           }
       
           private int bitField0_;
      +    private int eventTypePresentCase_ = 0;
      +    private java.lang.Object eventTypePresent_;
      +    public enum EventTypePresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      EVENTTYPE(2),
      +      EVENTTYPEPRESENT_NOT_SET(0);
      +      private final int value;
      +      private EventTypePresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static EventTypePresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static EventTypePresentCase forNumber(int value) {
      +        switch (value) {
      +          case 2: return EVENTTYPE;
      +          case 0: return EVENTTYPEPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public EventTypePresentCase
      +    getEventTypePresentCase() {
      +      return EventTypePresentCase.forNumber(
      +          eventTypePresentCase_);
      +    }
      +
      +    private int isDdlPresentCase_ = 0;
      +    private java.lang.Object isDdlPresent_;
      +    public enum IsDdlPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      ISDDL(10),
      +      ISDDLPRESENT_NOT_SET(0);
      +      private final int value;
      +      private IsDdlPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static IsDdlPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static IsDdlPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 10: return ISDDL;
      +          case 0: return ISDDLPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public IsDdlPresentCase
      +    getIsDdlPresentCase() {
      +      return IsDdlPresentCase.forNumber(
      +          isDdlPresentCase_);
      +    }
      +
           public static final int TABLEID_FIELD_NUMBER = 1;
           private long tableId_;
           /**
      @@ -8715,68 +8318,44 @@ public final class CanalEntry {
            **tableId,由数据库产生*
            * 
      * - * optional int64 tableId = 1; - */ - public boolean hasTableId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -     **tableId,由数据库产生*
      -     * 
      - * - * optional int64 tableId = 1; + * int64 tableId = 1; */ public long getTableId() { return tableId_; } public static final int EVENTTYPE_FIELD_NUMBER = 2; - private int eventType_; /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000002) == 0x00000002); + public int getEventTypeValue() { + if (eventTypePresentCase_ == 2) { + return (java.lang.Integer) eventTypePresent_; + } + return 0; } /** - *
      -     **数据变更类型*
      -     * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; + if (eventTypePresentCase_ == 2) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf( + (java.lang.Integer) eventTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UNRECOGNIZED : result; + } + return EventType.UPDATE; } public static final int ISDDL_FIELD_NUMBER = 10; - private boolean isDdl_; /** - *
      -     ** 标识是否是ddl语句  *
      -     * 
      - * - * optional bool isDdl = 10 [default = false]; - */ - public boolean hasIsDdl() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -     ** 标识是否是ddl语句  *
      -     * 
      - * - * optional bool isDdl = 10 [default = false]; + * bool isDdl = 10; */ public boolean getIsDdl() { - return isDdl_; + if (isDdlPresentCase_ == 10) { + return (java.lang.Boolean) isDdlPresent_; + } + return false; } public static final int SQL_FIELD_NUMBER = 11; @@ -8786,17 +8365,7 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; - */ - public boolean hasSql() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -     ** ddl/query的sql语句  *
      -     * 
      - * - * optional string sql = 11; + * string sql = 11; */ public java.lang.String getSql() { java.lang.Object ref = sql_; @@ -8806,9 +8375,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - sql_ = s; - } + sql_ = s; return s; } } @@ -8817,7 +8384,7 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; + * string sql = 11; */ public com.google.protobuf.ByteString getSqlBytes() { @@ -8950,17 +8517,7 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; - */ - public boolean hasDdlSchemaName() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - *
      -     ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -     * 
      - * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ public java.lang.String getDdlSchemaName() { java.lang.Object ref = ddlSchemaName_; @@ -8970,9 +8527,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - ddlSchemaName_ = s; - } + ddlSchemaName_ = s; return s; } } @@ -8981,7 +8536,7 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ public com.google.protobuf.ByteString getDdlSchemaNameBytes() { @@ -9011,16 +8566,17 @@ public final class CanalEntry { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (tableId_ != 0L) { output.writeInt64(1, tableId_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, eventType_); + if (eventTypePresentCase_ == 2) { + output.writeEnum(2, ((java.lang.Integer) eventTypePresent_)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBool(10, isDdl_); + if (isDdlPresentCase_ == 10) { + output.writeBool( + 10, (boolean)((java.lang.Boolean) isDdlPresent_)); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (!getSqlBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 11, sql_); } for (int i = 0; i < rowDatas_.size(); i++) { @@ -9029,7 +8585,7 @@ public final class CanalEntry { for (int i = 0; i < props_.size(); i++) { output.writeMessage(13, props_.get(i)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!getDdlSchemaNameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 14, ddlSchemaName_); } unknownFields.writeTo(output); @@ -9041,19 +8597,20 @@ public final class CanalEntry { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (tableId_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(1, tableId_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (eventTypePresentCase_ == 2) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(2, eventType_); + .computeEnumSize(2, ((java.lang.Integer) eventTypePresent_)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (isDdlPresentCase_ == 10) { size += com.google.protobuf.CodedOutputStream - .computeBoolSize(10, isDdl_); + .computeBoolSize( + 10, (boolean)((java.lang.Boolean) isDdlPresent_)); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (!getSqlBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(11, sql_); } for (int i = 0; i < rowDatas_.size(); i++) { @@ -9064,7 +8621,7 @@ public final class CanalEntry { size += com.google.protobuf.CodedOutputStream .computeMessageSize(13, props_.get(i)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!getDdlSchemaNameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(14, ddlSchemaName_); } size += unknownFields.getSerializedSize(); @@ -9083,33 +8640,37 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.RowChange other = (com.alibaba.otter.canal.protocol.CanalEntry.RowChange) obj; boolean result = true; - result = result && (hasTableId() == other.hasTableId()); - if (hasTableId()) { - result = result && (getTableId() - == other.getTableId()); - } - result = result && (hasEventType() == other.hasEventType()); - if (hasEventType()) { - result = result && eventType_ == other.eventType_; - } - result = result && (hasIsDdl() == other.hasIsDdl()); - if (hasIsDdl()) { - result = result && (getIsDdl() - == other.getIsDdl()); - } - result = result && (hasSql() == other.hasSql()); - if (hasSql()) { - result = result && getSql() - .equals(other.getSql()); - } + result = result && (getTableId() + == other.getTableId()); + result = result && getSql() + .equals(other.getSql()); result = result && getRowDatasList() .equals(other.getRowDatasList()); result = result && getPropsList() .equals(other.getPropsList()); - result = result && (hasDdlSchemaName() == other.hasDdlSchemaName()); - if (hasDdlSchemaName()) { - result = result && getDdlSchemaName() - .equals(other.getDdlSchemaName()); + result = result && getDdlSchemaName() + .equals(other.getDdlSchemaName()); + result = result && getEventTypePresentCase().equals( + other.getEventTypePresentCase()); + if (!result) return false; + switch (eventTypePresentCase_) { + case 2: + result = result && getEventTypeValue() + == other.getEventTypeValue(); + break; + case 0: + default: + } + result = result && getIsDdlPresentCase().equals( + other.getIsDdlPresentCase()); + if (!result) return false; + switch (isDdlPresentCase_) { + case 10: + result = result && (getIsDdl() + == other.getIsDdl()); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -9122,24 +8683,11 @@ public final class CanalEntry { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasTableId()) { - hash = (37 * hash) + TABLEID_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getTableId()); - } - if (hasEventType()) { - hash = (37 * hash) + EVENTTYPE_FIELD_NUMBER; - hash = (53 * hash) + eventType_; - } - if (hasIsDdl()) { - hash = (37 * hash) + ISDDL_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getIsDdl()); - } - if (hasSql()) { - hash = (37 * hash) + SQL_FIELD_NUMBER; - hash = (53 * hash) + getSql().hashCode(); - } + hash = (37 * hash) + TABLEID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getTableId()); + hash = (37 * hash) + SQL_FIELD_NUMBER; + hash = (53 * hash) + getSql().hashCode(); if (getRowDatasCount() > 0) { hash = (37 * hash) + ROWDATAS_FIELD_NUMBER; hash = (53 * hash) + getRowDatasList().hashCode(); @@ -9148,9 +8696,24 @@ public final class CanalEntry { hash = (37 * hash) + PROPS_FIELD_NUMBER; hash = (53 * hash) + getPropsList().hashCode(); } - if (hasDdlSchemaName()) { - hash = (37 * hash) + DDLSCHEMANAME_FIELD_NUMBER; - hash = (53 * hash) + getDdlSchemaName().hashCode(); + hash = (37 * hash) + DDLSCHEMANAME_FIELD_NUMBER; + hash = (53 * hash) + getDdlSchemaName().hashCode(); + switch (eventTypePresentCase_) { + case 2: + hash = (37 * hash) + EVENTTYPE_FIELD_NUMBER; + hash = (53 * hash) + getEventTypeValue(); + break; + case 0: + default: + } + switch (isDdlPresentCase_) { + case 10: + hash = (37 * hash) + ISDDL_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getIsDdl()); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -9292,13 +8855,9 @@ public final class CanalEntry { public Builder clear() { super.clear(); tableId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); - eventType_ = 2; - bitField0_ = (bitField0_ & ~0x00000002); - isDdl_ = false; - bitField0_ = (bitField0_ & ~0x00000004); + sql_ = ""; - bitField0_ = (bitField0_ & ~0x00000008); + if (rowDatasBuilder_ == null) { rowDatas_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000010); @@ -9312,7 +8871,11 @@ public final class CanalEntry { propsBuilder_.clear(); } ddlSchemaName_ = ""; - bitField0_ = (bitField0_ & ~0x00000040); + + eventTypePresentCase_ = 0; + eventTypePresent_ = null; + isDdlPresentCase_ = 0; + isDdlPresent_ = null; return this; } @@ -9341,20 +8904,12 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.RowChange result = new com.alibaba.otter.canal.protocol.CanalEntry.RowChange(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.tableId_ = tableId_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; + if (eventTypePresentCase_ == 2) { + result.eventTypePresent_ = eventTypePresent_; } - result.eventType_ = eventType_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.isDdl_ = isDdl_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; + if (isDdlPresentCase_ == 10) { + result.isDdlPresent_ = isDdlPresent_; } result.sql_ = sql_; if (rowDatasBuilder_ == null) { @@ -9375,11 +8930,10 @@ public final class CanalEntry { } else { result.props_ = propsBuilder_.build(); } - if (((from_bitField0_ & 0x00000040) == 0x00000040)) { - to_bitField0_ |= 0x00000010; - } result.ddlSchemaName_ = ddlSchemaName_; result.bitField0_ = to_bitField0_; + result.eventTypePresentCase_ = eventTypePresentCase_; + result.isDdlPresentCase_ = isDdlPresentCase_; onBuilt(); return result; } @@ -9428,17 +8982,10 @@ public final class CanalEntry { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.RowChange other) { if (other == com.alibaba.otter.canal.protocol.CanalEntry.RowChange.getDefaultInstance()) return this; - if (other.hasTableId()) { + if (other.getTableId() != 0L) { setTableId(other.getTableId()); } - if (other.hasEventType()) { - setEventType(other.getEventType()); - } - if (other.hasIsDdl()) { - setIsDdl(other.getIsDdl()); - } - if (other.hasSql()) { - bitField0_ |= 0x00000008; + if (!other.getSql().isEmpty()) { sql_ = other.sql_; onChanged(); } @@ -9494,11 +9041,28 @@ public final class CanalEntry { } } } - if (other.hasDdlSchemaName()) { - bitField0_ |= 0x00000040; + if (!other.getDdlSchemaName().isEmpty()) { ddlSchemaName_ = other.ddlSchemaName_; onChanged(); } + switch (other.getEventTypePresentCase()) { + case EVENTTYPE: { + setEventTypeValue(other.getEventTypeValue()); + break; + } + case EVENTTYPEPRESENT_NOT_SET: { + break; + } + } + switch (other.getIsDdlPresentCase()) { + case ISDDL: { + setIsDdl(other.getIsDdl()); + break; + } + case ISDDLPRESENT_NOT_SET: { + break; + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -9527,6 +9091,36 @@ public final class CanalEntry { } return this; } + private int eventTypePresentCase_ = 0; + private java.lang.Object eventTypePresent_; + public EventTypePresentCase + getEventTypePresentCase() { + return EventTypePresentCase.forNumber( + eventTypePresentCase_); + } + + public Builder clearEventTypePresent() { + eventTypePresentCase_ = 0; + eventTypePresent_ = null; + onChanged(); + return this; + } + + private int isDdlPresentCase_ = 0; + private java.lang.Object isDdlPresent_; + public IsDdlPresentCase + getIsDdlPresentCase() { + return IsDdlPresentCase.forNumber( + isDdlPresentCase_); + } + + public Builder clearIsDdlPresent() { + isDdlPresentCase_ = 0; + isDdlPresent_ = null; + onChanged(); + return this; + } + private int bitField0_; private long tableId_ ; @@ -9535,17 +9129,7 @@ public final class CanalEntry { **tableId,由数据库产生* * * - * optional int64 tableId = 1; - */ - public boolean hasTableId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -       **tableId,由数据库产生*
      -       * 
      - * - * optional int64 tableId = 1; + * int64 tableId = 1; */ public long getTableId() { return tableId_; @@ -9555,10 +9139,10 @@ public final class CanalEntry { **tableId,由数据库产生* * * - * optional int64 tableId = 1; + * int64 tableId = 1; */ public Builder setTableId(long value) { - bitField0_ |= 0x00000001; + tableId_ = value; onChanged(); return this; @@ -9568,113 +9152,96 @@ public final class CanalEntry { **tableId,由数据库产生* * * - * optional int64 tableId = 1; + * int64 tableId = 1; */ public Builder clearTableId() { - bitField0_ = (bitField0_ & ~0x00000001); + tableId_ = 0L; onChanged(); return this; } - private int eventType_ = 2; /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ - public boolean hasEventType() { - return ((bitField0_ & 0x00000002) == 0x00000002); + public int getEventTypeValue() { + if (eventTypePresentCase_ == 2) { + return ((java.lang.Integer) eventTypePresent_).intValue(); + } + return 0; } /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; + */ + public Builder setEventTypeValue(int value) { + eventTypePresentCase_ = 2; + eventTypePresent_ = value; + onChanged(); + return this; + } + /** + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ public com.alibaba.otter.canal.protocol.CanalEntry.EventType getEventType() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf(eventType_); - return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UPDATE : result; + if (eventTypePresentCase_ == 2) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalEntry.EventType result = com.alibaba.otter.canal.protocol.CanalEntry.EventType.valueOf( + (java.lang.Integer) eventTypePresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalEntry.EventType.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalEntry.EventType.EVENTTYPECOMPATIBLEPROTO2; } /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ public Builder setEventType(com.alibaba.otter.canal.protocol.CanalEntry.EventType value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; - eventType_ = value.getNumber(); + eventTypePresentCase_ = 2; + eventTypePresent_ = value.getNumber(); onChanged(); return this; } /** - *
      -       **数据变更类型*
      -       * 
      - * - * optional .com.alibaba.otter.canal.protocol.EventType eventType = 2 [default = UPDATE]; + * .com.alibaba.otter.canal.protocol.EventType eventType = 2; */ public Builder clearEventType() { - bitField0_ = (bitField0_ & ~0x00000002); - eventType_ = 2; - onChanged(); + if (eventTypePresentCase_ == 2) { + eventTypePresentCase_ = 0; + eventTypePresent_ = null; + onChanged(); + } return this; } - private boolean isDdl_ ; /** - *
      -       ** 标识是否是ddl语句  *
      -       * 
      - * - * optional bool isDdl = 10 [default = false]; - */ - public boolean hasIsDdl() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -       ** 标识是否是ddl语句  *
      -       * 
      - * - * optional bool isDdl = 10 [default = false]; + * bool isDdl = 10; */ public boolean getIsDdl() { - return isDdl_; + if (isDdlPresentCase_ == 10) { + return (java.lang.Boolean) isDdlPresent_; + } + return false; } /** - *
      -       ** 标识是否是ddl语句  *
      -       * 
      - * - * optional bool isDdl = 10 [default = false]; + * bool isDdl = 10; */ public Builder setIsDdl(boolean value) { - bitField0_ |= 0x00000004; - isDdl_ = value; + isDdlPresentCase_ = 10; + isDdlPresent_ = value; onChanged(); return this; } /** - *
      -       ** 标识是否是ddl语句  *
      -       * 
      - * - * optional bool isDdl = 10 [default = false]; + * bool isDdl = 10; */ public Builder clearIsDdl() { - bitField0_ = (bitField0_ & ~0x00000004); - isDdl_ = false; - onChanged(); + if (isDdlPresentCase_ == 10) { + isDdlPresentCase_ = 0; + isDdlPresent_ = null; + onChanged(); + } return this; } @@ -9684,17 +9251,7 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; - */ - public boolean hasSql() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -       ** ddl/query的sql语句  *
      -       * 
      - * - * optional string sql = 11; + * string sql = 11; */ public java.lang.String getSql() { java.lang.Object ref = sql_; @@ -9702,9 +9259,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - sql_ = s; - } + sql_ = s; return s; } else { return (java.lang.String) ref; @@ -9715,7 +9270,7 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; + * string sql = 11; */ public com.google.protobuf.ByteString getSqlBytes() { @@ -9735,14 +9290,14 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; + * string sql = 11; */ public Builder setSql( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + sql_ = value; onChanged(); return this; @@ -9752,10 +9307,10 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; + * string sql = 11; */ public Builder clearSql() { - bitField0_ = (bitField0_ & ~0x00000008); + sql_ = getDefaultInstance().getSql(); onChanged(); return this; @@ -9765,14 +9320,15 @@ public final class CanalEntry { ** ddl/query的sql语句 * * * - * optional string sql = 11; + * string sql = 11; */ public Builder setSqlBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + checkByteStringIsUtf8(value); + sql_ = value; onChanged(); return this; @@ -10408,17 +9964,7 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; - */ - public boolean hasDdlSchemaName() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - /** - *
      -       ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  *
      -       * 
      - * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ public java.lang.String getDdlSchemaName() { java.lang.Object ref = ddlSchemaName_; @@ -10426,9 +9972,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - ddlSchemaName_ = s; - } + ddlSchemaName_ = s; return s; } else { return (java.lang.String) ref; @@ -10439,7 +9983,7 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ public com.google.protobuf.ByteString getDdlSchemaNameBytes() { @@ -10459,14 +10003,14 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ public Builder setDdlSchemaName( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000040; + ddlSchemaName_ = value; onChanged(); return this; @@ -10476,10 +10020,10 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ public Builder clearDdlSchemaName() { - bitField0_ = (bitField0_ & ~0x00000040); + ddlSchemaName_ = getDefaultInstance().getDdlSchemaName(); onChanged(); return this; @@ -10489,14 +10033,15 @@ public final class CanalEntry { ** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName * * * - * optional string ddlSchemaName = 14; + * string ddlSchemaName = 14; */ public Builder setDdlSchemaNameBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000040; + checkByteStringIsUtf8(value); + ddlSchemaName_ = value; onChanged(); return this; @@ -10504,7 +10049,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -10527,7 +10072,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public RowChange parsePartialFrom( @@ -10563,15 +10108,7 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; - */ - boolean hasExecuteTime(); - /** - *
      -     **已废弃,请使用header里的executeTime*
      -     * 
      - * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ long getExecuteTime(); @@ -10580,15 +10117,7 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; - */ - boolean hasTransactionId(); - /** - *
      -     **已废弃,Begin里不提供事务id*
      -     * 
      - * - * optional string transactionId = 2; + * string transactionId = 2; */ java.lang.String getTransactionId(); /** @@ -10596,7 +10125,7 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; + * string transactionId = 2; */ com.google.protobuf.ByteString getTransactionIdBytes(); @@ -10650,15 +10179,7 @@ public final class CanalEntry { **执行的thread Id* * * - * optional int64 threadId = 4; - */ - boolean hasThreadId(); - /** - *
      -     **执行的thread Id*
      -     * 
      - * - * optional int64 threadId = 4; + * int64 threadId = 4; */ long getThreadId(); } @@ -10710,14 +10231,14 @@ public final class CanalEntry { done = true; break; case 8: { - bitField0_ |= 0x00000001; + executeTime_ = input.readInt64(); break; } case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - transactionId_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + transactionId_ = s; break; } case 26: { @@ -10726,16 +10247,16 @@ public final class CanalEntry { mutable_bitField0_ |= 0x00000004; } props_.add( - input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.parser(), extensionRegistry)); break; } case 32: { - bitField0_ |= 0x00000004; + threadId_ = input.readInt64(); break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -10777,17 +10298,7 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -     **已废弃,请使用header里的executeTime*
      -     * 
      - * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; @@ -10800,17 +10311,7 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -     **已废弃,Begin里不提供事务id*
      -     * 
      - * - * optional string transactionId = 2; + * string transactionId = 2; */ public java.lang.String getTransactionId() { java.lang.Object ref = transactionId_; @@ -10820,9 +10321,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } + transactionId_ = s; return s; } } @@ -10831,7 +10330,7 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { @@ -10909,17 +10408,7 @@ public final class CanalEntry { **执行的thread Id* * * - * optional int64 threadId = 4; - */ - public boolean hasThreadId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -     **执行的thread Id*
      -     * 
      - * - * optional int64 threadId = 4; + * int64 threadId = 4; */ public long getThreadId() { return threadId_; @@ -10939,16 +10428,16 @@ public final class CanalEntry { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (executeTime_ != 0L) { output.writeInt64(1, executeTime_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getTransactionIdBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, transactionId_); } for (int i = 0; i < props_.size(); i++) { output.writeMessage(3, props_.get(i)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (threadId_ != 0L) { output.writeInt64(4, threadId_); } unknownFields.writeTo(output); @@ -10960,18 +10449,18 @@ public final class CanalEntry { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (executeTime_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(1, executeTime_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getTransactionIdBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, transactionId_); } for (int i = 0; i < props_.size(); i++) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(3, props_.get(i)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (threadId_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(4, threadId_); } @@ -10991,23 +10480,14 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin other = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin) obj; boolean result = true; - result = result && (hasExecuteTime() == other.hasExecuteTime()); - if (hasExecuteTime()) { - result = result && (getExecuteTime() - == other.getExecuteTime()); - } - result = result && (hasTransactionId() == other.hasTransactionId()); - if (hasTransactionId()) { - result = result && getTransactionId() - .equals(other.getTransactionId()); - } + result = result && (getExecuteTime() + == other.getExecuteTime()); + result = result && getTransactionId() + .equals(other.getTransactionId()); result = result && getPropsList() .equals(other.getPropsList()); - result = result && (hasThreadId() == other.hasThreadId()); - if (hasThreadId()) { - result = result && (getThreadId() - == other.getThreadId()); - } + result = result && (getThreadId() + == other.getThreadId()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -11019,24 +10499,18 @@ public final class CanalEntry { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasExecuteTime()) { - hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getExecuteTime()); - } - if (hasTransactionId()) { - hash = (37 * hash) + TRANSACTIONID_FIELD_NUMBER; - hash = (53 * hash) + getTransactionId().hashCode(); - } + hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getExecuteTime()); + hash = (37 * hash) + TRANSACTIONID_FIELD_NUMBER; + hash = (53 * hash) + getTransactionId().hashCode(); if (getPropsCount() > 0) { hash = (37 * hash) + PROPS_FIELD_NUMBER; hash = (53 * hash) + getPropsList().hashCode(); } - if (hasThreadId()) { - hash = (37 * hash) + THREADID_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getThreadId()); - } + hash = (37 * hash) + THREADID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getThreadId()); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -11176,9 +10650,9 @@ public final class CanalEntry { public Builder clear() { super.clear(); executeTime_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); + transactionId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + if (propsBuilder_ == null) { props_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000004); @@ -11186,7 +10660,7 @@ public final class CanalEntry { propsBuilder_.clear(); } threadId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000008); + return this; } @@ -11215,13 +10689,7 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin result = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.executeTime_ = executeTime_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.transactionId_ = transactionId_; if (propsBuilder_ == null) { if (((bitField0_ & 0x00000004) == 0x00000004)) { @@ -11232,9 +10700,6 @@ public final class CanalEntry { } else { result.props_ = propsBuilder_.build(); } - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000004; - } result.threadId_ = threadId_; result.bitField0_ = to_bitField0_; onBuilt(); @@ -11285,11 +10750,10 @@ public final class CanalEntry { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin other) { if (other == com.alibaba.otter.canal.protocol.CanalEntry.TransactionBegin.getDefaultInstance()) return this; - if (other.hasExecuteTime()) { + if (other.getExecuteTime() != 0L) { setExecuteTime(other.getExecuteTime()); } - if (other.hasTransactionId()) { - bitField0_ |= 0x00000002; + if (!other.getTransactionId().isEmpty()) { transactionId_ = other.transactionId_; onChanged(); } @@ -11319,7 +10783,7 @@ public final class CanalEntry { } } } - if (other.hasThreadId()) { + if (other.getThreadId() != 0L) { setThreadId(other.getThreadId()); } this.mergeUnknownFields(other.unknownFields); @@ -11358,17 +10822,7 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -       **已废弃,请使用header里的executeTime*
      -       * 
      - * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; @@ -11378,10 +10832,10 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public Builder setExecuteTime(long value) { - bitField0_ |= 0x00000001; + executeTime_ = value; onChanged(); return this; @@ -11391,10 +10845,10 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public Builder clearExecuteTime() { - bitField0_ = (bitField0_ & ~0x00000001); + executeTime_ = 0L; onChanged(); return this; @@ -11406,17 +10860,7 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -       **已废弃,Begin里不提供事务id*
      -       * 
      - * - * optional string transactionId = 2; + * string transactionId = 2; */ public java.lang.String getTransactionId() { java.lang.Object ref = transactionId_; @@ -11424,9 +10868,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } + transactionId_ = s; return s; } else { return (java.lang.String) ref; @@ -11437,7 +10879,7 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { @@ -11457,14 +10899,14 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public Builder setTransactionId( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + transactionId_ = value; onChanged(); return this; @@ -11474,10 +10916,10 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public Builder clearTransactionId() { - bitField0_ = (bitField0_ & ~0x00000002); + transactionId_ = getDefaultInstance().getTransactionId(); onChanged(); return this; @@ -11487,14 +10929,15 @@ public final class CanalEntry { **已废弃,Begin里不提供事务id* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public Builder setTransactionIdBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + transactionId_ = value; onChanged(); return this; @@ -11818,17 +11261,7 @@ public final class CanalEntry { **执行的thread Id* * * - * optional int64 threadId = 4; - */ - public boolean hasThreadId() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -       **执行的thread Id*
      -       * 
      - * - * optional int64 threadId = 4; + * int64 threadId = 4; */ public long getThreadId() { return threadId_; @@ -11838,10 +11271,10 @@ public final class CanalEntry { **执行的thread Id* * * - * optional int64 threadId = 4; + * int64 threadId = 4; */ public Builder setThreadId(long value) { - bitField0_ |= 0x00000008; + threadId_ = value; onChanged(); return this; @@ -11851,10 +11284,10 @@ public final class CanalEntry { **执行的thread Id* * * - * optional int64 threadId = 4; + * int64 threadId = 4; */ public Builder clearThreadId() { - bitField0_ = (bitField0_ & ~0x00000008); + threadId_ = 0L; onChanged(); return this; @@ -11862,7 +11295,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -11885,7 +11318,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public TransactionBegin parsePartialFrom( @@ -11921,15 +11354,7 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; - */ - boolean hasExecuteTime(); - /** - *
      -     **已废弃,请使用header里的executeTime*
      -     * 
      - * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ long getExecuteTime(); @@ -11938,15 +11363,7 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; - */ - boolean hasTransactionId(); - /** - *
      -     **事务号*
      -     * 
      - * - * optional string transactionId = 2; + * string transactionId = 2; */ java.lang.String getTransactionId(); /** @@ -11954,7 +11371,7 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; + * string transactionId = 2; */ com.google.protobuf.ByteString getTransactionIdBytes(); @@ -12050,14 +11467,14 @@ public final class CanalEntry { done = true; break; case 8: { - bitField0_ |= 0x00000001; + executeTime_ = input.readInt64(); break; } case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - transactionId_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + transactionId_ = s; break; } case 26: { @@ -12066,11 +11483,11 @@ public final class CanalEntry { mutable_bitField0_ |= 0x00000004; } props_.add( - input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.PARSER, extensionRegistry)); + input.readMessage(com.alibaba.otter.canal.protocol.CanalEntry.Pair.parser(), extensionRegistry)); break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -12112,17 +11529,7 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -     **已废弃,请使用header里的executeTime*
      -     * 
      - * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; @@ -12135,17 +11542,7 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -     **事务号*
      -     * 
      - * - * optional string transactionId = 2; + * string transactionId = 2; */ public java.lang.String getTransactionId() { java.lang.Object ref = transactionId_; @@ -12155,9 +11552,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } + transactionId_ = s; return s; } } @@ -12166,7 +11561,7 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { @@ -12251,10 +11646,10 @@ public final class CanalEntry { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (executeTime_ != 0L) { output.writeInt64(1, executeTime_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getTransactionIdBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, transactionId_); } for (int i = 0; i < props_.size(); i++) { @@ -12269,11 +11664,11 @@ public final class CanalEntry { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (executeTime_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(1, executeTime_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getTransactionIdBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, transactionId_); } for (int i = 0; i < props_.size(); i++) { @@ -12296,16 +11691,10 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd other = (com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd) obj; boolean result = true; - result = result && (hasExecuteTime() == other.hasExecuteTime()); - if (hasExecuteTime()) { - result = result && (getExecuteTime() - == other.getExecuteTime()); - } - result = result && (hasTransactionId() == other.hasTransactionId()); - if (hasTransactionId()) { - result = result && getTransactionId() - .equals(other.getTransactionId()); - } + result = result && (getExecuteTime() + == other.getExecuteTime()); + result = result && getTransactionId() + .equals(other.getTransactionId()); result = result && getPropsList() .equals(other.getPropsList()); result = result && unknownFields.equals(other.unknownFields); @@ -12319,15 +11708,11 @@ public final class CanalEntry { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasExecuteTime()) { - hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getExecuteTime()); - } - if (hasTransactionId()) { - hash = (37 * hash) + TRANSACTIONID_FIELD_NUMBER; - hash = (53 * hash) + getTransactionId().hashCode(); - } + hash = (37 * hash) + EXECUTETIME_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getExecuteTime()); + hash = (37 * hash) + TRANSACTIONID_FIELD_NUMBER; + hash = (53 * hash) + getTransactionId().hashCode(); if (getPropsCount() > 0) { hash = (37 * hash) + PROPS_FIELD_NUMBER; hash = (53 * hash) + getPropsList().hashCode(); @@ -12471,9 +11856,9 @@ public final class CanalEntry { public Builder clear() { super.clear(); executeTime_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); + transactionId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + if (propsBuilder_ == null) { props_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000004); @@ -12508,13 +11893,7 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd result = new com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.executeTime_ = executeTime_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.transactionId_ = transactionId_; if (propsBuilder_ == null) { if (((bitField0_ & 0x00000004) == 0x00000004)) { @@ -12574,11 +11953,10 @@ public final class CanalEntry { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd other) { if (other == com.alibaba.otter.canal.protocol.CanalEntry.TransactionEnd.getDefaultInstance()) return this; - if (other.hasExecuteTime()) { + if (other.getExecuteTime() != 0L) { setExecuteTime(other.getExecuteTime()); } - if (other.hasTransactionId()) { - bitField0_ |= 0x00000002; + if (!other.getTransactionId().isEmpty()) { transactionId_ = other.transactionId_; onChanged(); } @@ -12644,17 +12022,7 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; - */ - public boolean hasExecuteTime() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - *
      -       **已废弃,请使用header里的executeTime*
      -       * 
      - * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public long getExecuteTime() { return executeTime_; @@ -12664,10 +12032,10 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public Builder setExecuteTime(long value) { - bitField0_ |= 0x00000001; + executeTime_ = value; onChanged(); return this; @@ -12677,10 +12045,10 @@ public final class CanalEntry { **已废弃,请使用header里的executeTime* * * - * optional int64 executeTime = 1; + * int64 executeTime = 1; */ public Builder clearExecuteTime() { - bitField0_ = (bitField0_ & ~0x00000001); + executeTime_ = 0L; onChanged(); return this; @@ -12692,17 +12060,7 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; - */ - public boolean hasTransactionId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -       **事务号*
      -       * 
      - * - * optional string transactionId = 2; + * string transactionId = 2; */ public java.lang.String getTransactionId() { java.lang.Object ref = transactionId_; @@ -12710,9 +12068,7 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - transactionId_ = s; - } + transactionId_ = s; return s; } else { return (java.lang.String) ref; @@ -12723,7 +12079,7 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public com.google.protobuf.ByteString getTransactionIdBytes() { @@ -12743,14 +12099,14 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public Builder setTransactionId( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + transactionId_ = value; onChanged(); return this; @@ -12760,10 +12116,10 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public Builder clearTransactionId() { - bitField0_ = (bitField0_ & ~0x00000002); + transactionId_ = getDefaultInstance().getTransactionId(); onChanged(); return this; @@ -12773,14 +12129,15 @@ public final class CanalEntry { **事务号* * * - * optional string transactionId = 2; + * string transactionId = 2; */ public Builder setTransactionIdBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + transactionId_ = value; onChanged(); return this; @@ -13100,7 +12457,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -13123,7 +12480,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public TransactionEnd parsePartialFrom( @@ -13155,29 +12512,21 @@ public final class CanalEntry { com.google.protobuf.MessageOrBuilder { /** - * optional string key = 1; - */ - boolean hasKey(); - /** - * optional string key = 1; + * string key = 1; */ java.lang.String getKey(); /** - * optional string key = 1; + * string key = 1; */ com.google.protobuf.ByteString getKeyBytes(); /** - * optional string value = 2; - */ - boolean hasValue(); - /** - * optional string value = 2; + * string value = 2; */ java.lang.String getValue(); /** - * optional string value = 2; + * string value = 2; */ com.google.protobuf.ByteString getValueBytes(); @@ -13228,19 +12577,19 @@ public final class CanalEntry { done = true; break; case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - key_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + key_ = s; break; } case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - value_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + value_ = s; break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -13271,17 +12620,10 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.Pair.class, com.alibaba.otter.canal.protocol.CanalEntry.Pair.Builder.class); } - private int bitField0_; public static final int KEY_FIELD_NUMBER = 1; private volatile java.lang.Object key_; /** - * optional string key = 1; - */ - public boolean hasKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string key = 1; + * string key = 1; */ public java.lang.String getKey() { java.lang.Object ref = key_; @@ -13291,14 +12633,12 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - key_ = s; - } + key_ = s; return s; } } /** - * optional string key = 1; + * string key = 1; */ public com.google.protobuf.ByteString getKeyBytes() { @@ -13317,13 +12657,7 @@ public final class CanalEntry { public static final int VALUE_FIELD_NUMBER = 2; private volatile java.lang.Object value_; /** - * optional string value = 2; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string value = 2; + * string value = 2; */ public java.lang.String getValue() { java.lang.Object ref = value_; @@ -13333,14 +12667,12 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } + value_ = s; return s; } } /** - * optional string value = 2; + * string value = 2; */ public com.google.protobuf.ByteString getValueBytes() { @@ -13370,10 +12702,10 @@ public final class CanalEntry { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getKeyBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, key_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getValueBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, value_); } unknownFields.writeTo(output); @@ -13385,10 +12717,10 @@ public final class CanalEntry { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getKeyBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, key_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getValueBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, value_); } size += unknownFields.getSerializedSize(); @@ -13407,16 +12739,10 @@ public final class CanalEntry { com.alibaba.otter.canal.protocol.CanalEntry.Pair other = (com.alibaba.otter.canal.protocol.CanalEntry.Pair) obj; boolean result = true; - result = result && (hasKey() == other.hasKey()); - if (hasKey()) { - result = result && getKey() - .equals(other.getKey()); - } - result = result && (hasValue() == other.hasValue()); - if (hasValue()) { - result = result && getValue() - .equals(other.getValue()); - } + result = result && getKey() + .equals(other.getKey()); + result = result && getValue() + .equals(other.getValue()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -13428,14 +12754,10 @@ public final class CanalEntry { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasKey()) { - hash = (37 * hash) + KEY_FIELD_NUMBER; - hash = (53 * hash) + getKey().hashCode(); - } - if (hasValue()) { - hash = (37 * hash) + VALUE_FIELD_NUMBER; - hash = (53 * hash) + getValue().hashCode(); - } + hash = (37 * hash) + KEY_FIELD_NUMBER; + hash = (53 * hash) + getKey().hashCode(); + hash = (37 * hash) + VALUE_FIELD_NUMBER; + hash = (53 * hash) + getValue().hashCode(); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -13574,9 +12896,9 @@ public final class CanalEntry { public Builder clear() { super.clear(); key_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); + value_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + return this; } @@ -13603,17 +12925,8 @@ public final class CanalEntry { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalEntry.Pair buildPartial() { com.alibaba.otter.canal.protocol.CanalEntry.Pair result = new com.alibaba.otter.canal.protocol.CanalEntry.Pair(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.key_ = key_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.value_ = value_; - result.bitField0_ = to_bitField0_; onBuilt(); return result; } @@ -13662,13 +12975,11 @@ public final class CanalEntry { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalEntry.Pair other) { if (other == com.alibaba.otter.canal.protocol.CanalEntry.Pair.getDefaultInstance()) return this; - if (other.hasKey()) { - bitField0_ |= 0x00000001; + if (!other.getKey().isEmpty()) { key_ = other.key_; onChanged(); } - if (other.hasValue()) { - bitField0_ |= 0x00000002; + if (!other.getValue().isEmpty()) { value_ = other.value_; onChanged(); } @@ -13700,17 +13011,10 @@ public final class CanalEntry { } return this; } - private int bitField0_; private java.lang.Object key_ = ""; /** - * optional string key = 1; - */ - public boolean hasKey() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string key = 1; + * string key = 1; */ public java.lang.String getKey() { java.lang.Object ref = key_; @@ -13718,16 +13022,14 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - key_ = s; - } + key_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string key = 1; + * string key = 1; */ public com.google.protobuf.ByteString getKeyBytes() { @@ -13743,36 +13045,37 @@ public final class CanalEntry { } } /** - * optional string key = 1; + * string key = 1; */ public Builder setKey( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + key_ = value; onChanged(); return this; } /** - * optional string key = 1; + * string key = 1; */ public Builder clearKey() { - bitField0_ = (bitField0_ & ~0x00000001); + key_ = getDefaultInstance().getKey(); onChanged(); return this; } /** - * optional string key = 1; + * string key = 1; */ public Builder setKeyBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + checkByteStringIsUtf8(value); + key_ = value; onChanged(); return this; @@ -13780,13 +13083,7 @@ public final class CanalEntry { private java.lang.Object value_ = ""; /** - * optional string value = 2; - */ - public boolean hasValue() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string value = 2; + * string value = 2; */ public java.lang.String getValue() { java.lang.Object ref = value_; @@ -13794,16 +13091,14 @@ public final class CanalEntry { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - value_ = s; - } + value_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string value = 2; + * string value = 2; */ public com.google.protobuf.ByteString getValueBytes() { @@ -13819,36 +13114,37 @@ public final class CanalEntry { } } /** - * optional string value = 2; + * string value = 2; */ public Builder setValue( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + value_ = value; onChanged(); return this; } /** - * optional string value = 2; + * string value = 2; */ public Builder clearValue() { - bitField0_ = (bitField0_ & ~0x00000002); + value_ = getDefaultInstance().getValue(); onChanged(); return this; } /** - * optional string value = 2; + * string value = 2; */ public Builder setValueBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + value_ = value; onChanged(); return this; @@ -13856,7 +13152,7 @@ public final class CanalEntry { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -13879,7 +13175,7 @@ public final class CanalEntry { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Pair parsePartialFrom( @@ -13956,55 +13252,59 @@ public final class CanalEntry { static { java.lang.String[] descriptorData = { "\n\023EntryProtocol.proto\022 com.alibaba.otter" + - ".canal.protocol\"\236\001\n\005Entry\0228\n\006header\030\001 \001(" + + ".canal.protocol\"\254\001\n\005Entry\0228\n\006header\030\001 \001(" + "\0132(.com.alibaba.otter.canal.protocol.Hea" + - "der\022G\n\tentryType\030\002 \001(\0162+.com.alibaba.ott" + - "er.canal.protocol.EntryType:\007ROWDATA\022\022\n\n" + - "storeValue\030\003 \001(\014\"\221\003\n\006Header\022\022\n\007version\030\001" + - " \001(\005:\0011\022\023\n\013logfileName\030\002 \001(\t\022\025\n\rlogfileO" + - "ffset\030\003 \001(\003\022\020\n\010serverId\030\004 \001(\003\022\024\n\014servere" + - "nCode\030\005 \001(\t\022\023\n\013executeTime\030\006 \001(\003\022A\n\nsour" + - "ceType\030\007 \001(\0162&.com.alibaba.otter.canal.p" + - "rotocol.Type:\005MYSQL\022\022\n\nschemaName\030\010 \001(\t\022" + - "\021\n\ttableName\030\t \001(\t\022\023\n\013eventLength\030\n \001(\003\022" + - "F\n\teventType\030\013 \001(\0162+.com.alibaba.otter.c" + - "anal.protocol.EventType:\006UPDATE\0225\n\005props" + - "\030\014 \003(\0132&.com.alibaba.otter.canal.protoco" + - "l.Pair\022\014\n\004gtid\030\r \001(\t\"\326\001\n\006Column\022\r\n\005index" + - "\030\001 \001(\005\022\017\n\007sqlType\030\002 \001(\005\022\014\n\004name\030\003 \001(\t\022\r\n" + - "\005isKey\030\004 \001(\010\022\017\n\007updated\030\005 \001(\010\022\025\n\006isNull\030" + - "\006 \001(\010:\005false\0225\n\005props\030\007 \003(\0132&.com.alibab" + - "a.otter.canal.protocol.Pair\022\r\n\005value\030\010 \001" + - "(\t\022\016\n\006length\030\t \001(\005\022\021\n\tmysqlType\030\n \001(\t\"\301\001" + - "\n\007RowData\022?\n\rbeforeColumns\030\001 \003(\0132(.com.a" + - "libaba.otter.canal.protocol.Column\022>\n\014af" + - "terColumns\030\002 \003(\0132(.com.alibaba.otter.can" + - "al.protocol.Column\0225\n\005props\030\003 \003(\0132&.com." + - "alibaba.otter.canal.protocol.Pair\"\222\002\n\tRo" + - "wChange\022\017\n\007tableId\030\001 \001(\003\022F\n\teventType\030\002 " + - "\001(\0162+.com.alibaba.otter.canal.protocol.E" + - "ventType:\006UPDATE\022\024\n\005isDdl\030\n \001(\010:\005false\022\013" + - "\n\003sql\030\013 \001(\t\022;\n\010rowDatas\030\014 \003(\0132).com.alib" + - "aba.otter.canal.protocol.RowData\0225\n\005prop" + - "s\030\r \003(\0132&.com.alibaba.otter.canal.protoc" + - "ol.Pair\022\025\n\rddlSchemaName\030\016 \001(\t\"\207\001\n\020Trans" + - "actionBegin\022\023\n\013executeTime\030\001 \001(\003\022\025\n\rtran" + - "sactionId\030\002 \001(\t\0225\n\005props\030\003 \003(\0132&.com.ali" + - "baba.otter.canal.protocol.Pair\022\020\n\010thread" + - "Id\030\004 \001(\003\"s\n\016TransactionEnd\022\023\n\013executeTim" + - "e\030\001 \001(\003\022\025\n\rtransactionId\030\002 \001(\t\0225\n\005props\030" + - "\003 \003(\0132&.com.alibaba.otter.canal.protocol" + - ".Pair\"\"\n\004Pair\022\013\n\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(" + - "\t*^\n\tEntryType\022\024\n\020TRANSACTIONBEGIN\020\001\022\013\n\007" + - "ROWDATA\020\002\022\022\n\016TRANSACTIONEND\020\003\022\r\n\tHEARTBE" + - "AT\020\004\022\013\n\007GTIDLOG\020\005*\306\001\n\tEventType\022\n\n\006INSER" + - "T\020\001\022\n\n\006UPDATE\020\002\022\n\n\006DELETE\020\003\022\n\n\006CREATE\020\004\022" + - "\t\n\005ALTER\020\005\022\t\n\005ERASE\020\006\022\t\n\005QUERY\020\007\022\014\n\010TRUN" + - "CATE\020\010\022\n\n\006RENAME\020\t\022\n\n\006CINDEX\020\n\022\n\n\006DINDEX" + - "\020\013\022\010\n\004GTID\020\014\022\014\n\010XACOMMIT\020\r\022\016\n\nXAROLLBACK" + - "\020\016\022\016\n\nMHEARTBEAT\020\017*(\n\004Type\022\n\n\006ORACLE\020\001\022\t" + - "\n\005MYSQL\020\002\022\t\n\005PGSQL\020\003B0\n com.alibaba.otte" + - "r.canal.protocolB\nCanalEntryH\001" + "der\022@\n\tentryType\030\002 \001(\0162+.com.alibaba.ott" + + "er.canal.protocol.EntryTypeH\000\022\022\n\nstoreVa" + + "lue\030\003 \001(\014B\023\n\021entryType_present\"\303\003\n\006Heade" + + "r\022\021\n\007version\030\001 \001(\005H\000\022\023\n\013logfileName\030\002 \001(" + + "\t\022\025\n\rlogfileOffset\030\003 \001(\003\022\020\n\010serverId\030\004 \001" + + "(\003\022\024\n\014serverenCode\030\005 \001(\t\022\023\n\013executeTime\030" + + "\006 \001(\003\022<\n\nsourceType\030\007 \001(\0162&.com.alibaba." + + "otter.canal.protocol.TypeH\001\022\022\n\nschemaNam" + + "e\030\010 \001(\t\022\021\n\ttableName\030\t \001(\t\022\023\n\013eventLengt" + + "h\030\n \001(\003\022@\n\teventType\030\013 \001(\0162+.com.alibaba" + + ".otter.canal.protocol.EventTypeH\002\0225\n\005pro" + + "ps\030\014 \003(\0132&.com.alibaba.otter.canal.proto" + + "col.Pair\022\014\n\004gtid\030\r \001(\tB\021\n\017version_presen" + + "tB\024\n\022sourceType_presentB\023\n\021eventType_pre" + + "sent\"\343\001\n\006Column\022\r\n\005index\030\001 \001(\005\022\017\n\007sqlTyp" + + "e\030\002 \001(\005\022\014\n\004name\030\003 \001(\t\022\r\n\005isKey\030\004 \001(\010\022\017\n\007" + + "updated\030\005 \001(\010\022\020\n\006isNull\030\006 \001(\010H\000\0225\n\005props" + + "\030\007 \003(\0132&.com.alibaba.otter.canal.protoco" + + "l.Pair\022\r\n\005value\030\010 \001(\t\022\016\n\006length\030\t \001(\005\022\021\n" + + "\tmysqlType\030\n \001(\tB\020\n\016isNull_present\"\301\001\n\007R" + + "owData\022?\n\rbeforeColumns\030\001 \003(\0132(.com.alib" + + "aba.otter.canal.protocol.Column\022>\n\014after" + + "Columns\030\002 \003(\0132(.com.alibaba.otter.canal." + + "protocol.Column\0225\n\005props\030\003 \003(\0132&.com.ali" + + "baba.otter.canal.protocol.Pair\"\255\002\n\tRowCh" + + "ange\022\017\n\007tableId\030\001 \001(\003\022@\n\teventType\030\002 \001(\016" + + "2+.com.alibaba.otter.canal.protocol.Even" + + "tTypeH\000\022\017\n\005isDdl\030\n \001(\010H\001\022\013\n\003sql\030\013 \001(\t\022;\n" + + "\010rowDatas\030\014 \003(\0132).com.alibaba.otter.cana" + + "l.protocol.RowData\0225\n\005props\030\r \003(\0132&.com." + + "alibaba.otter.canal.protocol.Pair\022\025\n\rddl" + + "SchemaName\030\016 \001(\tB\023\n\021eventType_presentB\017\n" + + "\risDdl_present\"\207\001\n\020TransactionBegin\022\023\n\013e" + + "xecuteTime\030\001 \001(\003\022\025\n\rtransactionId\030\002 \001(\t\022" + + "5\n\005props\030\003 \003(\0132&.com.alibaba.otter.canal" + + ".protocol.Pair\022\020\n\010threadId\030\004 \001(\003\"s\n\016Tran" + + "sactionEnd\022\023\n\013executeTime\030\001 \001(\003\022\025\n\rtrans" + + "actionId\030\002 \001(\t\0225\n\005props\030\003 \003(\0132&.com.alib" + + "aba.otter.canal.protocol.Pair\"\"\n\004Pair\022\013\n" + + "\003key\030\001 \001(\t\022\r\n\005value\030\002 \001(\t*^\n\tEntryType\022\024" + + "\n\020TRANSACTIONBEGIN\020\000\022\013\n\007ROWDATA\020\001\022\022\n\016TRA" + + "NSACTIONEND\020\002\022\r\n\tHEARTBEAT\020\003\022\013\n\007GTIDLOG\020" + + "\004*\345\001\n\tEventType\022\035\n\031EVENTTYPECOMPATIBLEPR" + + "OTO2\020\000\022\n\n\006INSERT\020\001\022\n\n\006UPDATE\020\002\022\n\n\006DELETE" + + "\020\003\022\n\n\006CREATE\020\004\022\t\n\005ALTER\020\005\022\t\n\005ERASE\020\006\022\t\n\005" + + "QUERY\020\007\022\014\n\010TRUNCATE\020\010\022\n\n\006RENAME\020\t\022\n\n\006CIN" + + "DEX\020\n\022\n\n\006DINDEX\020\013\022\010\n\004GTID\020\014\022\014\n\010XACOMMIT\020" + + "\r\022\016\n\nXAROLLBACK\020\016\022\016\n\nMHEARTBEAT\020\017*B\n\004Typ" + + "e\022\030\n\024TYPECOMPATIBLEPROTO2\020\000\022\n\n\006ORACLE\020\001\022" + + "\t\n\005MYSQL\020\002\022\t\n\005PGSQL\020\003B0\n com.alibaba.ott" + + "er.canal.protocolB\nCanalEntryH\001b\006proto3" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor. InternalDescriptorAssigner() { @@ -14023,19 +13323,19 @@ public final class CanalEntry { internal_static_com_alibaba_otter_canal_protocol_Entry_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_Entry_descriptor, - new java.lang.String[] { "Header", "EntryType", "StoreValue", }); + new java.lang.String[] { "Header", "EntryType", "StoreValue", "EntryTypePresent", }); internal_static_com_alibaba_otter_canal_protocol_Header_descriptor = getDescriptor().getMessageTypes().get(1); internal_static_com_alibaba_otter_canal_protocol_Header_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_Header_descriptor, - new java.lang.String[] { "Version", "LogfileName", "LogfileOffset", "ServerId", "ServerenCode", "ExecuteTime", "SourceType", "SchemaName", "TableName", "EventLength", "EventType", "Props", "Gtid", }); + new java.lang.String[] { "Version", "LogfileName", "LogfileOffset", "ServerId", "ServerenCode", "ExecuteTime", "SourceType", "SchemaName", "TableName", "EventLength", "EventType", "Props", "Gtid", "VersionPresent", "SourceTypePresent", "EventTypePresent", }); internal_static_com_alibaba_otter_canal_protocol_Column_descriptor = getDescriptor().getMessageTypes().get(2); internal_static_com_alibaba_otter_canal_protocol_Column_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_Column_descriptor, - new java.lang.String[] { "Index", "SqlType", "Name", "IsKey", "Updated", "IsNull", "Props", "Value", "Length", "MysqlType", }); + new java.lang.String[] { "Index", "SqlType", "Name", "IsKey", "Updated", "IsNull", "Props", "Value", "Length", "MysqlType", "IsNullPresent", }); internal_static_com_alibaba_otter_canal_protocol_RowData_descriptor = getDescriptor().getMessageTypes().get(3); internal_static_com_alibaba_otter_canal_protocol_RowData_fieldAccessorTable = new @@ -14047,7 +13347,7 @@ public final class CanalEntry { internal_static_com_alibaba_otter_canal_protocol_RowChange_fieldAccessorTable = new com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( internal_static_com_alibaba_otter_canal_protocol_RowChange_descriptor, - new java.lang.String[] { "TableId", "EventType", "IsDdl", "Sql", "RowDatas", "Props", "DdlSchemaName", }); + new java.lang.String[] { "TableId", "EventType", "IsDdl", "Sql", "RowDatas", "Props", "DdlSchemaName", "EventTypePresent", "IsDdlPresent", }); internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_descriptor = getDescriptor().getMessageTypes().get(5); internal_static_com_alibaba_otter_canal_protocol_TransactionBegin_fieldAccessorTable = new diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java index 9c912af7..1e7e4e53 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalPacket.java @@ -19,6 +19,10 @@ public final class CanalPacket { */ public enum Compression implements com.google.protobuf.ProtocolMessageEnum { + /** + * COMPRESSIONCOMPATIBLEPROTO2 = 0; + */ + COMPRESSIONCOMPATIBLEPROTO2(0), /** * NONE = 1; */ @@ -35,8 +39,13 @@ public final class CanalPacket { * LZF = 4; */ LZF(4), + UNRECOGNIZED(-1), ; + /** + * COMPRESSIONCOMPATIBLEPROTO2 = 0; + */ + public static final int COMPRESSIONCOMPATIBLEPROTO2_VALUE = 0; /** * NONE = 1; */ @@ -56,6 +65,10 @@ public final class CanalPacket { public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } return value; } @@ -69,6 +82,7 @@ public final class CanalPacket { public static Compression forNumber(int value) { switch (value) { + case 0: return COMPRESSIONCOMPATIBLEPROTO2; case 1: return NONE; case 2: return ZLIB; case 3: return GZIP; @@ -110,6 +124,9 @@ public final class CanalPacket { throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } return VALUES[desc.getIndex()]; } @@ -127,6 +144,14 @@ public final class CanalPacket { */ public enum PacketType implements com.google.protobuf.ProtocolMessageEnum { + /** + *
      +     *compatible
      +     * 
      + * + * PACKAGETYPECOMPATIBLEPROTO2 = 0; + */ + PACKAGETYPECOMPATIBLEPROTO2(0), /** * HANDSHAKE = 1; */ @@ -183,8 +208,17 @@ public final class CanalPacket { * CLIENTROLLBACK = 12; */ CLIENTROLLBACK(12), + UNRECOGNIZED(-1), ; + /** + *
      +     *compatible
      +     * 
      + * + * PACKAGETYPECOMPATIBLEPROTO2 = 0; + */ + public static final int PACKAGETYPECOMPATIBLEPROTO2_VALUE = 0; /** * HANDSHAKE = 1; */ @@ -244,6 +278,10 @@ public final class CanalPacket { public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } return value; } @@ -257,6 +295,7 @@ public final class CanalPacket { public static PacketType forNumber(int value) { switch (value) { + case 0: return PACKAGETYPECOMPATIBLEPROTO2; case 1: return HANDSHAKE; case 2: return CLIENTAUTHENTICATION; case 3: return ACK; @@ -306,6 +345,9 @@ public final class CanalPacket { throw new java.lang.IllegalArgumentException( "EnumValueDescriptor is not for this type."); } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } return VALUES[desc.getIndex()]; } @@ -323,49 +365,43 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional int32 magic_number = 1 [default = 17]; - */ - boolean hasMagicNumber(); - /** - * optional int32 magic_number = 1 [default = 17]; + * int32 magic_number = 1; */ int getMagicNumber(); /** - * optional int32 version = 2 [default = 1]; - */ - boolean hasVersion(); - /** - * optional int32 version = 2 [default = 1]; + * int32 version = 2; */ int getVersion(); /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ - boolean hasType(); + int getTypeValue(); /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType(); /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ - boolean hasCompression(); + int getCompressionValue(); /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression(); /** - * optional bytes body = 5; - */ - boolean hasBody(); - /** - * optional bytes body = 5; + * bytes body = 5; */ com.google.protobuf.ByteString getBody(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Packet.MagicNumberPresentCase getMagicNumberPresentCase(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Packet.VersionPresentCase getVersionPresentCase(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Packet.CompressionPresentCase getCompressionPresentCase(); } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Packet} @@ -380,10 +416,7 @@ public final class CanalPacket { super(builder); } private Packet() { - magicNumber_ = 17; - version_ = 1; - type_ = 1; - compression_ = 1; + type_ = 0; body_ = com.google.protobuf.ByteString.EMPTY; } @@ -412,46 +445,34 @@ public final class CanalPacket { done = true; break; case 8: { - bitField0_ |= 0x00000001; - magicNumber_ = input.readInt32(); + magicNumberPresentCase_ = 1; + magicNumberPresent_ = input.readInt32(); break; } case 16: { - bitField0_ |= 0x00000002; - version_ = input.readInt32(); + versionPresentCase_ = 2; + versionPresent_ = input.readInt32(); break; } case 24: { int rawValue = input.readEnum(); - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalPacket.PacketType value = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(3, rawValue); - } else { - bitField0_ |= 0x00000004; - type_ = rawValue; - } + + type_ = rawValue; break; } case 32: { int rawValue = input.readEnum(); - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(4, rawValue); - } else { - bitField0_ |= 0x00000008; - compression_ = rawValue; - } + compressionPresentCase_ = 4; + compressionPresent_ = rawValue; break; } case 42: { - bitField0_ |= 0x00000010; + body_ = input.readBytes(); break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -482,81 +503,180 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Packet.class, com.alibaba.otter.canal.protocol.CanalPacket.Packet.Builder.class); } - private int bitField0_; - public static final int MAGIC_NUMBER_FIELD_NUMBER = 1; - private int magicNumber_; - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public boolean hasMagicNumber() { - return ((bitField0_ & 0x00000001) == 0x00000001); + private int magicNumberPresentCase_ = 0; + private java.lang.Object magicNumberPresent_; + public enum MagicNumberPresentCase + implements com.google.protobuf.Internal.EnumLite { + MAGIC_NUMBER(1), + MAGICNUMBERPRESENT_NOT_SET(0); + private final int value; + private MagicNumberPresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static MagicNumberPresentCase valueOf(int value) { + return forNumber(value); + } + + public static MagicNumberPresentCase forNumber(int value) { + switch (value) { + case 1: return MAGIC_NUMBER; + case 0: return MAGICNUMBERPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public MagicNumberPresentCase + getMagicNumberPresentCase() { + return MagicNumberPresentCase.forNumber( + magicNumberPresentCase_); } + + private int versionPresentCase_ = 0; + private java.lang.Object versionPresent_; + public enum VersionPresentCase + implements com.google.protobuf.Internal.EnumLite { + VERSION(2), + VERSIONPRESENT_NOT_SET(0); + private final int value; + private VersionPresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static VersionPresentCase valueOf(int value) { + return forNumber(value); + } + + public static VersionPresentCase forNumber(int value) { + switch (value) { + case 2: return VERSION; + case 0: return VERSIONPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public VersionPresentCase + getVersionPresentCase() { + return VersionPresentCase.forNumber( + versionPresentCase_); + } + + private int compressionPresentCase_ = 0; + private java.lang.Object compressionPresent_; + public enum CompressionPresentCase + implements com.google.protobuf.Internal.EnumLite { + COMPRESSION(4), + COMPRESSIONPRESENT_NOT_SET(0); + private final int value; + private CompressionPresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static CompressionPresentCase valueOf(int value) { + return forNumber(value); + } + + public static CompressionPresentCase forNumber(int value) { + switch (value) { + case 4: return COMPRESSION; + case 0: return COMPRESSIONPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public CompressionPresentCase + getCompressionPresentCase() { + return CompressionPresentCase.forNumber( + compressionPresentCase_); + } + + public static final int MAGIC_NUMBER_FIELD_NUMBER = 1; /** - * optional int32 magic_number = 1 [default = 17]; + * int32 magic_number = 1; */ public int getMagicNumber() { - return magicNumber_; + if (magicNumberPresentCase_ == 1) { + return (java.lang.Integer) magicNumberPresent_; + } + return 0; } public static final int VERSION_FIELD_NUMBER = 2; - private int version_; /** - * optional int32 version = 2 [default = 1]; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional int32 version = 2 [default = 1]; + * int32 version = 2; */ public int getVersion() { - return version_; + if (versionPresentCase_ == 2) { + return (java.lang.Integer) versionPresent_; + } + return 1; } public static final int TYPE_FIELD_NUMBER = 3; private int type_; /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ - public boolean hasType() { - return ((bitField0_ & 0x00000004) == 0x00000004); + public int getTypeValue() { + return type_; } /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ public com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType() { @SuppressWarnings("deprecation") com.alibaba.otter.canal.protocol.CanalPacket.PacketType result = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.valueOf(type_); - return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE : result; + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.PacketType.UNRECOGNIZED : result; } public static final int COMPRESSION_FIELD_NUMBER = 4; - private int compression_; /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ - public boolean hasCompression() { - return ((bitField0_ & 0x00000008) == 0x00000008); + public int getCompressionValue() { + if (compressionPresentCase_ == 4) { + return (java.lang.Integer) compressionPresent_; + } + return Compression.NONE_VALUE; } /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ public com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(compression_); - return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE : result; + if (compressionPresentCase_ == 4) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf( + (java.lang.Integer) compressionPresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalPacket.Compression.COMPRESSIONCOMPATIBLEPROTO2; } public static final int BODY_FIELD_NUMBER = 5; private com.google.protobuf.ByteString body_; /** - * optional bytes body = 5; - */ - public boolean hasBody() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional bytes body = 5; + * bytes body = 5; */ public com.google.protobuf.ByteString getBody() { return body_; @@ -576,19 +696,21 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt32(1, magicNumber_); + if (magicNumberPresentCase_ == 1) { + output.writeInt32( + 1, (int)((java.lang.Integer) magicNumberPresent_)); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeInt32(2, version_); + if (versionPresentCase_ == 2) { + output.writeInt32( + 2, (int)((java.lang.Integer) versionPresent_)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (type_ != com.alibaba.otter.canal.protocol.CanalPacket.PacketType.PACKAGETYPECOMPATIBLEPROTO2.getNumber()) { output.writeEnum(3, type_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeEnum(4, compression_); + if (compressionPresentCase_ == 4) { + output.writeEnum(4, ((java.lang.Integer) compressionPresent_)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!body_.isEmpty()) { output.writeBytes(5, body_); } unknownFields.writeTo(output); @@ -600,23 +722,25 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (magicNumberPresentCase_ == 1) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(1, magicNumber_); + .computeInt32Size( + 1, (int)((java.lang.Integer) magicNumberPresent_)); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (versionPresentCase_ == 2) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(2, version_); + .computeInt32Size( + 2, (int)((java.lang.Integer) versionPresent_)); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (type_ != com.alibaba.otter.canal.protocol.CanalPacket.PacketType.PACKAGETYPECOMPATIBLEPROTO2.getNumber()) { size += com.google.protobuf.CodedOutputStream .computeEnumSize(3, type_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (compressionPresentCase_ == 4) { size += com.google.protobuf.CodedOutputStream - .computeEnumSize(4, compression_); + .computeEnumSize(4, ((java.lang.Integer) compressionPresent_)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!body_.isEmpty()) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(5, body_); } @@ -636,28 +760,41 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Packet other = (com.alibaba.otter.canal.protocol.CanalPacket.Packet) obj; boolean result = true; - result = result && (hasMagicNumber() == other.hasMagicNumber()); - if (hasMagicNumber()) { - result = result && (getMagicNumber() - == other.getMagicNumber()); + result = result && type_ == other.type_; + result = result && getBody() + .equals(other.getBody()); + result = result && getMagicNumberPresentCase().equals( + other.getMagicNumberPresentCase()); + if (!result) return false; + switch (magicNumberPresentCase_) { + case 1: + result = result && (getMagicNumber() + == other.getMagicNumber()); + break; + case 0: + default: } - result = result && (hasVersion() == other.hasVersion()); - if (hasVersion()) { - result = result && (getVersion() - == other.getVersion()); + result = result && getVersionPresentCase().equals( + other.getVersionPresentCase()); + if (!result) return false; + switch (versionPresentCase_) { + case 2: + result = result && (getVersion() + == other.getVersion()); + break; + case 0: + default: } - result = result && (hasType() == other.hasType()); - if (hasType()) { - result = result && type_ == other.type_; - } - result = result && (hasCompression() == other.hasCompression()); - if (hasCompression()) { - result = result && compression_ == other.compression_; - } - result = result && (hasBody() == other.hasBody()); - if (hasBody()) { - result = result && getBody() - .equals(other.getBody()); + result = result && getCompressionPresentCase().equals( + other.getCompressionPresentCase()); + if (!result) return false; + switch (compressionPresentCase_) { + case 4: + result = result && getCompressionValue() + == other.getCompressionValue(); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -670,25 +807,33 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasMagicNumber()) { - hash = (37 * hash) + MAGIC_NUMBER_FIELD_NUMBER; - hash = (53 * hash) + getMagicNumber(); + hash = (37 * hash) + TYPE_FIELD_NUMBER; + hash = (53 * hash) + type_; + hash = (37 * hash) + BODY_FIELD_NUMBER; + hash = (53 * hash) + getBody().hashCode(); + switch (magicNumberPresentCase_) { + case 1: + hash = (37 * hash) + MAGIC_NUMBER_FIELD_NUMBER; + hash = (53 * hash) + getMagicNumber(); + break; + case 0: + default: } - if (hasVersion()) { - hash = (37 * hash) + VERSION_FIELD_NUMBER; - hash = (53 * hash) + getVersion(); + switch (versionPresentCase_) { + case 2: + hash = (37 * hash) + VERSION_FIELD_NUMBER; + hash = (53 * hash) + getVersion(); + break; + case 0: + default: } - if (hasType()) { - hash = (37 * hash) + TYPE_FIELD_NUMBER; - hash = (53 * hash) + type_; - } - if (hasCompression()) { - hash = (37 * hash) + COMPRESSION_FIELD_NUMBER; - hash = (53 * hash) + compression_; - } - if (hasBody()) { - hash = (37 * hash) + BODY_FIELD_NUMBER; - hash = (53 * hash) + getBody().hashCode(); + switch (compressionPresentCase_) { + case 4: + hash = (37 * hash) + COMPRESSION_FIELD_NUMBER; + hash = (53 * hash) + getCompressionValue(); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -823,16 +968,16 @@ public final class CanalPacket { @java.lang.Override public Builder clear() { super.clear(); - magicNumber_ = 17; - bitField0_ = (bitField0_ & ~0x00000001); - version_ = 1; - bitField0_ = (bitField0_ & ~0x00000002); - type_ = 1; - bitField0_ = (bitField0_ & ~0x00000004); - compression_ = 1; - bitField0_ = (bitField0_ & ~0x00000008); + type_ = 0; + body_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000010); + + magicNumberPresentCase_ = 0; + magicNumberPresent_ = null; + versionPresentCase_ = 0; + versionPresent_ = null; + compressionPresentCase_ = 0; + compressionPresent_ = null; return this; } @@ -859,29 +1004,20 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.Packet buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.Packet result = new com.alibaba.otter.canal.protocol.CanalPacket.Packet(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; + if (magicNumberPresentCase_ == 1) { + result.magicNumberPresent_ = magicNumberPresent_; } - result.magicNumber_ = magicNumber_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.version_ = version_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; + if (versionPresentCase_ == 2) { + result.versionPresent_ = versionPresent_; } result.type_ = type_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.compression_ = compression_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; + if (compressionPresentCase_ == 4) { + result.compressionPresent_ = compressionPresent_; } result.body_ = body_; - result.bitField0_ = to_bitField0_; + result.magicNumberPresentCase_ = magicNumberPresentCase_; + result.versionPresentCase_ = versionPresentCase_; + result.compressionPresentCase_ = compressionPresentCase_; onBuilt(); return result; } @@ -930,21 +1066,39 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Packet other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.Packet.getDefaultInstance()) return this; - if (other.hasMagicNumber()) { - setMagicNumber(other.getMagicNumber()); + if (other.type_ != 0) { + setTypeValue(other.getTypeValue()); } - if (other.hasVersion()) { - setVersion(other.getVersion()); - } - if (other.hasType()) { - setType(other.getType()); - } - if (other.hasCompression()) { - setCompression(other.getCompression()); - } - if (other.hasBody()) { + if (other.getBody() != com.google.protobuf.ByteString.EMPTY) { setBody(other.getBody()); } + switch (other.getMagicNumberPresentCase()) { + case MAGIC_NUMBER: { + setMagicNumber(other.getMagicNumber()); + break; + } + case MAGICNUMBERPRESENT_NOT_SET: { + break; + } + } + switch (other.getVersionPresentCase()) { + case VERSION: { + setVersion(other.getVersion()); + break; + } + case VERSIONPRESENT_NOT_SET: { + break; + } + } + switch (other.getCompressionPresentCase()) { + case COMPRESSION: { + setCompressionValue(other.getCompressionValue()); + break; + } + case COMPRESSIONPRESENT_NOT_SET: { + break; + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -973,176 +1127,235 @@ public final class CanalPacket { } return this; } - private int bitField0_; - - private int magicNumber_ = 17; - /** - * optional int32 magic_number = 1 [default = 17]; - */ - public boolean hasMagicNumber() { - return ((bitField0_ & 0x00000001) == 0x00000001); + private int magicNumberPresentCase_ = 0; + private java.lang.Object magicNumberPresent_; + public MagicNumberPresentCase + getMagicNumberPresentCase() { + return MagicNumberPresentCase.forNumber( + magicNumberPresentCase_); } + + public Builder clearMagicNumberPresent() { + magicNumberPresentCase_ = 0; + magicNumberPresent_ = null; + onChanged(); + return this; + } + + private int versionPresentCase_ = 0; + private java.lang.Object versionPresent_; + public VersionPresentCase + getVersionPresentCase() { + return VersionPresentCase.forNumber( + versionPresentCase_); + } + + public Builder clearVersionPresent() { + versionPresentCase_ = 0; + versionPresent_ = null; + onChanged(); + return this; + } + + private int compressionPresentCase_ = 0; + private java.lang.Object compressionPresent_; + public CompressionPresentCase + getCompressionPresentCase() { + return CompressionPresentCase.forNumber( + compressionPresentCase_); + } + + public Builder clearCompressionPresent() { + compressionPresentCase_ = 0; + compressionPresent_ = null; + onChanged(); + return this; + } + + /** - * optional int32 magic_number = 1 [default = 17]; + * int32 magic_number = 1; */ public int getMagicNumber() { - return magicNumber_; + if (magicNumberPresentCase_ == 1) { + return (java.lang.Integer) magicNumberPresent_; + } + return 17; } /** - * optional int32 magic_number = 1 [default = 17]; + * int32 magic_number = 1; */ public Builder setMagicNumber(int value) { - bitField0_ |= 0x00000001; - magicNumber_ = value; + magicNumberPresentCase_ = 1; + magicNumberPresent_ = value; onChanged(); return this; } /** - * optional int32 magic_number = 1 [default = 17]; + * int32 magic_number = 1; */ public Builder clearMagicNumber() { - bitField0_ = (bitField0_ & ~0x00000001); - magicNumber_ = 17; - onChanged(); + if (magicNumberPresentCase_ == 1) { + magicNumberPresentCase_ = 0; + magicNumberPresent_ = null; + onChanged(); + } return this; } - private int version_ = 1; /** - * optional int32 version = 2 [default = 1]; - */ - public boolean hasVersion() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional int32 version = 2 [default = 1]; + * int32 version = 2; */ public int getVersion() { - return version_; + if (versionPresentCase_ == 2) { + return (java.lang.Integer) versionPresent_; + } + return 0; } /** - * optional int32 version = 2 [default = 1]; + * int32 version = 2; */ public Builder setVersion(int value) { - bitField0_ |= 0x00000002; - version_ = value; + versionPresentCase_ = 2; + versionPresent_ = value; onChanged(); return this; } /** - * optional int32 version = 2 [default = 1]; + * int32 version = 2; */ public Builder clearVersion() { - bitField0_ = (bitField0_ & ~0x00000002); - version_ = 1; - onChanged(); + if (versionPresentCase_ == 2) { + versionPresentCase_ = 0; + versionPresent_ = null; + onChanged(); + } return this; } - private int type_ = 1; + private int type_ = 0; /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ - public boolean hasType() { - return ((bitField0_ & 0x00000004) == 0x00000004); + public int getTypeValue() { + return type_; } /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; + */ + public Builder setTypeValue(int value) { + type_ = value; + onChanged(); + return this; + } + /** + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ public com.alibaba.otter.canal.protocol.CanalPacket.PacketType getType() { @SuppressWarnings("deprecation") com.alibaba.otter.canal.protocol.CanalPacket.PacketType result = com.alibaba.otter.canal.protocol.CanalPacket.PacketType.valueOf(type_); - return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.PacketType.HANDSHAKE : result; + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.PacketType.UNRECOGNIZED : result; } /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ public Builder setType(com.alibaba.otter.canal.protocol.CanalPacket.PacketType value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + type_ = value.getNumber(); onChanged(); return this; } /** - * optional .com.alibaba.otter.canal.protocol.PacketType type = 3; + * .com.alibaba.otter.canal.protocol.PacketType type = 3; */ public Builder clearType() { - bitField0_ = (bitField0_ & ~0x00000004); - type_ = 1; + + type_ = 0; onChanged(); return this; } - private int compression_ = 1; /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ - public boolean hasCompression() { - return ((bitField0_ & 0x00000008) == 0x00000008); + public int getCompressionValue() { + if (compressionPresentCase_ == 4) { + return ((java.lang.Integer) compressionPresent_).intValue(); + } + return 0; } /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; + */ + public Builder setCompressionValue(int value) { + compressionPresentCase_ = 4; + compressionPresent_ = value; + onChanged(); + return this; + } + /** + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ public com.alibaba.otter.canal.protocol.CanalPacket.Compression getCompression() { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(compression_); - return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE : result; + if (compressionPresentCase_ == 4) { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf( + (java.lang.Integer) compressionPresent_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.UNRECOGNIZED : result; + } + return com.alibaba.otter.canal.protocol.CanalPacket.Compression.COMPRESSIONCOMPATIBLEPROTO2; } /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ public Builder setCompression(com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; - compression_ = value.getNumber(); + compressionPresentCase_ = 4; + compressionPresent_ = value.getNumber(); onChanged(); return this; } /** - * optional .com.alibaba.otter.canal.protocol.Compression compression = 4 [default = NONE]; + * .com.alibaba.otter.canal.protocol.Compression compression = 4; */ public Builder clearCompression() { - bitField0_ = (bitField0_ & ~0x00000008); - compression_ = 1; - onChanged(); + if (compressionPresentCase_ == 4) { + compressionPresentCase_ = 0; + compressionPresent_ = null; + onChanged(); + } return this; } private com.google.protobuf.ByteString body_ = com.google.protobuf.ByteString.EMPTY; /** - * optional bytes body = 5; - */ - public boolean hasBody() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional bytes body = 5; + * bytes body = 5; */ public com.google.protobuf.ByteString getBody() { return body_; } /** - * optional bytes body = 5; + * bytes body = 5; */ public Builder setBody(com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000010; + body_ = value; onChanged(); return this; } /** - * optional bytes body = 5; + * bytes body = 5; */ public Builder clearBody() { - bitField0_ = (bitField0_ & ~0x00000010); + body_ = getDefaultInstance().getBody(); onChanged(); return this; @@ -1150,7 +1363,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -1173,7 +1386,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Packet parsePartialFrom( @@ -1205,20 +1418,12 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional int64 send_timestamp = 1; - */ - boolean hasSendTimestamp(); - /** - * optional int64 send_timestamp = 1; + * int64 send_timestamp = 1; */ long getSendTimestamp(); /** - * optional int64 start_timestamp = 2; - */ - boolean hasStartTimestamp(); - /** - * optional int64 start_timestamp = 2; + * int64 start_timestamp = 2; */ long getStartTimestamp(); } @@ -1264,17 +1469,17 @@ public final class CanalPacket { done = true; break; case 8: { - bitField0_ |= 0x00000001; + sendTimestamp_ = input.readInt64(); break; } case 16: { - bitField0_ |= 0x00000002; + startTimestamp_ = input.readInt64(); break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -1305,17 +1510,10 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.class, com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.Builder.class); } - private int bitField0_; public static final int SEND_TIMESTAMP_FIELD_NUMBER = 1; private long sendTimestamp_; /** - * optional int64 send_timestamp = 1; - */ - public boolean hasSendTimestamp() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional int64 send_timestamp = 1; + * int64 send_timestamp = 1; */ public long getSendTimestamp() { return sendTimestamp_; @@ -1324,13 +1522,7 @@ public final class CanalPacket { public static final int START_TIMESTAMP_FIELD_NUMBER = 2; private long startTimestamp_; /** - * optional int64 start_timestamp = 2; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional int64 start_timestamp = 2; + * int64 start_timestamp = 2; */ public long getStartTimestamp() { return startTimestamp_; @@ -1350,10 +1542,10 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (sendTimestamp_ != 0L) { output.writeInt64(1, sendTimestamp_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (startTimestamp_ != 0L) { output.writeInt64(2, startTimestamp_); } unknownFields.writeTo(output); @@ -1365,11 +1557,11 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (sendTimestamp_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(1, sendTimestamp_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (startTimestamp_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(2, startTimestamp_); } @@ -1389,16 +1581,10 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat other = (com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat) obj; boolean result = true; - result = result && (hasSendTimestamp() == other.hasSendTimestamp()); - if (hasSendTimestamp()) { - result = result && (getSendTimestamp() - == other.getSendTimestamp()); - } - result = result && (hasStartTimestamp() == other.hasStartTimestamp()); - if (hasStartTimestamp()) { - result = result && (getStartTimestamp() - == other.getStartTimestamp()); - } + result = result && (getSendTimestamp() + == other.getSendTimestamp()); + result = result && (getStartTimestamp() + == other.getStartTimestamp()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -1410,16 +1596,12 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasSendTimestamp()) { - hash = (37 * hash) + SEND_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getSendTimestamp()); - } - if (hasStartTimestamp()) { - hash = (37 * hash) + START_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getStartTimestamp()); - } + hash = (37 * hash) + SEND_TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getSendTimestamp()); + hash = (37 * hash) + START_TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getStartTimestamp()); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -1554,9 +1736,9 @@ public final class CanalPacket { public Builder clear() { super.clear(); sendTimestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); + startTimestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000002); + return this; } @@ -1583,17 +1765,8 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat result = new com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.sendTimestamp_ = sendTimestamp_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.startTimestamp_ = startTimestamp_; - result.bitField0_ = to_bitField0_; onBuilt(); return result; } @@ -1642,10 +1815,10 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.HeartBeat.getDefaultInstance()) return this; - if (other.hasSendTimestamp()) { + if (other.getSendTimestamp() != 0L) { setSendTimestamp(other.getSendTimestamp()); } - if (other.hasStartTimestamp()) { + if (other.getStartTimestamp() != 0L) { setStartTimestamp(other.getStartTimestamp()); } this.mergeUnknownFields(other.unknownFields); @@ -1676,35 +1849,28 @@ public final class CanalPacket { } return this; } - private int bitField0_; private long sendTimestamp_ ; /** - * optional int64 send_timestamp = 1; - */ - public boolean hasSendTimestamp() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional int64 send_timestamp = 1; + * int64 send_timestamp = 1; */ public long getSendTimestamp() { return sendTimestamp_; } /** - * optional int64 send_timestamp = 1; + * int64 send_timestamp = 1; */ public Builder setSendTimestamp(long value) { - bitField0_ |= 0x00000001; + sendTimestamp_ = value; onChanged(); return this; } /** - * optional int64 send_timestamp = 1; + * int64 send_timestamp = 1; */ public Builder clearSendTimestamp() { - bitField0_ = (bitField0_ & ~0x00000001); + sendTimestamp_ = 0L; onChanged(); return this; @@ -1712,31 +1878,25 @@ public final class CanalPacket { private long startTimestamp_ ; /** - * optional int64 start_timestamp = 2; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional int64 start_timestamp = 2; + * int64 start_timestamp = 2; */ public long getStartTimestamp() { return startTimestamp_; } /** - * optional int64 start_timestamp = 2; + * int64 start_timestamp = 2; */ public Builder setStartTimestamp(long value) { - bitField0_ |= 0x00000002; + startTimestamp_ = value; onChanged(); return this; } /** - * optional int64 start_timestamp = 2; + * int64 start_timestamp = 2; */ public Builder clearStartTimestamp() { - bitField0_ = (bitField0_ & ~0x00000002); + startTimestamp_ = 0L; onChanged(); return this; @@ -1744,7 +1904,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -1767,7 +1927,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public HeartBeat parsePartialFrom( @@ -1799,40 +1959,30 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - boolean hasCommunicationEncoding(); - /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ java.lang.String getCommunicationEncoding(); /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ com.google.protobuf.ByteString getCommunicationEncodingBytes(); /** - * optional bytes seeds = 2; - */ - boolean hasSeeds(); - /** - * optional bytes seeds = 2; + * bytes seeds = 2; */ com.google.protobuf.ByteString getSeeds(); /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ - java.util.List getSupportedCompressionsList(); + int getSupportedCompressionsValue(); /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ - int getSupportedCompressionsCount(); - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index); + com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Handshake.CommunicationEncodingPresentCase getCommunicationEncodingPresentCase(); } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Handshake} @@ -1847,9 +1997,8 @@ public final class CanalPacket { super(builder); } private Handshake() { - communicationEncoding_ = "utf8"; seeds_ = com.google.protobuf.ByteString.EMPTY; - supportedCompressions_ = java.util.Collections.emptyList(); + supportedCompressions_ = 0; } @java.lang.Override @@ -1877,53 +2026,24 @@ public final class CanalPacket { done = true; break; case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - communicationEncoding_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + communicationEncodingPresentCase_ = 1; + communicationEncodingPresent_ = s; break; } case 18: { - bitField0_ |= 0x00000002; + seeds_ = input.readBytes(); break; } case 24: { int rawValue = input.readEnum(); - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(3, rawValue); - } else { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - supportedCompressions_.add(rawValue); - } - break; - } - case 26: { - int length = input.readRawVarint32(); - int oldLimit = input.pushLimit(length); - while(input.getBytesUntilLimit() > 0) { - int rawValue = input.readEnum(); - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalPacket.Compression value = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(3, rawValue); - } else { - if (!((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000004; - } - supportedCompressions_.add(rawValue); - } - } - input.popLimit(oldLimit); + + supportedCompressions_ = rawValue; break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -1937,9 +2057,6 @@ public final class CanalPacket { throw new com.google.protobuf.InvalidProtocolBufferException( e).setUnfinishedMessage(this); } finally { - if (((mutable_bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = java.util.Collections.unmodifiableList(supportedCompressions_); - } this.unknownFields = unknownFields.build(); makeExtensionsImmutable(); } @@ -1957,43 +2074,79 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Handshake.class, com.alibaba.otter.canal.protocol.CanalPacket.Handshake.Builder.class); } - private int bitField0_; - public static final int COMMUNICATION_ENCODING_FIELD_NUMBER = 1; - private volatile java.lang.Object communicationEncoding_; - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public boolean hasCommunicationEncoding() { - return ((bitField0_ & 0x00000001) == 0x00000001); + private int communicationEncodingPresentCase_ = 0; + private java.lang.Object communicationEncodingPresent_; + public enum CommunicationEncodingPresentCase + implements com.google.protobuf.Internal.EnumLite { + COMMUNICATION_ENCODING(1), + COMMUNICATIONENCODINGPRESENT_NOT_SET(0); + private final int value; + private CommunicationEncodingPresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static CommunicationEncodingPresentCase valueOf(int value) { + return forNumber(value); + } + + public static CommunicationEncodingPresentCase forNumber(int value) { + switch (value) { + case 1: return COMMUNICATION_ENCODING; + case 0: return COMMUNICATIONENCODINGPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public CommunicationEncodingPresentCase + getCommunicationEncodingPresentCase() { + return CommunicationEncodingPresentCase.forNumber( + communicationEncodingPresentCase_); } + + public static final int COMMUNICATION_ENCODING_FIELD_NUMBER = 1; /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ public java.lang.String getCommunicationEncoding() { - java.lang.Object ref = communicationEncoding_; + java.lang.Object ref = ""; + if (communicationEncodingPresentCase_ == 1) { + ref = communicationEncodingPresent_; + } if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - communicationEncoding_ = s; + if (communicationEncodingPresentCase_ == 1) { + communicationEncodingPresent_ = s; } return s; } } /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ public com.google.protobuf.ByteString getCommunicationEncodingBytes() { - java.lang.Object ref = communicationEncoding_; + java.lang.Object ref = ""; + if (communicationEncodingPresentCase_ == 1) { + ref = communicationEncodingPresent_; + } if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - communicationEncoding_ = b; + if (communicationEncodingPresentCase_ == 1) { + communicationEncodingPresent_ = b; + } return b; } else { return (com.google.protobuf.ByteString) ref; @@ -2003,48 +2156,27 @@ public final class CanalPacket { public static final int SEEDS_FIELD_NUMBER = 2; private com.google.protobuf.ByteString seeds_; /** - * optional bytes seeds = 2; - */ - public boolean hasSeeds() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional bytes seeds = 2; + * bytes seeds = 2; */ public com.google.protobuf.ByteString getSeeds() { return seeds_; } public static final int SUPPORTED_COMPRESSIONS_FIELD_NUMBER = 3; - private java.util.List supportedCompressions_; - private static final com.google.protobuf.Internal.ListAdapter.Converter< - java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression> supportedCompressions_converter_ = - new com.google.protobuf.Internal.ListAdapter.Converter< - java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression>() { - public com.alibaba.otter.canal.protocol.CanalPacket.Compression convert(java.lang.Integer from) { - @SuppressWarnings("deprecation") - com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(from); - return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.NONE : result; - } - }; + private int supportedCompressions_; /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ - public java.util.List getSupportedCompressionsList() { - return new com.google.protobuf.Internal.ListAdapter< - java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression>(supportedCompressions_, supportedCompressions_converter_); + public int getSupportedCompressionsValue() { + return supportedCompressions_; } /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ - public int getSupportedCompressionsCount() { - return supportedCompressions_.size(); - } - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index) { - return supportedCompressions_converter_.convert(supportedCompressions_.get(index)); + public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(supportedCompressions_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.UNRECOGNIZED : result; } private byte memoizedIsInitialized = -1; @@ -2061,14 +2193,14 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { - com.google.protobuf.GeneratedMessageV3.writeString(output, 1, communicationEncoding_); + if (communicationEncodingPresentCase_ == 1) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, communicationEncodingPresent_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!seeds_.isEmpty()) { output.writeBytes(2, seeds_); } - for (int i = 0; i < supportedCompressions_.size(); i++) { - output.writeEnum(3, supportedCompressions_.get(i)); + if (supportedCompressions_ != com.alibaba.otter.canal.protocol.CanalPacket.Compression.COMPRESSIONCOMPATIBLEPROTO2.getNumber()) { + output.writeEnum(3, supportedCompressions_); } unknownFields.writeTo(output); } @@ -2079,21 +2211,16 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, communicationEncoding_); + if (communicationEncodingPresentCase_ == 1) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, communicationEncodingPresent_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!seeds_.isEmpty()) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(2, seeds_); } - { - int dataSize = 0; - for (int i = 0; i < supportedCompressions_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeEnumSizeNoTag(supportedCompressions_.get(i)); - } - size += dataSize; - size += 1 * supportedCompressions_.size(); + if (supportedCompressions_ != com.alibaba.otter.canal.protocol.CanalPacket.Compression.COMPRESSIONCOMPATIBLEPROTO2.getNumber()) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(3, supportedCompressions_); } size += unknownFields.getSerializedSize(); memoizedSize = size; @@ -2111,17 +2238,20 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Handshake other = (com.alibaba.otter.canal.protocol.CanalPacket.Handshake) obj; boolean result = true; - result = result && (hasCommunicationEncoding() == other.hasCommunicationEncoding()); - if (hasCommunicationEncoding()) { - result = result && getCommunicationEncoding() - .equals(other.getCommunicationEncoding()); + result = result && getSeeds() + .equals(other.getSeeds()); + result = result && supportedCompressions_ == other.supportedCompressions_; + result = result && getCommunicationEncodingPresentCase().equals( + other.getCommunicationEncodingPresentCase()); + if (!result) return false; + switch (communicationEncodingPresentCase_) { + case 1: + result = result && getCommunicationEncoding() + .equals(other.getCommunicationEncoding()); + break; + case 0: + default: } - result = result && (hasSeeds() == other.hasSeeds()); - if (hasSeeds()) { - result = result && getSeeds() - .equals(other.getSeeds()); - } - result = result && supportedCompressions_.equals(other.supportedCompressions_); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -2133,17 +2263,17 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasCommunicationEncoding()) { - hash = (37 * hash) + COMMUNICATION_ENCODING_FIELD_NUMBER; - hash = (53 * hash) + getCommunicationEncoding().hashCode(); - } - if (hasSeeds()) { - hash = (37 * hash) + SEEDS_FIELD_NUMBER; - hash = (53 * hash) + getSeeds().hashCode(); - } - if (getSupportedCompressionsCount() > 0) { - hash = (37 * hash) + SUPPORTED_COMPRESSIONS_FIELD_NUMBER; - hash = (53 * hash) + supportedCompressions_.hashCode(); + hash = (37 * hash) + SEEDS_FIELD_NUMBER; + hash = (53 * hash) + getSeeds().hashCode(); + hash = (37 * hash) + SUPPORTED_COMPRESSIONS_FIELD_NUMBER; + hash = (53 * hash) + supportedCompressions_; + switch (communicationEncodingPresentCase_) { + case 1: + hash = (37 * hash) + COMMUNICATION_ENCODING_FIELD_NUMBER; + hash = (53 * hash) + getCommunicationEncoding().hashCode(); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -2278,12 +2408,12 @@ public final class CanalPacket { @java.lang.Override public Builder clear() { super.clear(); - communicationEncoding_ = "utf8"; - bitField0_ = (bitField0_ & ~0x00000001); seeds_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000002); - supportedCompressions_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); + + supportedCompressions_ = 0; + + communicationEncodingPresentCase_ = 0; + communicationEncodingPresent_ = null; return this; } @@ -2310,22 +2440,12 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.Handshake buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.Handshake result = new com.alibaba.otter.canal.protocol.CanalPacket.Handshake(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.communicationEncoding_ = communicationEncoding_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; + if (communicationEncodingPresentCase_ == 1) { + result.communicationEncodingPresent_ = communicationEncodingPresent_; } result.seeds_ = seeds_; - if (((bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = java.util.Collections.unmodifiableList(supportedCompressions_); - bitField0_ = (bitField0_ & ~0x00000004); - } result.supportedCompressions_ = supportedCompressions_; - result.bitField0_ = to_bitField0_; + result.communicationEncodingPresentCase_ = communicationEncodingPresentCase_; onBuilt(); return result; } @@ -2374,23 +2494,22 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Handshake other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.Handshake.getDefaultInstance()) return this; - if (other.hasCommunicationEncoding()) { - bitField0_ |= 0x00000001; - communicationEncoding_ = other.communicationEncoding_; - onChanged(); - } - if (other.hasSeeds()) { + if (other.getSeeds() != com.google.protobuf.ByteString.EMPTY) { setSeeds(other.getSeeds()); } - if (!other.supportedCompressions_.isEmpty()) { - if (supportedCompressions_.isEmpty()) { - supportedCompressions_ = other.supportedCompressions_; - bitField0_ = (bitField0_ & ~0x00000004); - } else { - ensureSupportedCompressionsIsMutable(); - supportedCompressions_.addAll(other.supportedCompressions_); + if (other.supportedCompressions_ != 0) { + setSupportedCompressionsValue(other.getSupportedCompressionsValue()); + } + switch (other.getCommunicationEncodingPresentCase()) { + case COMMUNICATION_ENCODING: { + communicationEncodingPresentCase_ = 1; + communicationEncodingPresent_ = other.communicationEncodingPresent_; + onChanged(); + break; + } + case COMMUNICATIONENCODINGPRESENT_NOT_SET: { + break; } - onChanged(); } this.mergeUnknownFields(other.unknownFields); onChanged(); @@ -2420,26 +2539,36 @@ public final class CanalPacket { } return this; } - private int bitField0_; - - private java.lang.Object communicationEncoding_ = "utf8"; - /** - * optional string communication_encoding = 1 [default = "utf8"]; - */ - public boolean hasCommunicationEncoding() { - return ((bitField0_ & 0x00000001) == 0x00000001); + private int communicationEncodingPresentCase_ = 0; + private java.lang.Object communicationEncodingPresent_; + public CommunicationEncodingPresentCase + getCommunicationEncodingPresentCase() { + return CommunicationEncodingPresentCase.forNumber( + communicationEncodingPresentCase_); } + + public Builder clearCommunicationEncodingPresent() { + communicationEncodingPresentCase_ = 0; + communicationEncodingPresent_ = null; + onChanged(); + return this; + } + + /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ public java.lang.String getCommunicationEncoding() { - java.lang.Object ref = communicationEncoding_; + java.lang.Object ref = ""; + if (communicationEncodingPresentCase_ == 1) { + ref = communicationEncodingPresent_; + } if (!(ref instanceof java.lang.String)) { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - communicationEncoding_ = s; + if (communicationEncodingPresentCase_ == 1) { + communicationEncodingPresent_ = s; } return s; } else { @@ -2447,169 +2576,142 @@ public final class CanalPacket { } } /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ public com.google.protobuf.ByteString getCommunicationEncodingBytes() { - java.lang.Object ref = communicationEncoding_; + java.lang.Object ref = ""; + if (communicationEncodingPresentCase_ == 1) { + ref = communicationEncodingPresent_; + } if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - communicationEncoding_ = b; + if (communicationEncodingPresentCase_ == 1) { + communicationEncodingPresent_ = b; + } return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ public Builder setCommunicationEncoding( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; - communicationEncoding_ = value; + communicationEncodingPresentCase_ = 1; + communicationEncodingPresent_ = value; onChanged(); return this; } /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ public Builder clearCommunicationEncoding() { - bitField0_ = (bitField0_ & ~0x00000001); - communicationEncoding_ = getDefaultInstance().getCommunicationEncoding(); - onChanged(); + if (communicationEncodingPresentCase_ == 1) { + communicationEncodingPresentCase_ = 0; + communicationEncodingPresent_ = null; + onChanged(); + } return this; } /** - * optional string communication_encoding = 1 [default = "utf8"]; + * string communication_encoding = 1; */ public Builder setCommunicationEncodingBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; - communicationEncoding_ = value; + checkByteStringIsUtf8(value); + communicationEncodingPresentCase_ = 1; + communicationEncodingPresent_ = value; onChanged(); return this; } private com.google.protobuf.ByteString seeds_ = com.google.protobuf.ByteString.EMPTY; /** - * optional bytes seeds = 2; - */ - public boolean hasSeeds() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional bytes seeds = 2; + * bytes seeds = 2; */ public com.google.protobuf.ByteString getSeeds() { return seeds_; } /** - * optional bytes seeds = 2; + * bytes seeds = 2; */ public Builder setSeeds(com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + seeds_ = value; onChanged(); return this; } /** - * optional bytes seeds = 2; + * bytes seeds = 2; */ public Builder clearSeeds() { - bitField0_ = (bitField0_ & ~0x00000002); + seeds_ = getDefaultInstance().getSeeds(); onChanged(); return this; } - private java.util.List supportedCompressions_ = - java.util.Collections.emptyList(); - private void ensureSupportedCompressionsIsMutable() { - if (!((bitField0_ & 0x00000004) == 0x00000004)) { - supportedCompressions_ = new java.util.ArrayList(supportedCompressions_); - bitField0_ |= 0x00000004; - } + private int supportedCompressions_ = 0; + /** + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + */ + public int getSupportedCompressionsValue() { + return supportedCompressions_; } /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ - public java.util.List getSupportedCompressionsList() { - return new com.google.protobuf.Internal.ListAdapter< - java.lang.Integer, com.alibaba.otter.canal.protocol.CanalPacket.Compression>(supportedCompressions_, supportedCompressions_converter_); + public Builder setSupportedCompressionsValue(int value) { + supportedCompressions_ = value; + onChanged(); + return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ - public int getSupportedCompressionsCount() { - return supportedCompressions_.size(); + public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions() { + @SuppressWarnings("deprecation") + com.alibaba.otter.canal.protocol.CanalPacket.Compression result = com.alibaba.otter.canal.protocol.CanalPacket.Compression.valueOf(supportedCompressions_); + return result == null ? com.alibaba.otter.canal.protocol.CanalPacket.Compression.UNRECOGNIZED : result; } /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ - public com.alibaba.otter.canal.protocol.CanalPacket.Compression getSupportedCompressions(int index) { - return supportedCompressions_converter_.convert(supportedCompressions_.get(index)); - } - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public Builder setSupportedCompressions( - int index, com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { + public Builder setSupportedCompressions(com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { if (value == null) { throw new NullPointerException(); } - ensureSupportedCompressionsIsMutable(); - supportedCompressions_.set(index, value.getNumber()); + + supportedCompressions_ = value.getNumber(); onChanged(); return this; } /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public Builder addSupportedCompressions(com.alibaba.otter.canal.protocol.CanalPacket.Compression value) { - if (value == null) { - throw new NullPointerException(); - } - ensureSupportedCompressionsIsMutable(); - supportedCompressions_.add(value.getNumber()); - onChanged(); - return this; - } - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; - */ - public Builder addAllSupportedCompressions( - java.lang.Iterable values) { - ensureSupportedCompressionsIsMutable(); - for (com.alibaba.otter.canal.protocol.CanalPacket.Compression value : values) { - supportedCompressions_.add(value.getNumber()); - } - onChanged(); - return this; - } - /** - * repeated .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; + * .com.alibaba.otter.canal.protocol.Compression supported_compressions = 3; */ public Builder clearSupportedCompressions() { - supportedCompressions_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000004); + + supportedCompressions_ = 0; onChanged(); return this; } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -2632,7 +2734,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Handshake parsePartialFrom( @@ -2664,15 +2766,11 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional string username = 1; - */ - boolean hasUsername(); - /** - * optional string username = 1; + * string username = 1; */ java.lang.String getUsername(); /** - * optional string username = 1; + * string username = 1; */ com.google.protobuf.ByteString getUsernameBytes(); @@ -2682,15 +2780,7 @@ public final class CanalPacket { * hashed password with seeds from Handshake message * * - * optional bytes password = 2; - */ - boolean hasPassword(); - /** - *
      -     * hashed password with seeds from Handshake message
      -     * 
      - * - * optional bytes password = 2; + * bytes password = 2; */ com.google.protobuf.ByteString getPassword(); @@ -2699,15 +2789,7 @@ public final class CanalPacket { * in seconds * * - * optional int32 net_read_timeout = 3 [default = 0]; - */ - boolean hasNetReadTimeout(); - /** - *
      -     * in seconds
      -     * 
      - * - * optional int32 net_read_timeout = 3 [default = 0]; + * int32 net_read_timeout = 3; */ int getNetReadTimeout(); @@ -2716,68 +2798,48 @@ public final class CanalPacket { * in seconds * * - * optional int32 net_write_timeout = 4 [default = 0]; - */ - boolean hasNetWriteTimeout(); - /** - *
      -     * in seconds
      -     * 
      - * - * optional int32 net_write_timeout = 4 [default = 0]; + * int32 net_write_timeout = 4; */ int getNetWriteTimeout(); /** - * optional string destination = 5; - */ - boolean hasDestination(); - /** - * optional string destination = 5; + * string destination = 5; */ java.lang.String getDestination(); /** - * optional string destination = 5; + * string destination = 5; */ com.google.protobuf.ByteString getDestinationBytes(); /** - * optional string client_id = 6; - */ - boolean hasClientId(); - /** - * optional string client_id = 6; + * string client_id = 6; */ java.lang.String getClientId(); /** - * optional string client_id = 6; + * string client_id = 6; */ com.google.protobuf.ByteString getClientIdBytes(); /** - * optional string filter = 7; - */ - boolean hasFilter(); - /** - * optional string filter = 7; + * string filter = 7; */ java.lang.String getFilter(); /** - * optional string filter = 7; + * string filter = 7; */ com.google.protobuf.ByteString getFilterBytes(); /** - * optional int64 start_timestamp = 8; - */ - boolean hasStartTimestamp(); - /** - * optional int64 start_timestamp = 8; + * int64 start_timestamp = 8; */ long getStartTimestamp(); + + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.NetReadTimeoutPresentCase getNetReadTimeoutPresentCase(); + + public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.NetWriteTimeoutPresentCase getNetWriteTimeoutPresentCase(); } /** *
      @@ -2798,8 +2860,6 @@ public final class CanalPacket {
           private ClientAuth() {
             username_ = "";
             password_ = com.google.protobuf.ByteString.EMPTY;
      -      netReadTimeout_ = 0;
      -      netWriteTimeout_ = 0;
             destination_ = "";
             clientId_ = "";
             filter_ = "";
      @@ -2831,51 +2891,51 @@ public final class CanalPacket {
                     done = true;
                     break;
                   case 10: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000001;
      -              username_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              username_ = s;
                     break;
                   }
                   case 18: {
      -              bitField0_ |= 0x00000002;
      +
                     password_ = input.readBytes();
                     break;
                   }
                   case 24: {
      -              bitField0_ |= 0x00000004;
      -              netReadTimeout_ = input.readInt32();
      +              netReadTimeoutPresentCase_ = 3;
      +              netReadTimeoutPresent_ = input.readInt32();
                     break;
                   }
                   case 32: {
      -              bitField0_ |= 0x00000008;
      -              netWriteTimeout_ = input.readInt32();
      +              netWriteTimeoutPresentCase_ = 4;
      +              netWriteTimeoutPresent_ = input.readInt32();
                     break;
                   }
                   case 42: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000010;
      -              destination_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              destination_ = s;
                     break;
                   }
                   case 50: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000020;
      -              clientId_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              clientId_ = s;
                     break;
                   }
                   case 58: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000040;
      -              filter_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              filter_ = s;
                     break;
                   }
                   case 64: {
      -              bitField0_ |= 0x00000080;
      +
                     startTimestamp_ = input.readInt64();
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -2906,17 +2966,82 @@ public final class CanalPacket {
                     com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.Builder.class);
           }
       
      -    private int bitField0_;
      +    private int netReadTimeoutPresentCase_ = 0;
      +    private java.lang.Object netReadTimeoutPresent_;
      +    public enum NetReadTimeoutPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      NET_READ_TIMEOUT(3),
      +      NETREADTIMEOUTPRESENT_NOT_SET(0);
      +      private final int value;
      +      private NetReadTimeoutPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static NetReadTimeoutPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static NetReadTimeoutPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 3: return NET_READ_TIMEOUT;
      +          case 0: return NETREADTIMEOUTPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public NetReadTimeoutPresentCase
      +    getNetReadTimeoutPresentCase() {
      +      return NetReadTimeoutPresentCase.forNumber(
      +          netReadTimeoutPresentCase_);
      +    }
      +
      +    private int netWriteTimeoutPresentCase_ = 0;
      +    private java.lang.Object netWriteTimeoutPresent_;
      +    public enum NetWriteTimeoutPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      NET_WRITE_TIMEOUT(4),
      +      NETWRITETIMEOUTPRESENT_NOT_SET(0);
      +      private final int value;
      +      private NetWriteTimeoutPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static NetWriteTimeoutPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static NetWriteTimeoutPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 4: return NET_WRITE_TIMEOUT;
      +          case 0: return NETWRITETIMEOUTPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public NetWriteTimeoutPresentCase
      +    getNetWriteTimeoutPresentCase() {
      +      return NetWriteTimeoutPresentCase.forNumber(
      +          netWriteTimeoutPresentCase_);
      +    }
      +
           public static final int USERNAME_FIELD_NUMBER = 1;
           private volatile java.lang.Object username_;
           /**
      -     * optional string username = 1;
      -     */
      -    public boolean hasUsername() {
      -      return ((bitField0_ & 0x00000001) == 0x00000001);
      -    }
      -    /**
      -     * optional string username = 1;
      +     * string username = 1;
            */
           public java.lang.String getUsername() {
             java.lang.Object ref = username_;
      @@ -2926,14 +3051,12 @@ public final class CanalPacket {
               com.google.protobuf.ByteString bs = 
                   (com.google.protobuf.ByteString) ref;
               java.lang.String s = bs.toStringUtf8();
      -        if (bs.isValidUtf8()) {
      -          username_ = s;
      -        }
      +        username_ = s;
               return s;
             }
           }
           /**
      -     * optional string username = 1;
      +     * string username = 1;
            */
           public com.google.protobuf.ByteString
               getUsernameBytes() {
      @@ -2956,78 +3079,46 @@ public final class CanalPacket {
            * hashed password with seeds from Handshake message
            * 
      * - * optional bytes password = 2; - */ - public boolean hasPassword() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -     * hashed password with seeds from Handshake message
      -     * 
      - * - * optional bytes password = 2; + * bytes password = 2; */ public com.google.protobuf.ByteString getPassword() { return password_; } public static final int NET_READ_TIMEOUT_FIELD_NUMBER = 3; - private int netReadTimeout_; /** *
            * in seconds
            * 
      * - * optional int32 net_read_timeout = 3 [default = 0]; - */ - public boolean hasNetReadTimeout() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -     * in seconds
      -     * 
      - * - * optional int32 net_read_timeout = 3 [default = 0]; + * int32 net_read_timeout = 3; */ public int getNetReadTimeout() { - return netReadTimeout_; + if (netReadTimeoutPresentCase_ == 3) { + return (java.lang.Integer) netReadTimeoutPresent_; + } + return 0; } public static final int NET_WRITE_TIMEOUT_FIELD_NUMBER = 4; - private int netWriteTimeout_; /** *
            * in seconds
            * 
      * - * optional int32 net_write_timeout = 4 [default = 0]; - */ - public boolean hasNetWriteTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -     * in seconds
      -     * 
      - * - * optional int32 net_write_timeout = 4 [default = 0]; + * int32 net_write_timeout = 4; */ public int getNetWriteTimeout() { - return netWriteTimeout_; + if (netWriteTimeoutPresentCase_ == 4) { + return (java.lang.Integer) netWriteTimeoutPresent_; + } + return 0; } public static final int DESTINATION_FIELD_NUMBER = 5; private volatile java.lang.Object destination_; /** - * optional string destination = 5; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional string destination = 5; + * string destination = 5; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -3037,14 +3128,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } } /** - * optional string destination = 5; + * string destination = 5; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -3063,13 +3152,7 @@ public final class CanalPacket { public static final int CLIENT_ID_FIELD_NUMBER = 6; private volatile java.lang.Object clientId_; /** - * optional string client_id = 6; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - * optional string client_id = 6; + * string client_id = 6; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -3079,14 +3162,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } } /** - * optional string client_id = 6; + * string client_id = 6; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -3105,13 +3186,7 @@ public final class CanalPacket { public static final int FILTER_FIELD_NUMBER = 7; private volatile java.lang.Object filter_; /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - /** - * optional string filter = 7; + * string filter = 7; */ public java.lang.String getFilter() { java.lang.Object ref = filter_; @@ -3121,14 +3196,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } + filter_ = s; return s; } } /** - * optional string filter = 7; + * string filter = 7; */ public com.google.protobuf.ByteString getFilterBytes() { @@ -3147,13 +3220,7 @@ public final class CanalPacket { public static final int START_TIMESTAMP_FIELD_NUMBER = 8; private long startTimestamp_; /** - * optional int64 start_timestamp = 8; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - * optional int64 start_timestamp = 8; + * int64 start_timestamp = 8; */ public long getStartTimestamp() { return startTimestamp_; @@ -3173,28 +3240,30 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getUsernameBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, username_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!password_.isEmpty()) { output.writeBytes(2, password_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeInt32(3, netReadTimeout_); + if (netReadTimeoutPresentCase_ == 3) { + output.writeInt32( + 3, (int)((java.lang.Integer) netReadTimeoutPresent_)); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeInt32(4, netWriteTimeout_); + if (netWriteTimeoutPresentCase_ == 4) { + output.writeInt32( + 4, (int)((java.lang.Integer) netWriteTimeoutPresent_)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!getDestinationBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 5, destination_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (!getClientIdBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 6, clientId_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (!getFilterBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 7, filter_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (startTimestamp_ != 0L) { output.writeInt64(8, startTimestamp_); } unknownFields.writeTo(output); @@ -3206,31 +3275,33 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getUsernameBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, username_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!password_.isEmpty()) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(2, password_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (netReadTimeoutPresentCase_ == 3) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(3, netReadTimeout_); + .computeInt32Size( + 3, (int)((java.lang.Integer) netReadTimeoutPresent_)); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (netWriteTimeoutPresentCase_ == 4) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(4, netWriteTimeout_); + .computeInt32Size( + 4, (int)((java.lang.Integer) netWriteTimeoutPresent_)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (!getDestinationBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(5, destination_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (!getClientIdBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(6, clientId_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (!getFilterBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, filter_); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (startTimestamp_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(8, startTimestamp_); } @@ -3250,45 +3321,39 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth other = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth) obj; boolean result = true; - result = result && (hasUsername() == other.hasUsername()); - if (hasUsername()) { - result = result && getUsername() - .equals(other.getUsername()); + result = result && getUsername() + .equals(other.getUsername()); + result = result && getPassword() + .equals(other.getPassword()); + result = result && getDestination() + .equals(other.getDestination()); + result = result && getClientId() + .equals(other.getClientId()); + result = result && getFilter() + .equals(other.getFilter()); + result = result && (getStartTimestamp() + == other.getStartTimestamp()); + result = result && getNetReadTimeoutPresentCase().equals( + other.getNetReadTimeoutPresentCase()); + if (!result) return false; + switch (netReadTimeoutPresentCase_) { + case 3: + result = result && (getNetReadTimeout() + == other.getNetReadTimeout()); + break; + case 0: + default: } - result = result && (hasPassword() == other.hasPassword()); - if (hasPassword()) { - result = result && getPassword() - .equals(other.getPassword()); - } - result = result && (hasNetReadTimeout() == other.hasNetReadTimeout()); - if (hasNetReadTimeout()) { - result = result && (getNetReadTimeout() - == other.getNetReadTimeout()); - } - result = result && (hasNetWriteTimeout() == other.hasNetWriteTimeout()); - if (hasNetWriteTimeout()) { - result = result && (getNetWriteTimeout() - == other.getNetWriteTimeout()); - } - result = result && (hasDestination() == other.hasDestination()); - if (hasDestination()) { - result = result && getDestination() - .equals(other.getDestination()); - } - result = result && (hasClientId() == other.hasClientId()); - if (hasClientId()) { - result = result && getClientId() - .equals(other.getClientId()); - } - result = result && (hasFilter() == other.hasFilter()); - if (hasFilter()) { - result = result && getFilter() - .equals(other.getFilter()); - } - result = result && (hasStartTimestamp() == other.hasStartTimestamp()); - if (hasStartTimestamp()) { - result = result && (getStartTimestamp() - == other.getStartTimestamp()); + result = result && getNetWriteTimeoutPresentCase().equals( + other.getNetWriteTimeoutPresentCase()); + if (!result) return false; + switch (netWriteTimeoutPresentCase_) { + case 4: + result = result && (getNetWriteTimeout() + == other.getNetWriteTimeout()); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -3301,38 +3366,34 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasUsername()) { - hash = (37 * hash) + USERNAME_FIELD_NUMBER; - hash = (53 * hash) + getUsername().hashCode(); + hash = (37 * hash) + USERNAME_FIELD_NUMBER; + hash = (53 * hash) + getUsername().hashCode(); + hash = (37 * hash) + PASSWORD_FIELD_NUMBER; + hash = (53 * hash) + getPassword().hashCode(); + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + hash = (37 * hash) + FILTER_FIELD_NUMBER; + hash = (53 * hash) + getFilter().hashCode(); + hash = (37 * hash) + START_TIMESTAMP_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getStartTimestamp()); + switch (netReadTimeoutPresentCase_) { + case 3: + hash = (37 * hash) + NET_READ_TIMEOUT_FIELD_NUMBER; + hash = (53 * hash) + getNetReadTimeout(); + break; + case 0: + default: } - if (hasPassword()) { - hash = (37 * hash) + PASSWORD_FIELD_NUMBER; - hash = (53 * hash) + getPassword().hashCode(); - } - if (hasNetReadTimeout()) { - hash = (37 * hash) + NET_READ_TIMEOUT_FIELD_NUMBER; - hash = (53 * hash) + getNetReadTimeout(); - } - if (hasNetWriteTimeout()) { - hash = (37 * hash) + NET_WRITE_TIMEOUT_FIELD_NUMBER; - hash = (53 * hash) + getNetWriteTimeout(); - } - if (hasDestination()) { - hash = (37 * hash) + DESTINATION_FIELD_NUMBER; - hash = (53 * hash) + getDestination().hashCode(); - } - if (hasClientId()) { - hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; - hash = (53 * hash) + getClientId().hashCode(); - } - if (hasFilter()) { - hash = (37 * hash) + FILTER_FIELD_NUMBER; - hash = (53 * hash) + getFilter().hashCode(); - } - if (hasStartTimestamp()) { - hash = (37 * hash) + START_TIMESTAMP_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getStartTimestamp()); + switch (netWriteTimeoutPresentCase_) { + case 4: + hash = (37 * hash) + NET_WRITE_TIMEOUT_FIELD_NUMBER; + hash = (53 * hash) + getNetWriteTimeout(); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -3472,21 +3533,21 @@ public final class CanalPacket { public Builder clear() { super.clear(); username_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); + password_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000002); - netReadTimeout_ = 0; - bitField0_ = (bitField0_ & ~0x00000004); - netWriteTimeout_ = 0; - bitField0_ = (bitField0_ & ~0x00000008); + destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000010); + clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000020); + filter_ = ""; - bitField0_ = (bitField0_ & ~0x00000040); + startTimestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000080); + + netReadTimeoutPresentCase_ = 0; + netReadTimeoutPresent_ = null; + netWriteTimeoutPresentCase_ = 0; + netWriteTimeoutPresent_ = null; return this; } @@ -3513,41 +3574,20 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.username_ = username_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.password_ = password_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; + if (netReadTimeoutPresentCase_ == 3) { + result.netReadTimeoutPresent_ = netReadTimeoutPresent_; } - result.netReadTimeout_ = netReadTimeout_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; - } - result.netWriteTimeout_ = netWriteTimeout_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; + if (netWriteTimeoutPresentCase_ == 4) { + result.netWriteTimeoutPresent_ = netWriteTimeoutPresent_; } result.destination_ = destination_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; - } result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000040) == 0x00000040)) { - to_bitField0_ |= 0x00000040; - } result.filter_ = filter_; - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000080; - } result.startTimestamp_ = startTimestamp_; - result.bitField0_ = to_bitField0_; + result.netReadTimeoutPresentCase_ = netReadTimeoutPresentCase_; + result.netWriteTimeoutPresentCase_ = netWriteTimeoutPresentCase_; onBuilt(); return result; } @@ -3596,38 +3636,46 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientAuth.getDefaultInstance()) return this; - if (other.hasUsername()) { - bitField0_ |= 0x00000001; + if (!other.getUsername().isEmpty()) { username_ = other.username_; onChanged(); } - if (other.hasPassword()) { + if (other.getPassword() != com.google.protobuf.ByteString.EMPTY) { setPassword(other.getPassword()); } - if (other.hasNetReadTimeout()) { - setNetReadTimeout(other.getNetReadTimeout()); - } - if (other.hasNetWriteTimeout()) { - setNetWriteTimeout(other.getNetWriteTimeout()); - } - if (other.hasDestination()) { - bitField0_ |= 0x00000010; + if (!other.getDestination().isEmpty()) { destination_ = other.destination_; onChanged(); } - if (other.hasClientId()) { - bitField0_ |= 0x00000020; + if (!other.getClientId().isEmpty()) { clientId_ = other.clientId_; onChanged(); } - if (other.hasFilter()) { - bitField0_ |= 0x00000040; + if (!other.getFilter().isEmpty()) { filter_ = other.filter_; onChanged(); } - if (other.hasStartTimestamp()) { + if (other.getStartTimestamp() != 0L) { setStartTimestamp(other.getStartTimestamp()); } + switch (other.getNetReadTimeoutPresentCase()) { + case NET_READ_TIMEOUT: { + setNetReadTimeout(other.getNetReadTimeout()); + break; + } + case NETREADTIMEOUTPRESENT_NOT_SET: { + break; + } + } + switch (other.getNetWriteTimeoutPresentCase()) { + case NET_WRITE_TIMEOUT: { + setNetWriteTimeout(other.getNetWriteTimeout()); + break; + } + case NETWRITETIMEOUTPRESENT_NOT_SET: { + break; + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -3656,17 +3704,40 @@ public final class CanalPacket { } return this; } - private int bitField0_; + private int netReadTimeoutPresentCase_ = 0; + private java.lang.Object netReadTimeoutPresent_; + public NetReadTimeoutPresentCase + getNetReadTimeoutPresentCase() { + return NetReadTimeoutPresentCase.forNumber( + netReadTimeoutPresentCase_); + } + + public Builder clearNetReadTimeoutPresent() { + netReadTimeoutPresentCase_ = 0; + netReadTimeoutPresent_ = null; + onChanged(); + return this; + } + + private int netWriteTimeoutPresentCase_ = 0; + private java.lang.Object netWriteTimeoutPresent_; + public NetWriteTimeoutPresentCase + getNetWriteTimeoutPresentCase() { + return NetWriteTimeoutPresentCase.forNumber( + netWriteTimeoutPresentCase_); + } + + public Builder clearNetWriteTimeoutPresent() { + netWriteTimeoutPresentCase_ = 0; + netWriteTimeoutPresent_ = null; + onChanged(); + return this; + } + private java.lang.Object username_ = ""; /** - * optional string username = 1; - */ - public boolean hasUsername() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string username = 1; + * string username = 1; */ public java.lang.String getUsername() { java.lang.Object ref = username_; @@ -3674,16 +3745,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - username_ = s; - } + username_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string username = 1; + * string username = 1; */ public com.google.protobuf.ByteString getUsernameBytes() { @@ -3699,36 +3768,37 @@ public final class CanalPacket { } } /** - * optional string username = 1; + * string username = 1; */ public Builder setUsername( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + username_ = value; onChanged(); return this; } /** - * optional string username = 1; + * string username = 1; */ public Builder clearUsername() { - bitField0_ = (bitField0_ & ~0x00000001); + username_ = getDefaultInstance().getUsername(); onChanged(); return this; } /** - * optional string username = 1; + * string username = 1; */ public Builder setUsernameBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + checkByteStringIsUtf8(value); + username_ = value; onChanged(); return this; @@ -3740,17 +3810,7 @@ public final class CanalPacket { * hashed password with seeds from Handshake message * * - * optional bytes password = 2; - */ - public boolean hasPassword() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -       * hashed password with seeds from Handshake message
      -       * 
      - * - * optional bytes password = 2; + * bytes password = 2; */ public com.google.protobuf.ByteString getPassword() { return password_; @@ -3760,13 +3820,13 @@ public final class CanalPacket { * hashed password with seeds from Handshake message * * - * optional bytes password = 2; + * bytes password = 2; */ public Builder setPassword(com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + password_ = value; onChanged(); return this; @@ -3776,46 +3836,38 @@ public final class CanalPacket { * hashed password with seeds from Handshake message * * - * optional bytes password = 2; + * bytes password = 2; */ public Builder clearPassword() { - bitField0_ = (bitField0_ & ~0x00000002); + password_ = getDefaultInstance().getPassword(); onChanged(); return this; } - private int netReadTimeout_ ; /** *
              * in seconds
              * 
      * - * optional int32 net_read_timeout = 3 [default = 0]; - */ - public boolean hasNetReadTimeout() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - *
      -       * in seconds
      -       * 
      - * - * optional int32 net_read_timeout = 3 [default = 0]; + * int32 net_read_timeout = 3; */ public int getNetReadTimeout() { - return netReadTimeout_; + if (netReadTimeoutPresentCase_ == 3) { + return (java.lang.Integer) netReadTimeoutPresent_; + } + return 0; } /** *
              * in seconds
              * 
      * - * optional int32 net_read_timeout = 3 [default = 0]; + * int32 net_read_timeout = 3; */ public Builder setNetReadTimeout(int value) { - bitField0_ |= 0x00000004; - netReadTimeout_ = value; + netReadTimeoutPresentCase_ = 3; + netReadTimeoutPresent_ = value; onChanged(); return this; } @@ -3824,46 +3876,40 @@ public final class CanalPacket { * in seconds * * - * optional int32 net_read_timeout = 3 [default = 0]; + * int32 net_read_timeout = 3; */ public Builder clearNetReadTimeout() { - bitField0_ = (bitField0_ & ~0x00000004); - netReadTimeout_ = 0; - onChanged(); + if (netReadTimeoutPresentCase_ == 3) { + netReadTimeoutPresentCase_ = 0; + netReadTimeoutPresent_ = null; + onChanged(); + } return this; } - private int netWriteTimeout_ ; /** *
              * in seconds
              * 
      * - * optional int32 net_write_timeout = 4 [default = 0]; - */ - public boolean hasNetWriteTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -       * in seconds
      -       * 
      - * - * optional int32 net_write_timeout = 4 [default = 0]; + * int32 net_write_timeout = 4; */ public int getNetWriteTimeout() { - return netWriteTimeout_; + if (netWriteTimeoutPresentCase_ == 4) { + return (java.lang.Integer) netWriteTimeoutPresent_; + } + return 0; } /** *
              * in seconds
              * 
      * - * optional int32 net_write_timeout = 4 [default = 0]; + * int32 net_write_timeout = 4; */ public Builder setNetWriteTimeout(int value) { - bitField0_ |= 0x00000008; - netWriteTimeout_ = value; + netWriteTimeoutPresentCase_ = 4; + netWriteTimeoutPresent_ = value; onChanged(); return this; } @@ -3872,24 +3918,20 @@ public final class CanalPacket { * in seconds * * - * optional int32 net_write_timeout = 4 [default = 0]; + * int32 net_write_timeout = 4; */ public Builder clearNetWriteTimeout() { - bitField0_ = (bitField0_ & ~0x00000008); - netWriteTimeout_ = 0; - onChanged(); + if (netWriteTimeoutPresentCase_ == 4) { + netWriteTimeoutPresentCase_ = 0; + netWriteTimeoutPresent_ = null; + onChanged(); + } return this; } private java.lang.Object destination_ = ""; /** - * optional string destination = 5; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional string destination = 5; + * string destination = 5; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -3897,16 +3939,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string destination = 5; + * string destination = 5; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -3922,36 +3962,37 @@ public final class CanalPacket { } } /** - * optional string destination = 5; + * string destination = 5; */ public Builder setDestination( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000010; + destination_ = value; onChanged(); return this; } /** - * optional string destination = 5; + * string destination = 5; */ public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000010); + destination_ = getDefaultInstance().getDestination(); onChanged(); return this; } /** - * optional string destination = 5; + * string destination = 5; */ public Builder setDestinationBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000010; + checkByteStringIsUtf8(value); + destination_ = value; onChanged(); return this; @@ -3959,13 +4000,7 @@ public final class CanalPacket { private java.lang.Object clientId_ = ""; /** - * optional string client_id = 6; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - * optional string client_id = 6; + * string client_id = 6; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -3973,16 +4008,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string client_id = 6; + * string client_id = 6; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -3998,36 +4031,37 @@ public final class CanalPacket { } } /** - * optional string client_id = 6; + * string client_id = 6; */ public Builder setClientId( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000020; + clientId_ = value; onChanged(); return this; } /** - * optional string client_id = 6; + * string client_id = 6; */ public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000020); + clientId_ = getDefaultInstance().getClientId(); onChanged(); return this; } /** - * optional string client_id = 6; + * string client_id = 6; */ public Builder setClientIdBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000020; + checkByteStringIsUtf8(value); + clientId_ = value; onChanged(); return this; @@ -4035,13 +4069,7 @@ public final class CanalPacket { private java.lang.Object filter_ = ""; /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000040) == 0x00000040); - } - /** - * optional string filter = 7; + * string filter = 7; */ public java.lang.String getFilter() { java.lang.Object ref = filter_; @@ -4049,16 +4077,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } + filter_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string filter = 7; + * string filter = 7; */ public com.google.protobuf.ByteString getFilterBytes() { @@ -4074,36 +4100,37 @@ public final class CanalPacket { } } /** - * optional string filter = 7; + * string filter = 7; */ public Builder setFilter( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000040; + filter_ = value; onChanged(); return this; } /** - * optional string filter = 7; + * string filter = 7; */ public Builder clearFilter() { - bitField0_ = (bitField0_ & ~0x00000040); + filter_ = getDefaultInstance().getFilter(); onChanged(); return this; } /** - * optional string filter = 7; + * string filter = 7; */ public Builder setFilterBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000040; + checkByteStringIsUtf8(value); + filter_ = value; onChanged(); return this; @@ -4111,31 +4138,25 @@ public final class CanalPacket { private long startTimestamp_ ; /** - * optional int64 start_timestamp = 8; - */ - public boolean hasStartTimestamp() { - return ((bitField0_ & 0x00000080) == 0x00000080); - } - /** - * optional int64 start_timestamp = 8; + * int64 start_timestamp = 8; */ public long getStartTimestamp() { return startTimestamp_; } /** - * optional int64 start_timestamp = 8; + * int64 start_timestamp = 8; */ public Builder setStartTimestamp(long value) { - bitField0_ |= 0x00000080; + startTimestamp_ = value; onChanged(); return this; } /** - * optional int64 start_timestamp = 8; + * int64 start_timestamp = 8; */ public Builder clearStartTimestamp() { - bitField0_ = (bitField0_ & ~0x00000080); + startTimestamp_ = 0L; onChanged(); return this; @@ -4143,7 +4164,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -4166,7 +4187,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public ClientAuth parsePartialFrom( @@ -4198,11 +4219,7 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional int32 error_code = 1 [default = 0]; - */ - boolean hasErrorCode(); - /** - * optional int32 error_code = 1 [default = 0]; + * int32 error_code = 1; */ int getErrorCode(); @@ -4211,15 +4228,7 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; - */ - boolean hasErrorMessage(); - /** - *
      -     * if something like compression is not supported, erorr_message will tell about it.
      -     * 
      - * - * optional string error_message = 2; + * string error_message = 2; */ java.lang.String getErrorMessage(); /** @@ -4227,10 +4236,12 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; + * string error_message = 2; */ com.google.protobuf.ByteString getErrorMessageBytes(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Ack.ErrorCodePresentCase getErrorCodePresentCase(); } /** * Protobuf type {@code com.alibaba.otter.canal.protocol.Ack} @@ -4245,7 +4256,6 @@ public final class CanalPacket { super(builder); } private Ack() { - errorCode_ = 0; errorMessage_ = ""; } @@ -4274,18 +4284,18 @@ public final class CanalPacket { done = true; break; case 8: { - bitField0_ |= 0x00000001; - errorCode_ = input.readInt32(); + errorCodePresentCase_ = 1; + errorCodePresent_ = input.readInt32(); break; } case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - errorMessage_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + errorMessage_ = s; break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -4316,20 +4326,51 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Ack.class, com.alibaba.otter.canal.protocol.CanalPacket.Ack.Builder.class); } - private int bitField0_; - public static final int ERROR_CODE_FIELD_NUMBER = 1; - private int errorCode_; - /** - * optional int32 error_code = 1 [default = 0]; - */ - public boolean hasErrorCode() { - return ((bitField0_ & 0x00000001) == 0x00000001); + private int errorCodePresentCase_ = 0; + private java.lang.Object errorCodePresent_; + public enum ErrorCodePresentCase + implements com.google.protobuf.Internal.EnumLite { + ERROR_CODE(1), + ERRORCODEPRESENT_NOT_SET(0); + private final int value; + private ErrorCodePresentCase(int value) { + this.value = value; + } + /** + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static ErrorCodePresentCase valueOf(int value) { + return forNumber(value); + } + + public static ErrorCodePresentCase forNumber(int value) { + switch (value) { + case 1: return ERROR_CODE; + case 0: return ERRORCODEPRESENT_NOT_SET; + default: return null; + } + } + public int getNumber() { + return this.value; + } + }; + + public ErrorCodePresentCase + getErrorCodePresentCase() { + return ErrorCodePresentCase.forNumber( + errorCodePresentCase_); } + + public static final int ERROR_CODE_FIELD_NUMBER = 1; /** - * optional int32 error_code = 1 [default = 0]; + * int32 error_code = 1; */ public int getErrorCode() { - return errorCode_; + if (errorCodePresentCase_ == 1) { + return (java.lang.Integer) errorCodePresent_; + } + return 0; } public static final int ERROR_MESSAGE_FIELD_NUMBER = 2; @@ -4339,17 +4380,7 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; - */ - public boolean hasErrorMessage() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -     * if something like compression is not supported, erorr_message will tell about it.
      -     * 
      - * - * optional string error_message = 2; + * string error_message = 2; */ public java.lang.String getErrorMessage() { java.lang.Object ref = errorMessage_; @@ -4359,9 +4390,7 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - errorMessage_ = s; - } + errorMessage_ = s; return s; } } @@ -4370,7 +4399,7 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; + * string error_message = 2; */ public com.google.protobuf.ByteString getErrorMessageBytes() { @@ -4400,10 +4429,11 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeInt32(1, errorCode_); + if (errorCodePresentCase_ == 1) { + output.writeInt32( + 1, (int)((java.lang.Integer) errorCodePresent_)); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getErrorMessageBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, errorMessage_); } unknownFields.writeTo(output); @@ -4415,11 +4445,12 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (errorCodePresentCase_ == 1) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(1, errorCode_); + .computeInt32Size( + 1, (int)((java.lang.Integer) errorCodePresent_)); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getErrorMessageBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, errorMessage_); } size += unknownFields.getSerializedSize(); @@ -4438,15 +4469,18 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Ack other = (com.alibaba.otter.canal.protocol.CanalPacket.Ack) obj; boolean result = true; - result = result && (hasErrorCode() == other.hasErrorCode()); - if (hasErrorCode()) { - result = result && (getErrorCode() - == other.getErrorCode()); - } - result = result && (hasErrorMessage() == other.hasErrorMessage()); - if (hasErrorMessage()) { - result = result && getErrorMessage() - .equals(other.getErrorMessage()); + result = result && getErrorMessage() + .equals(other.getErrorMessage()); + result = result && getErrorCodePresentCase().equals( + other.getErrorCodePresentCase()); + if (!result) return false; + switch (errorCodePresentCase_) { + case 1: + result = result && (getErrorCode() + == other.getErrorCode()); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -4459,13 +4493,15 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasErrorCode()) { - hash = (37 * hash) + ERROR_CODE_FIELD_NUMBER; - hash = (53 * hash) + getErrorCode(); - } - if (hasErrorMessage()) { - hash = (37 * hash) + ERROR_MESSAGE_FIELD_NUMBER; - hash = (53 * hash) + getErrorMessage().hashCode(); + hash = (37 * hash) + ERROR_MESSAGE_FIELD_NUMBER; + hash = (53 * hash) + getErrorMessage().hashCode(); + switch (errorCodePresentCase_) { + case 1: + hash = (37 * hash) + ERROR_CODE_FIELD_NUMBER; + hash = (53 * hash) + getErrorCode(); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -4600,10 +4636,10 @@ public final class CanalPacket { @java.lang.Override public Builder clear() { super.clear(); - errorCode_ = 0; - bitField0_ = (bitField0_ & ~0x00000001); errorMessage_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + + errorCodePresentCase_ = 0; + errorCodePresent_ = null; return this; } @@ -4630,17 +4666,11 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.Ack buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.Ack result = new com.alibaba.otter.canal.protocol.CanalPacket.Ack(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.errorCode_ = errorCode_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; + if (errorCodePresentCase_ == 1) { + result.errorCodePresent_ = errorCodePresent_; } result.errorMessage_ = errorMessage_; - result.bitField0_ = to_bitField0_; + result.errorCodePresentCase_ = errorCodePresentCase_; onBuilt(); return result; } @@ -4689,14 +4719,19 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Ack other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.Ack.getDefaultInstance()) return this; - if (other.hasErrorCode()) { - setErrorCode(other.getErrorCode()); - } - if (other.hasErrorMessage()) { - bitField0_ |= 0x00000002; + if (!other.getErrorMessage().isEmpty()) { errorMessage_ = other.errorMessage_; onChanged(); } + switch (other.getErrorCodePresentCase()) { + case ERROR_CODE: { + setErrorCode(other.getErrorCode()); + break; + } + case ERRORCODEPRESENT_NOT_SET: { + break; + } + } this.mergeUnknownFields(other.unknownFields); onChanged(); return this; @@ -4725,37 +4760,49 @@ public final class CanalPacket { } return this; } - private int bitField0_; - - private int errorCode_ ; - /** - * optional int32 error_code = 1 [default = 0]; - */ - public boolean hasErrorCode() { - return ((bitField0_ & 0x00000001) == 0x00000001); + private int errorCodePresentCase_ = 0; + private java.lang.Object errorCodePresent_; + public ErrorCodePresentCase + getErrorCodePresentCase() { + return ErrorCodePresentCase.forNumber( + errorCodePresentCase_); } + + public Builder clearErrorCodePresent() { + errorCodePresentCase_ = 0; + errorCodePresent_ = null; + onChanged(); + return this; + } + + /** - * optional int32 error_code = 1 [default = 0]; + * int32 error_code = 1; */ public int getErrorCode() { - return errorCode_; + if (errorCodePresentCase_ == 1) { + return (java.lang.Integer) errorCodePresent_; + } + return 0; } /** - * optional int32 error_code = 1 [default = 0]; + * int32 error_code = 1; */ public Builder setErrorCode(int value) { - bitField0_ |= 0x00000001; - errorCode_ = value; + errorCodePresentCase_ = 1; + errorCodePresent_ = value; onChanged(); return this; } /** - * optional int32 error_code = 1 [default = 0]; + * int32 error_code = 1; */ public Builder clearErrorCode() { - bitField0_ = (bitField0_ & ~0x00000001); - errorCode_ = 0; - onChanged(); + if (errorCodePresentCase_ == 1) { + errorCodePresentCase_ = 0; + errorCodePresent_ = null; + onChanged(); + } return this; } @@ -4765,17 +4812,7 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; - */ - public boolean hasErrorMessage() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - *
      -       * if something like compression is not supported, erorr_message will tell about it.
      -       * 
      - * - * optional string error_message = 2; + * string error_message = 2; */ public java.lang.String getErrorMessage() { java.lang.Object ref = errorMessage_; @@ -4783,9 +4820,7 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - errorMessage_ = s; - } + errorMessage_ = s; return s; } else { return (java.lang.String) ref; @@ -4796,7 +4831,7 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; + * string error_message = 2; */ public com.google.protobuf.ByteString getErrorMessageBytes() { @@ -4816,14 +4851,14 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; + * string error_message = 2; */ public Builder setErrorMessage( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + errorMessage_ = value; onChanged(); return this; @@ -4833,10 +4868,10 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; + * string error_message = 2; */ public Builder clearErrorMessage() { - bitField0_ = (bitField0_ & ~0x00000002); + errorMessage_ = getDefaultInstance().getErrorMessage(); onChanged(); return this; @@ -4846,14 +4881,15 @@ public final class CanalPacket { * if something like compression is not supported, erorr_message will tell about it. * * - * optional string error_message = 2; + * string error_message = 2; */ public Builder setErrorMessageBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + errorMessage_ = value; onChanged(); return this; @@ -4861,7 +4897,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -4884,7 +4920,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Ack parsePartialFrom( @@ -4916,39 +4952,27 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional string destination = 1; - */ - boolean hasDestination(); - /** - * optional string destination = 1; + * string destination = 1; */ java.lang.String getDestination(); /** - * optional string destination = 1; + * string destination = 1; */ com.google.protobuf.ByteString getDestinationBytes(); /** - * optional string client_id = 2; - */ - boolean hasClientId(); - /** - * optional string client_id = 2; + * string client_id = 2; */ java.lang.String getClientId(); /** - * optional string client_id = 2; + * string client_id = 2; */ com.google.protobuf.ByteString getClientIdBytes(); /** - * optional int64 batch_id = 3; - */ - boolean hasBatchId(); - /** - * optional int64 batch_id = 3; + * int64 batch_id = 3; */ long getBatchId(); } @@ -4995,24 +5019,24 @@ public final class CanalPacket { done = true; break; case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + destination_ = s; break; } case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + clientId_ = s; break; } case 24: { - bitField0_ |= 0x00000004; + batchId_ = input.readInt64(); break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -5043,17 +5067,10 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.Builder.class); } - private int bitField0_; public static final int DESTINATION_FIELD_NUMBER = 1; private volatile java.lang.Object destination_; /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string destination = 1; + * string destination = 1; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -5063,14 +5080,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } } /** - * optional string destination = 1; + * string destination = 1; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -5089,13 +5104,7 @@ public final class CanalPacket { public static final int CLIENT_ID_FIELD_NUMBER = 2; private volatile java.lang.Object clientId_; /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string client_id = 2; + * string client_id = 2; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -5105,14 +5114,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } } /** - * optional string client_id = 2; + * string client_id = 2; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -5131,13 +5138,7 @@ public final class CanalPacket { public static final int BATCH_ID_FIELD_NUMBER = 3; private long batchId_; /** - * optional int64 batch_id = 3; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional int64 batch_id = 3; + * int64 batch_id = 3; */ public long getBatchId() { return batchId_; @@ -5157,13 +5158,13 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (batchId_ != 0L) { output.writeInt64(3, batchId_); } unknownFields.writeTo(output); @@ -5175,13 +5176,13 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (batchId_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(3, batchId_); } @@ -5201,21 +5202,12 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.ClientAck other = (com.alibaba.otter.canal.protocol.CanalPacket.ClientAck) obj; boolean result = true; - result = result && (hasDestination() == other.hasDestination()); - if (hasDestination()) { - result = result && getDestination() - .equals(other.getDestination()); - } - result = result && (hasClientId() == other.hasClientId()); - if (hasClientId()) { - result = result && getClientId() - .equals(other.getClientId()); - } - result = result && (hasBatchId() == other.hasBatchId()); - if (hasBatchId()) { - result = result && (getBatchId() - == other.getBatchId()); - } + result = result && getDestination() + .equals(other.getDestination()); + result = result && getClientId() + .equals(other.getClientId()); + result = result && (getBatchId() + == other.getBatchId()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -5227,19 +5219,13 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasDestination()) { - hash = (37 * hash) + DESTINATION_FIELD_NUMBER; - hash = (53 * hash) + getDestination().hashCode(); - } - if (hasClientId()) { - hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; - hash = (53 * hash) + getClientId().hashCode(); - } - if (hasBatchId()) { - hash = (37 * hash) + BATCH_ID_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getBatchId()); - } + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + hash = (37 * hash) + BATCH_ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getBatchId()); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -5374,11 +5360,11 @@ public final class CanalPacket { public Builder clear() { super.clear(); destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + batchId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000004); + return this; } @@ -5405,21 +5391,9 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.ClientAck buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.ClientAck result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientAck(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } result.batchId_ = batchId_; - result.bitField0_ = to_bitField0_; onBuilt(); return result; } @@ -5468,17 +5442,15 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientAck other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientAck.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; + if (!other.getDestination().isEmpty()) { destination_ = other.destination_; onChanged(); } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; + if (!other.getClientId().isEmpty()) { clientId_ = other.clientId_; onChanged(); } - if (other.hasBatchId()) { + if (other.getBatchId() != 0L) { setBatchId(other.getBatchId()); } this.mergeUnknownFields(other.unknownFields); @@ -5509,17 +5481,10 @@ public final class CanalPacket { } return this; } - private int bitField0_; private java.lang.Object destination_ = ""; /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string destination = 1; + * string destination = 1; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -5527,16 +5492,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string destination = 1; + * string destination = 1; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -5552,36 +5515,37 @@ public final class CanalPacket { } } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestination( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + destination_ = value; onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestinationBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + checkByteStringIsUtf8(value); + destination_ = value; onChanged(); return this; @@ -5589,13 +5553,7 @@ public final class CanalPacket { private java.lang.Object clientId_ = ""; /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string client_id = 2; + * string client_id = 2; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -5603,16 +5561,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string client_id = 2; + * string client_id = 2; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -5628,36 +5584,37 @@ public final class CanalPacket { } } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientId( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + clientId_ = value; onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientIdBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + clientId_ = value; onChanged(); return this; @@ -5665,31 +5622,25 @@ public final class CanalPacket { private long batchId_ ; /** - * optional int64 batch_id = 3; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional int64 batch_id = 3; + * int64 batch_id = 3; */ public long getBatchId() { return batchId_; } /** - * optional int64 batch_id = 3; + * int64 batch_id = 3; */ public Builder setBatchId(long value) { - bitField0_ |= 0x00000004; + batchId_ = value; onChanged(); return this; } /** - * optional int64 batch_id = 3; + * int64 batch_id = 3; */ public Builder clearBatchId() { - bitField0_ = (bitField0_ & ~0x00000004); + batchId_ = 0L; onChanged(); return this; @@ -5697,7 +5648,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -5720,7 +5671,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public ClientAck parsePartialFrom( @@ -5752,43 +5703,31 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional string destination = 1; - */ - boolean hasDestination(); - /** - * optional string destination = 1; + * string destination = 1; */ java.lang.String getDestination(); /** - * optional string destination = 1; + * string destination = 1; */ com.google.protobuf.ByteString getDestinationBytes(); /** - * optional string client_id = 2; - */ - boolean hasClientId(); - /** - * optional string client_id = 2; + * string client_id = 2; */ java.lang.String getClientId(); /** - * optional string client_id = 2; + * string client_id = 2; */ com.google.protobuf.ByteString getClientIdBytes(); /** - * optional string filter = 7; - */ - boolean hasFilter(); - /** - * optional string filter = 7; + * string filter = 7; */ java.lang.String getFilter(); /** - * optional string filter = 7; + * string filter = 7; */ com.google.protobuf.ByteString getFilterBytes(); @@ -5840,25 +5779,25 @@ public final class CanalPacket { done = true; break; case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + destination_ = s; break; } case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + clientId_ = s; break; } case 58: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000004; - filter_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + filter_ = s; break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -5889,17 +5828,10 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Sub.class, com.alibaba.otter.canal.protocol.CanalPacket.Sub.Builder.class); } - private int bitField0_; public static final int DESTINATION_FIELD_NUMBER = 1; private volatile java.lang.Object destination_; /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string destination = 1; + * string destination = 1; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -5909,14 +5841,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } } /** - * optional string destination = 1; + * string destination = 1; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -5935,13 +5865,7 @@ public final class CanalPacket { public static final int CLIENT_ID_FIELD_NUMBER = 2; private volatile java.lang.Object clientId_; /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string client_id = 2; + * string client_id = 2; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -5951,14 +5875,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } } /** - * optional string client_id = 2; + * string client_id = 2; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -5977,13 +5899,7 @@ public final class CanalPacket { public static final int FILTER_FIELD_NUMBER = 7; private volatile java.lang.Object filter_; /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string filter = 7; + * string filter = 7; */ public java.lang.String getFilter() { java.lang.Object ref = filter_; @@ -5993,14 +5909,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } + filter_ = s; return s; } } /** - * optional string filter = 7; + * string filter = 7; */ public com.google.protobuf.ByteString getFilterBytes() { @@ -6030,13 +5944,13 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!getFilterBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 7, filter_); } unknownFields.writeTo(output); @@ -6048,13 +5962,13 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!getFilterBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, filter_); } size += unknownFields.getSerializedSize(); @@ -6073,21 +5987,12 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Sub other = (com.alibaba.otter.canal.protocol.CanalPacket.Sub) obj; boolean result = true; - result = result && (hasDestination() == other.hasDestination()); - if (hasDestination()) { - result = result && getDestination() - .equals(other.getDestination()); - } - result = result && (hasClientId() == other.hasClientId()); - if (hasClientId()) { - result = result && getClientId() - .equals(other.getClientId()); - } - result = result && (hasFilter() == other.hasFilter()); - if (hasFilter()) { - result = result && getFilter() - .equals(other.getFilter()); - } + result = result && getDestination() + .equals(other.getDestination()); + result = result && getClientId() + .equals(other.getClientId()); + result = result && getFilter() + .equals(other.getFilter()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -6099,18 +6004,12 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasDestination()) { - hash = (37 * hash) + DESTINATION_FIELD_NUMBER; - hash = (53 * hash) + getDestination().hashCode(); - } - if (hasClientId()) { - hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; - hash = (53 * hash) + getClientId().hashCode(); - } - if (hasFilter()) { - hash = (37 * hash) + FILTER_FIELD_NUMBER; - hash = (53 * hash) + getFilter().hashCode(); - } + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + hash = (37 * hash) + FILTER_FIELD_NUMBER; + hash = (53 * hash) + getFilter().hashCode(); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -6249,11 +6148,11 @@ public final class CanalPacket { public Builder clear() { super.clear(); destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + filter_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); + return this; } @@ -6280,21 +6179,9 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.Sub buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.Sub result = new com.alibaba.otter.canal.protocol.CanalPacket.Sub(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } result.filter_ = filter_; - result.bitField0_ = to_bitField0_; onBuilt(); return result; } @@ -6343,18 +6230,15 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Sub other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.Sub.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; + if (!other.getDestination().isEmpty()) { destination_ = other.destination_; onChanged(); } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; + if (!other.getClientId().isEmpty()) { clientId_ = other.clientId_; onChanged(); } - if (other.hasFilter()) { - bitField0_ |= 0x00000004; + if (!other.getFilter().isEmpty()) { filter_ = other.filter_; onChanged(); } @@ -6386,17 +6270,10 @@ public final class CanalPacket { } return this; } - private int bitField0_; private java.lang.Object destination_ = ""; /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string destination = 1; + * string destination = 1; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -6404,16 +6281,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string destination = 1; + * string destination = 1; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -6429,36 +6304,37 @@ public final class CanalPacket { } } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestination( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + destination_ = value; onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestinationBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + checkByteStringIsUtf8(value); + destination_ = value; onChanged(); return this; @@ -6466,13 +6342,7 @@ public final class CanalPacket { private java.lang.Object clientId_ = ""; /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string client_id = 2; + * string client_id = 2; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -6480,16 +6350,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string client_id = 2; + * string client_id = 2; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -6505,36 +6373,37 @@ public final class CanalPacket { } } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientId( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + clientId_ = value; onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientIdBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + clientId_ = value; onChanged(); return this; @@ -6542,13 +6411,7 @@ public final class CanalPacket { private java.lang.Object filter_ = ""; /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string filter = 7; + * string filter = 7; */ public java.lang.String getFilter() { java.lang.Object ref = filter_; @@ -6556,16 +6419,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } + filter_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string filter = 7; + * string filter = 7; */ public com.google.protobuf.ByteString getFilterBytes() { @@ -6581,36 +6442,37 @@ public final class CanalPacket { } } /** - * optional string filter = 7; + * string filter = 7; */ public Builder setFilter( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + filter_ = value; onChanged(); return this; } /** - * optional string filter = 7; + * string filter = 7; */ public Builder clearFilter() { - bitField0_ = (bitField0_ & ~0x00000004); + filter_ = getDefaultInstance().getFilter(); onChanged(); return this; } /** - * optional string filter = 7; + * string filter = 7; */ public Builder setFilterBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + checkByteStringIsUtf8(value); + filter_ = value; onChanged(); return this; @@ -6618,7 +6480,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -6641,7 +6503,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Sub parsePartialFrom( @@ -6673,43 +6535,31 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional string destination = 1; - */ - boolean hasDestination(); - /** - * optional string destination = 1; + * string destination = 1; */ java.lang.String getDestination(); /** - * optional string destination = 1; + * string destination = 1; */ com.google.protobuf.ByteString getDestinationBytes(); /** - * optional string client_id = 2; - */ - boolean hasClientId(); - /** - * optional string client_id = 2; + * string client_id = 2; */ java.lang.String getClientId(); /** - * optional string client_id = 2; + * string client_id = 2; */ com.google.protobuf.ByteString getClientIdBytes(); /** - * optional string filter = 7; - */ - boolean hasFilter(); - /** - * optional string filter = 7; + * string filter = 7; */ java.lang.String getFilter(); /** - * optional string filter = 7; + * string filter = 7; */ com.google.protobuf.ByteString getFilterBytes(); @@ -6761,25 +6611,25 @@ public final class CanalPacket { done = true; break; case 10: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000001; - destination_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + destination_ = s; break; } case 18: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000002; - clientId_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + clientId_ = s; break; } case 58: { - com.google.protobuf.ByteString bs = input.readBytes(); - bitField0_ |= 0x00000004; - filter_ = bs; + java.lang.String s = input.readStringRequireUtf8(); + + filter_ = s; break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -6810,17 +6660,10 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Unsub.class, com.alibaba.otter.canal.protocol.CanalPacket.Unsub.Builder.class); } - private int bitField0_; public static final int DESTINATION_FIELD_NUMBER = 1; private volatile java.lang.Object destination_; /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string destination = 1; + * string destination = 1; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -6830,14 +6673,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } } /** - * optional string destination = 1; + * string destination = 1; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -6856,13 +6697,7 @@ public final class CanalPacket { public static final int CLIENT_ID_FIELD_NUMBER = 2; private volatile java.lang.Object clientId_; /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string client_id = 2; + * string client_id = 2; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -6872,14 +6707,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } } /** - * optional string client_id = 2; + * string client_id = 2; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -6898,13 +6731,7 @@ public final class CanalPacket { public static final int FILTER_FIELD_NUMBER = 7; private volatile java.lang.Object filter_; /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string filter = 7; + * string filter = 7; */ public java.lang.String getFilter() { java.lang.Object ref = filter_; @@ -6914,14 +6741,12 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } + filter_ = s; return s; } } /** - * optional string filter = 7; + * string filter = 7; */ public com.google.protobuf.ByteString getFilterBytes() { @@ -6951,13 +6776,13 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!getFilterBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 7, filter_); } unknownFields.writeTo(output); @@ -6969,13 +6794,13 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (!getFilterBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(7, filter_); } size += unknownFields.getSerializedSize(); @@ -6994,21 +6819,12 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Unsub other = (com.alibaba.otter.canal.protocol.CanalPacket.Unsub) obj; boolean result = true; - result = result && (hasDestination() == other.hasDestination()); - if (hasDestination()) { - result = result && getDestination() - .equals(other.getDestination()); - } - result = result && (hasClientId() == other.hasClientId()); - if (hasClientId()) { - result = result && getClientId() - .equals(other.getClientId()); - } - result = result && (hasFilter() == other.hasFilter()); - if (hasFilter()) { - result = result && getFilter() - .equals(other.getFilter()); - } + result = result && getDestination() + .equals(other.getDestination()); + result = result && getClientId() + .equals(other.getClientId()); + result = result && getFilter() + .equals(other.getFilter()); result = result && unknownFields.equals(other.unknownFields); return result; } @@ -7020,18 +6836,12 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasDestination()) { - hash = (37 * hash) + DESTINATION_FIELD_NUMBER; - hash = (53 * hash) + getDestination().hashCode(); - } - if (hasClientId()) { - hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; - hash = (53 * hash) + getClientId().hashCode(); - } - if (hasFilter()) { - hash = (37 * hash) + FILTER_FIELD_NUMBER; - hash = (53 * hash) + getFilter().hashCode(); - } + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + hash = (37 * hash) + FILTER_FIELD_NUMBER; + hash = (53 * hash) + getFilter().hashCode(); hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; return hash; @@ -7170,11 +6980,11 @@ public final class CanalPacket { public Builder clear() { super.clear(); destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + filter_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); + return this; } @@ -7201,21 +7011,9 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.Unsub buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.Unsub result = new com.alibaba.otter.canal.protocol.CanalPacket.Unsub(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } result.filter_ = filter_; - result.bitField0_ = to_bitField0_; onBuilt(); return result; } @@ -7264,18 +7062,15 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Unsub other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.Unsub.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; + if (!other.getDestination().isEmpty()) { destination_ = other.destination_; onChanged(); } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; + if (!other.getClientId().isEmpty()) { clientId_ = other.clientId_; onChanged(); } - if (other.hasFilter()) { - bitField0_ |= 0x00000004; + if (!other.getFilter().isEmpty()) { filter_ = other.filter_; onChanged(); } @@ -7307,17 +7102,10 @@ public final class CanalPacket { } return this; } - private int bitField0_; private java.lang.Object destination_ = ""; /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string destination = 1; + * string destination = 1; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -7325,16 +7113,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string destination = 1; + * string destination = 1; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -7350,36 +7136,37 @@ public final class CanalPacket { } } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestination( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + destination_ = value; onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestinationBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + checkByteStringIsUtf8(value); + destination_ = value; onChanged(); return this; @@ -7387,13 +7174,7 @@ public final class CanalPacket { private java.lang.Object clientId_ = ""; /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string client_id = 2; + * string client_id = 2; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -7401,16 +7182,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string client_id = 2; + * string client_id = 2; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -7426,36 +7205,37 @@ public final class CanalPacket { } } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientId( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + clientId_ = value; onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientIdBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + clientId_ = value; onChanged(); return this; @@ -7463,13 +7243,7 @@ public final class CanalPacket { private java.lang.Object filter_ = ""; /** - * optional string filter = 7; - */ - public boolean hasFilter() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string filter = 7; + * string filter = 7; */ public java.lang.String getFilter() { java.lang.Object ref = filter_; @@ -7477,16 +7251,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - filter_ = s; - } + filter_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string filter = 7; + * string filter = 7; */ public com.google.protobuf.ByteString getFilterBytes() { @@ -7502,36 +7274,37 @@ public final class CanalPacket { } } /** - * optional string filter = 7; + * string filter = 7; */ public Builder setFilter( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + filter_ = value; onChanged(); return this; } /** - * optional string filter = 7; + * string filter = 7; */ public Builder clearFilter() { - bitField0_ = (bitField0_ & ~0x00000004); + filter_ = getDefaultInstance().getFilter(); onChanged(); return this; } /** - * optional string filter = 7; + * string filter = 7; */ public Builder setFilterBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000004; + checkByteStringIsUtf8(value); + filter_ = value; onChanged(); return this; @@ -7539,7 +7312,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -7562,7 +7335,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Unsub parsePartialFrom( @@ -7594,39 +7367,27 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional string destination = 1; - */ - boolean hasDestination(); - /** - * optional string destination = 1; + * string destination = 1; */ java.lang.String getDestination(); /** - * optional string destination = 1; + * string destination = 1; */ com.google.protobuf.ByteString getDestinationBytes(); /** - * optional string client_id = 2; - */ - boolean hasClientId(); - /** - * optional string client_id = 2; + * string client_id = 2; */ java.lang.String getClientId(); /** - * optional string client_id = 2; + * string client_id = 2; */ com.google.protobuf.ByteString getClientIdBytes(); /** - * optional int32 fetch_size = 3; - */ - boolean hasFetchSize(); - /** - * optional int32 fetch_size = 3; + * int32 fetch_size = 3; */ int getFetchSize(); @@ -7635,15 +7396,7 @@ public final class CanalPacket { * 默认-1时代表不控制 * * - * optional int64 timeout = 4 [default = -1]; - */ - boolean hasTimeout(); - /** - *
      -     * 默认-1时代表不控制
      -     * 
      - * - * optional int64 timeout = 4 [default = -1]; + * int64 timeout = 4; */ long getTimeout(); @@ -7652,15 +7405,7 @@ public final class CanalPacket { * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天 * * - * optional int32 unit = 5 [default = 2]; - */ - boolean hasUnit(); - /** - *
      -     * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -     * 
      - * - * optional int32 unit = 5 [default = 2]; + * int32 unit = 5; */ int getUnit(); @@ -7669,17 +7414,15 @@ public final class CanalPacket { * 是否自动ack * * - * optional bool auto_ack = 6 [default = false]; - */ - boolean hasAutoAck(); - /** - *
      -     * 是否自动ack
      -     * 
      - * - * optional bool auto_ack = 6 [default = false]; + * bool auto_ack = 6; */ boolean getAutoAck(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Get.TimeoutPresentCase getTimeoutPresentCase(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Get.UnitPresentCase getUnitPresentCase(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Get.AutoAckPresentCase getAutoAckPresentCase(); } /** *
      @@ -7701,9 +7444,6 @@ public final class CanalPacket {
             destination_ = "";
             clientId_ = "";
             fetchSize_ = 0;
      -      timeout_ = -1L;
      -      unit_ = 2;
      -      autoAck_ = false;
           }
       
           @java.lang.Override
      @@ -7731,39 +7471,39 @@ public final class CanalPacket {
                     done = true;
                     break;
                   case 10: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000001;
      -              destination_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              destination_ = s;
                     break;
                   }
                   case 18: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000002;
      -              clientId_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              clientId_ = s;
                     break;
                   }
                   case 24: {
      -              bitField0_ |= 0x00000004;
      +
                     fetchSize_ = input.readInt32();
                     break;
                   }
                   case 32: {
      -              bitField0_ |= 0x00000008;
      -              timeout_ = input.readInt64();
      +              timeoutPresentCase_ = 4;
      +              timeoutPresent_ = input.readInt64();
                     break;
                   }
                   case 40: {
      -              bitField0_ |= 0x00000010;
      -              unit_ = input.readInt32();
      +              unitPresentCase_ = 5;
      +              unitPresent_ = input.readInt32();
                     break;
                   }
                   case 48: {
      -              bitField0_ |= 0x00000020;
      -              autoAck_ = input.readBool();
      +              autoAckPresentCase_ = 6;
      +              autoAckPresent_ = input.readBool();
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -7794,17 +7534,118 @@ public final class CanalPacket {
                     com.alibaba.otter.canal.protocol.CanalPacket.Get.class, com.alibaba.otter.canal.protocol.CanalPacket.Get.Builder.class);
           }
       
      -    private int bitField0_;
      +    private int timeoutPresentCase_ = 0;
      +    private java.lang.Object timeoutPresent_;
      +    public enum TimeoutPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      TIMEOUT(4),
      +      TIMEOUTPRESENT_NOT_SET(0);
      +      private final int value;
      +      private TimeoutPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static TimeoutPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static TimeoutPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 4: return TIMEOUT;
      +          case 0: return TIMEOUTPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public TimeoutPresentCase
      +    getTimeoutPresentCase() {
      +      return TimeoutPresentCase.forNumber(
      +          timeoutPresentCase_);
      +    }
      +
      +    private int unitPresentCase_ = 0;
      +    private java.lang.Object unitPresent_;
      +    public enum UnitPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      UNIT(5),
      +      UNITPRESENT_NOT_SET(0);
      +      private final int value;
      +      private UnitPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static UnitPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static UnitPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 5: return UNIT;
      +          case 0: return UNITPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public UnitPresentCase
      +    getUnitPresentCase() {
      +      return UnitPresentCase.forNumber(
      +          unitPresentCase_);
      +    }
      +
      +    private int autoAckPresentCase_ = 0;
      +    private java.lang.Object autoAckPresent_;
      +    public enum AutoAckPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      AUTO_ACK(6),
      +      AUTOACKPRESENT_NOT_SET(0);
      +      private final int value;
      +      private AutoAckPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static AutoAckPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static AutoAckPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 6: return AUTO_ACK;
      +          case 0: return AUTOACKPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public AutoAckPresentCase
      +    getAutoAckPresentCase() {
      +      return AutoAckPresentCase.forNumber(
      +          autoAckPresentCase_);
      +    }
      +
           public static final int DESTINATION_FIELD_NUMBER = 1;
           private volatile java.lang.Object destination_;
           /**
      -     * optional string destination = 1;
      -     */
      -    public boolean hasDestination() {
      -      return ((bitField0_ & 0x00000001) == 0x00000001);
      -    }
      -    /**
      -     * optional string destination = 1;
      +     * string destination = 1;
            */
           public java.lang.String getDestination() {
             java.lang.Object ref = destination_;
      @@ -7814,14 +7655,12 @@ public final class CanalPacket {
               com.google.protobuf.ByteString bs = 
                   (com.google.protobuf.ByteString) ref;
               java.lang.String s = bs.toStringUtf8();
      -        if (bs.isValidUtf8()) {
      -          destination_ = s;
      -        }
      +        destination_ = s;
               return s;
             }
           }
           /**
      -     * optional string destination = 1;
      +     * string destination = 1;
            */
           public com.google.protobuf.ByteString
               getDestinationBytes() {
      @@ -7840,13 +7679,7 @@ public final class CanalPacket {
           public static final int CLIENT_ID_FIELD_NUMBER = 2;
           private volatile java.lang.Object clientId_;
           /**
      -     * optional string client_id = 2;
      -     */
      -    public boolean hasClientId() {
      -      return ((bitField0_ & 0x00000002) == 0x00000002);
      -    }
      -    /**
      -     * optional string client_id = 2;
      +     * string client_id = 2;
            */
           public java.lang.String getClientId() {
             java.lang.Object ref = clientId_;
      @@ -7856,14 +7689,12 @@ public final class CanalPacket {
               com.google.protobuf.ByteString bs = 
                   (com.google.protobuf.ByteString) ref;
               java.lang.String s = bs.toStringUtf8();
      -        if (bs.isValidUtf8()) {
      -          clientId_ = s;
      -        }
      +        clientId_ = s;
               return s;
             }
           }
           /**
      -     * optional string client_id = 2;
      +     * string client_id = 2;
            */
           public com.google.protobuf.ByteString
               getClientIdBytes() {
      @@ -7882,85 +7713,55 @@ public final class CanalPacket {
           public static final int FETCH_SIZE_FIELD_NUMBER = 3;
           private int fetchSize_;
           /**
      -     * optional int32 fetch_size = 3;
      -     */
      -    public boolean hasFetchSize() {
      -      return ((bitField0_ & 0x00000004) == 0x00000004);
      -    }
      -    /**
      -     * optional int32 fetch_size = 3;
      +     * int32 fetch_size = 3;
            */
           public int getFetchSize() {
             return fetchSize_;
           }
       
           public static final int TIMEOUT_FIELD_NUMBER = 4;
      -    private long timeout_;
           /**
            * 
            * 默认-1时代表不控制
            * 
      * - * optional int64 timeout = 4 [default = -1]; - */ - public boolean hasTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -     * 默认-1时代表不控制
      -     * 
      - * - * optional int64 timeout = 4 [default = -1]; + * int64 timeout = 4; */ public long getTimeout() { - return timeout_; + if (timeoutPresentCase_ == 4) { + return (java.lang.Long) timeoutPresent_; + } + return -1L; } public static final int UNIT_FIELD_NUMBER = 5; - private int unit_; /** *
            * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
            * 
      * - * optional int32 unit = 5 [default = 2]; - */ - public boolean hasUnit() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - *
      -     * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -     * 
      - * - * optional int32 unit = 5 [default = 2]; + * int32 unit = 5; */ public int getUnit() { - return unit_; + if (unitPresentCase_ == 5) { + return (java.lang.Integer) unitPresent_; + } + return 2; } public static final int AUTO_ACK_FIELD_NUMBER = 6; - private boolean autoAck_; /** *
            * 是否自动ack
            * 
      * - * optional bool auto_ack = 6 [default = false]; - */ - public boolean hasAutoAck() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - *
      -     * 是否自动ack
      -     * 
      - * - * optional bool auto_ack = 6 [default = false]; + * bool auto_ack = 6; */ public boolean getAutoAck() { - return autoAck_; + if (autoAckPresentCase_ == 6) { + return (java.lang.Boolean) autoAckPresent_; + } + return false; } private byte memoizedIsInitialized = -1; @@ -7977,23 +7778,26 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (fetchSize_ != 0) { output.writeInt32(3, fetchSize_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeInt64(4, timeout_); + if (timeoutPresentCase_ == 4) { + output.writeInt64( + 4, (long)((java.lang.Long) timeoutPresent_)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { - output.writeInt32(5, unit_); + if (unitPresentCase_ == 5) { + output.writeInt32( + 5, (int)((java.lang.Integer) unitPresent_)); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - output.writeBool(6, autoAck_); + if (autoAckPresentCase_ == 6) { + output.writeBool( + 6, (boolean)((java.lang.Boolean) autoAckPresent_)); } unknownFields.writeTo(output); } @@ -8004,27 +7808,30 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (!getDestinationBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_); } - if (((bitField0_ & 0x00000002) == 0x00000002)) { + if (!getClientIdBytes().isEmpty()) { size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_); } - if (((bitField0_ & 0x00000004) == 0x00000004)) { + if (fetchSize_ != 0) { size += com.google.protobuf.CodedOutputStream .computeInt32Size(3, fetchSize_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (timeoutPresentCase_ == 4) { size += com.google.protobuf.CodedOutputStream - .computeInt64Size(4, timeout_); + .computeInt64Size( + 4, (long)((java.lang.Long) timeoutPresent_)); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (unitPresentCase_ == 5) { size += com.google.protobuf.CodedOutputStream - .computeInt32Size(5, unit_); + .computeInt32Size( + 5, (int)((java.lang.Integer) unitPresent_)); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (autoAckPresentCase_ == 6) { size += com.google.protobuf.CodedOutputStream - .computeBoolSize(6, autoAck_); + .computeBoolSize( + 6, (boolean)((java.lang.Boolean) autoAckPresent_)); } size += unknownFields.getSerializedSize(); memoizedSize = size; @@ -8042,35 +7849,44 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Get other = (com.alibaba.otter.canal.protocol.CanalPacket.Get) obj; boolean result = true; - result = result && (hasDestination() == other.hasDestination()); - if (hasDestination()) { - result = result && getDestination() - .equals(other.getDestination()); + result = result && getDestination() + .equals(other.getDestination()); + result = result && getClientId() + .equals(other.getClientId()); + result = result && (getFetchSize() + == other.getFetchSize()); + result = result && getTimeoutPresentCase().equals( + other.getTimeoutPresentCase()); + if (!result) return false; + switch (timeoutPresentCase_) { + case 4: + result = result && (getTimeout() + == other.getTimeout()); + break; + case 0: + default: } - result = result && (hasClientId() == other.hasClientId()); - if (hasClientId()) { - result = result && getClientId() - .equals(other.getClientId()); + result = result && getUnitPresentCase().equals( + other.getUnitPresentCase()); + if (!result) return false; + switch (unitPresentCase_) { + case 5: + result = result && (getUnit() + == other.getUnit()); + break; + case 0: + default: } - result = result && (hasFetchSize() == other.hasFetchSize()); - if (hasFetchSize()) { - result = result && (getFetchSize() - == other.getFetchSize()); - } - result = result && (hasTimeout() == other.hasTimeout()); - if (hasTimeout()) { - result = result && (getTimeout() - == other.getTimeout()); - } - result = result && (hasUnit() == other.hasUnit()); - if (hasUnit()) { - result = result && (getUnit() - == other.getUnit()); - } - result = result && (hasAutoAck() == other.hasAutoAck()); - if (hasAutoAck()) { - result = result && (getAutoAck() - == other.getAutoAck()); + result = result && getAutoAckPresentCase().equals( + other.getAutoAckPresentCase()); + if (!result) return false; + switch (autoAckPresentCase_) { + case 6: + result = result && (getAutoAck() + == other.getAutoAck()); + break; + case 0: + default: } result = result && unknownFields.equals(other.unknownFields); return result; @@ -8083,31 +7899,37 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasDestination()) { - hash = (37 * hash) + DESTINATION_FIELD_NUMBER; - hash = (53 * hash) + getDestination().hashCode(); + hash = (37 * hash) + DESTINATION_FIELD_NUMBER; + hash = (53 * hash) + getDestination().hashCode(); + hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; + hash = (53 * hash) + getClientId().hashCode(); + hash = (37 * hash) + FETCH_SIZE_FIELD_NUMBER; + hash = (53 * hash) + getFetchSize(); + switch (timeoutPresentCase_) { + case 4: + hash = (37 * hash) + TIMEOUT_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getTimeout()); + break; + case 0: + default: } - if (hasClientId()) { - hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER; - hash = (53 * hash) + getClientId().hashCode(); + switch (unitPresentCase_) { + case 5: + hash = (37 * hash) + UNIT_FIELD_NUMBER; + hash = (53 * hash) + getUnit(); + break; + case 0: + default: } - if (hasFetchSize()) { - hash = (37 * hash) + FETCH_SIZE_FIELD_NUMBER; - hash = (53 * hash) + getFetchSize(); - } - if (hasTimeout()) { - hash = (37 * hash) + TIMEOUT_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getTimeout()); - } - if (hasUnit()) { - hash = (37 * hash) + UNIT_FIELD_NUMBER; - hash = (53 * hash) + getUnit(); - } - if (hasAutoAck()) { - hash = (37 * hash) + AUTO_ACK_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( - getAutoAck()); + switch (autoAckPresentCase_) { + case 6: + hash = (37 * hash) + AUTO_ACK_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashBoolean( + getAutoAck()); + break; + case 0: + default: } hash = (29 * hash) + unknownFields.hashCode(); memoizedHashCode = hash; @@ -8247,17 +8069,17 @@ public final class CanalPacket { public Builder clear() { super.clear(); destination_ = ""; - bitField0_ = (bitField0_ & ~0x00000001); + clientId_ = ""; - bitField0_ = (bitField0_ & ~0x00000002); + fetchSize_ = 0; - bitField0_ = (bitField0_ & ~0x00000004); - timeout_ = -1L; - bitField0_ = (bitField0_ & ~0x00000008); - unit_ = 2; - bitField0_ = (bitField0_ & ~0x00000010); - autoAck_ = false; - bitField0_ = (bitField0_ & ~0x00000020); + + timeoutPresentCase_ = 0; + timeoutPresent_ = null; + unitPresentCase_ = 0; + unitPresent_ = null; + autoAckPresentCase_ = 0; + autoAckPresent_ = null; return this; } @@ -8284,33 +8106,21 @@ public final class CanalPacket { @java.lang.Override public com.alibaba.otter.canal.protocol.CanalPacket.Get buildPartial() { com.alibaba.otter.canal.protocol.CanalPacket.Get result = new com.alibaba.otter.canal.protocol.CanalPacket.Get(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.destination_ = destination_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } result.clientId_ = clientId_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } result.fetchSize_ = fetchSize_; - if (((from_bitField0_ & 0x00000008) == 0x00000008)) { - to_bitField0_ |= 0x00000008; + if (timeoutPresentCase_ == 4) { + result.timeoutPresent_ = timeoutPresent_; } - result.timeout_ = timeout_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000010; + if (unitPresentCase_ == 5) { + result.unitPresent_ = unitPresent_; } - result.unit_ = unit_; - if (((from_bitField0_ & 0x00000020) == 0x00000020)) { - to_bitField0_ |= 0x00000020; + if (autoAckPresentCase_ == 6) { + result.autoAckPresent_ = autoAckPresent_; } - result.autoAck_ = autoAck_; - result.bitField0_ = to_bitField0_; + result.timeoutPresentCase_ = timeoutPresentCase_; + result.unitPresentCase_ = unitPresentCase_; + result.autoAckPresentCase_ = autoAckPresentCase_; onBuilt(); return result; } @@ -8359,27 +8169,43 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Get other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.Get.getDefaultInstance()) return this; - if (other.hasDestination()) { - bitField0_ |= 0x00000001; + if (!other.getDestination().isEmpty()) { destination_ = other.destination_; onChanged(); } - if (other.hasClientId()) { - bitField0_ |= 0x00000002; + if (!other.getClientId().isEmpty()) { clientId_ = other.clientId_; onChanged(); } - if (other.hasFetchSize()) { + if (other.getFetchSize() != 0) { setFetchSize(other.getFetchSize()); } - if (other.hasTimeout()) { - setTimeout(other.getTimeout()); + switch (other.getTimeoutPresentCase()) { + case TIMEOUT: { + setTimeout(other.getTimeout()); + break; + } + case TIMEOUTPRESENT_NOT_SET: { + break; + } } - if (other.hasUnit()) { - setUnit(other.getUnit()); + switch (other.getUnitPresentCase()) { + case UNIT: { + setUnit(other.getUnit()); + break; + } + case UNITPRESENT_NOT_SET: { + break; + } } - if (other.hasAutoAck()) { - setAutoAck(other.getAutoAck()); + switch (other.getAutoAckPresentCase()) { + case AUTO_ACK: { + setAutoAck(other.getAutoAck()); + break; + } + case AUTOACKPRESENT_NOT_SET: { + break; + } } this.mergeUnknownFields(other.unknownFields); onChanged(); @@ -8409,17 +8235,55 @@ public final class CanalPacket { } return this; } - private int bitField0_; + private int timeoutPresentCase_ = 0; + private java.lang.Object timeoutPresent_; + public TimeoutPresentCase + getTimeoutPresentCase() { + return TimeoutPresentCase.forNumber( + timeoutPresentCase_); + } + + public Builder clearTimeoutPresent() { + timeoutPresentCase_ = 0; + timeoutPresent_ = null; + onChanged(); + return this; + } + + private int unitPresentCase_ = 0; + private java.lang.Object unitPresent_; + public UnitPresentCase + getUnitPresentCase() { + return UnitPresentCase.forNumber( + unitPresentCase_); + } + + public Builder clearUnitPresent() { + unitPresentCase_ = 0; + unitPresent_ = null; + onChanged(); + return this; + } + + private int autoAckPresentCase_ = 0; + private java.lang.Object autoAckPresent_; + public AutoAckPresentCase + getAutoAckPresentCase() { + return AutoAckPresentCase.forNumber( + autoAckPresentCase_); + } + + public Builder clearAutoAckPresent() { + autoAckPresentCase_ = 0; + autoAckPresent_ = null; + onChanged(); + return this; + } + private java.lang.Object destination_ = ""; /** - * optional string destination = 1; - */ - public boolean hasDestination() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional string destination = 1; + * string destination = 1; */ public java.lang.String getDestination() { java.lang.Object ref = destination_; @@ -8427,16 +8291,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - destination_ = s; - } + destination_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string destination = 1; + * string destination = 1; */ public com.google.protobuf.ByteString getDestinationBytes() { @@ -8452,36 +8314,37 @@ public final class CanalPacket { } } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestination( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + destination_ = value; onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder clearDestination() { - bitField0_ = (bitField0_ & ~0x00000001); + destination_ = getDefaultInstance().getDestination(); onChanged(); return this; } /** - * optional string destination = 1; + * string destination = 1; */ public Builder setDestinationBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000001; + checkByteStringIsUtf8(value); + destination_ = value; onChanged(); return this; @@ -8489,13 +8352,7 @@ public final class CanalPacket { private java.lang.Object clientId_ = ""; /** - * optional string client_id = 2; - */ - public boolean hasClientId() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional string client_id = 2; + * string client_id = 2; */ public java.lang.String getClientId() { java.lang.Object ref = clientId_; @@ -8503,16 +8360,14 @@ public final class CanalPacket { com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - clientId_ = s; - } + clientId_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string client_id = 2; + * string client_id = 2; */ public com.google.protobuf.ByteString getClientIdBytes() { @@ -8528,36 +8383,37 @@ public final class CanalPacket { } } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientId( java.lang.String value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + clientId_ = value; onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder clearClientId() { - bitField0_ = (bitField0_ & ~0x00000002); + clientId_ = getDefaultInstance().getClientId(); onChanged(); return this; } /** - * optional string client_id = 2; + * string client_id = 2; */ public Builder setClientIdBytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000002; + checkByteStringIsUtf8(value); + clientId_ = value; onChanged(); return this; @@ -8565,67 +8421,53 @@ public final class CanalPacket { private int fetchSize_ ; /** - * optional int32 fetch_size = 3; - */ - public boolean hasFetchSize() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional int32 fetch_size = 3; + * int32 fetch_size = 3; */ public int getFetchSize() { return fetchSize_; } /** - * optional int32 fetch_size = 3; + * int32 fetch_size = 3; */ public Builder setFetchSize(int value) { - bitField0_ |= 0x00000004; + fetchSize_ = value; onChanged(); return this; } /** - * optional int32 fetch_size = 3; + * int32 fetch_size = 3; */ public Builder clearFetchSize() { - bitField0_ = (bitField0_ & ~0x00000004); + fetchSize_ = 0; onChanged(); return this; } - private long timeout_ = -1L; /** *
              * 默认-1时代表不控制
              * 
      * - * optional int64 timeout = 4 [default = -1]; - */ - public boolean hasTimeout() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - *
      -       * 默认-1时代表不控制
      -       * 
      - * - * optional int64 timeout = 4 [default = -1]; + * int64 timeout = 4; */ public long getTimeout() { - return timeout_; + if (timeoutPresentCase_ == 4) { + return (java.lang.Long) timeoutPresent_; + } + return 0L; } /** *
              * 默认-1时代表不控制
              * 
      * - * optional int64 timeout = 4 [default = -1]; + * int64 timeout = 4; */ public Builder setTimeout(long value) { - bitField0_ |= 0x00000008; - timeout_ = value; + timeoutPresentCase_ = 4; + timeoutPresent_ = value; onChanged(); return this; } @@ -8634,46 +8476,40 @@ public final class CanalPacket { * 默认-1时代表不控制 *
      * - * optional int64 timeout = 4 [default = -1]; + * int64 timeout = 4; */ public Builder clearTimeout() { - bitField0_ = (bitField0_ & ~0x00000008); - timeout_ = -1L; - onChanged(); + if (timeoutPresentCase_ == 4) { + timeoutPresentCase_ = 0; + timeoutPresent_ = null; + onChanged(); + } return this; } - private int unit_ = 2; /** *
              * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
              * 
      * - * optional int32 unit = 5 [default = 2]; - */ - public boolean hasUnit() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - *
      -       * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -       * 
      - * - * optional int32 unit = 5 [default = 2]; + * int32 unit = 5; */ public int getUnit() { - return unit_; + if (unitPresentCase_ == 5) { + return (java.lang.Integer) unitPresent_; + } + return 0; } /** *
              * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
              * 
      * - * optional int32 unit = 5 [default = 2]; + * int32 unit = 5; */ public Builder setUnit(int value) { - bitField0_ |= 0x00000010; - unit_ = value; + unitPresentCase_ = 5; + unitPresent_ = value; onChanged(); return this; } @@ -8682,46 +8518,40 @@ public final class CanalPacket { * 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天 * * - * optional int32 unit = 5 [default = 2]; + * int32 unit = 5; */ public Builder clearUnit() { - bitField0_ = (bitField0_ & ~0x00000010); - unit_ = 2; - onChanged(); + if (unitPresentCase_ == 5) { + unitPresentCase_ = 0; + unitPresent_ = null; + onChanged(); + } return this; } - private boolean autoAck_ ; /** *
              * 是否自动ack
              * 
      * - * optional bool auto_ack = 6 [default = false]; - */ - public boolean hasAutoAck() { - return ((bitField0_ & 0x00000020) == 0x00000020); - } - /** - *
      -       * 是否自动ack
      -       * 
      - * - * optional bool auto_ack = 6 [default = false]; + * bool auto_ack = 6; */ public boolean getAutoAck() { - return autoAck_; + if (autoAckPresentCase_ == 6) { + return (java.lang.Boolean) autoAckPresent_; + } + return false; } /** *
              * 是否自动ack
              * 
      * - * optional bool auto_ack = 6 [default = false]; + * bool auto_ack = 6; */ public Builder setAutoAck(boolean value) { - bitField0_ |= 0x00000020; - autoAck_ = value; + autoAckPresentCase_ = 6; + autoAckPresent_ = value; onChanged(); return this; } @@ -8730,18 +8560,20 @@ public final class CanalPacket { * 是否自动ack * * - * optional bool auto_ack = 6 [default = false]; + * bool auto_ack = 6; */ public Builder clearAutoAck() { - bitField0_ = (bitField0_ & ~0x00000020); - autoAck_ = false; - onChanged(); + if (autoAckPresentCase_ == 6) { + autoAckPresentCase_ = 0; + autoAckPresent_ = null; + onChanged(); + } return this; } @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -8764,7 +8596,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Get parsePartialFrom( @@ -8796,11 +8628,7 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional int64 batch_id = 1; - */ - boolean hasBatchId(); - /** - * optional int64 batch_id = 1; + * int64 batch_id = 1; */ long getBatchId(); @@ -8862,7 +8690,7 @@ public final class CanalPacket { done = true; break; case 8: { - bitField0_ |= 0x00000001; + batchId_ = input.readInt64(); break; } @@ -8875,7 +8703,7 @@ public final class CanalPacket { break; } default: { - if (!parseUnknownField( + if (!parseUnknownFieldProto3( input, unknownFields, extensionRegistry, tag)) { done = true; } @@ -8913,13 +8741,7 @@ public final class CanalPacket { public static final int BATCH_ID_FIELD_NUMBER = 1; private long batchId_; /** - * optional int64 batch_id = 1; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional int64 batch_id = 1; + * int64 batch_id = 1; */ public long getBatchId() { return batchId_; @@ -8961,7 +8783,7 @@ public final class CanalPacket { @java.lang.Override public void writeTo(com.google.protobuf.CodedOutputStream output) throws java.io.IOException { - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (batchId_ != 0L) { output.writeInt64(1, batchId_); } for (int i = 0; i < messages_.size(); i++) { @@ -8976,7 +8798,7 @@ public final class CanalPacket { if (size != -1) return size; size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { + if (batchId_ != 0L) { size += com.google.protobuf.CodedOutputStream .computeInt64Size(1, batchId_); } @@ -9005,11 +8827,8 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Messages other = (com.alibaba.otter.canal.protocol.CanalPacket.Messages) obj; boolean result = true; - result = result && (hasBatchId() == other.hasBatchId()); - if (hasBatchId()) { - result = result && (getBatchId() - == other.getBatchId()); - } + result = result && (getBatchId() + == other.getBatchId()); result = result && getMessagesList() .equals(other.getMessagesList()); result = result && unknownFields.equals(other.unknownFields); @@ -9023,11 +8842,9 @@ public final class CanalPacket { } int hash = 41; hash = (19 * hash) + getDescriptor().hashCode(); - if (hasBatchId()) { - hash = (37 * hash) + BATCH_ID_FIELD_NUMBER; - hash = (53 * hash) + com.google.protobuf.Internal.hashLong( - getBatchId()); - } + hash = (37 * hash) + BATCH_ID_FIELD_NUMBER; + hash = (53 * hash) + com.google.protobuf.Internal.hashLong( + getBatchId()); if (getMessagesCount() > 0) { hash = (37 * hash) + MESSAGES_FIELD_NUMBER; hash = (53 * hash) + getMessagesList().hashCode(); @@ -9169,7 +8986,7 @@ public final class CanalPacket { public Builder clear() { super.clear(); batchId_ = 0L; - bitField0_ = (bitField0_ & ~0x00000001); + messages_ = java.util.Collections.emptyList(); bitField0_ = (bitField0_ & ~0x00000002); return this; @@ -9200,9 +9017,6 @@ public final class CanalPacket { com.alibaba.otter.canal.protocol.CanalPacket.Messages result = new com.alibaba.otter.canal.protocol.CanalPacket.Messages(this); int from_bitField0_ = bitField0_; int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } result.batchId_ = batchId_; if (((bitField0_ & 0x00000002) == 0x00000002)) { messages_ = java.util.Collections.unmodifiableList(messages_); @@ -9258,7 +9072,7 @@ public final class CanalPacket { public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Messages other) { if (other == com.alibaba.otter.canal.protocol.CanalPacket.Messages.getDefaultInstance()) return this; - if (other.hasBatchId()) { + if (other.getBatchId() != 0L) { setBatchId(other.getBatchId()); } if (!other.messages_.isEmpty()) { @@ -9303,31 +9117,25 @@ public final class CanalPacket { private long batchId_ ; /** - * optional int64 batch_id = 1; - */ - public boolean hasBatchId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional int64 batch_id = 1; + * int64 batch_id = 1; */ public long getBatchId() { return batchId_; } /** - * optional int64 batch_id = 1; + * int64 batch_id = 1; */ public Builder setBatchId(long value) { - bitField0_ |= 0x00000001; + batchId_ = value; onChanged(); return this; } /** - * optional int64 batch_id = 1; + * int64 batch_id = 1; */ public Builder clearBatchId() { - bitField0_ = (bitField0_ & ~0x00000001); + batchId_ = 0L; onChanged(); return this; @@ -9407,7 +9215,7 @@ public final class CanalPacket { @java.lang.Override public final Builder setUnknownFields( final com.google.protobuf.UnknownFieldSet unknownFields) { - return super.setUnknownFields(unknownFields); + return super.setUnknownFieldsProto3(unknownFields); } @java.lang.Override @@ -9430,7 +9238,7 @@ public final class CanalPacket { return DEFAULT_INSTANCE; } - @java.lang.Deprecated public static final com.google.protobuf.Parser + private static final com.google.protobuf.Parser PARSER = new com.google.protobuf.AbstractParser() { @java.lang.Override public Messages parsePartialFrom( @@ -9462,36 +9270,26 @@ public final class CanalPacket { com.google.protobuf.MessageOrBuilder { /** - * optional string journal = 1; - */ - boolean hasJournal(); - /** - * optional string journal = 1; + * string journal = 1; */ java.lang.String getJournal(); /** - * optional string journal = 1; + * string journal = 1; */ com.google.protobuf.ByteString getJournalBytes(); /** - * optional int64 position = 2; - */ - boolean hasPosition(); - /** - * optional int64 position = 2; + * int64 position = 2; */ long getPosition(); /** - * optional int64 timestamp = 3 [default = 0]; - */ - boolean hasTimestamp(); - /** - * optional int64 timestamp = 3 [default = 0]; + * int64 timestamp = 3; */ long getTimestamp(); + + public com.alibaba.otter.canal.protocol.CanalPacket.Dump.TimestampPresentCase getTimestampPresentCase(); } /** *
      @@ -9512,7 +9310,6 @@ public final class CanalPacket {
           private Dump() {
             journal_ = "";
             position_ = 0L;
      -      timestamp_ = 0L;
           }
       
           @java.lang.Override
      @@ -9540,23 +9337,23 @@ public final class CanalPacket {
                     done = true;
                     break;
                   case 10: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000001;
      -              journal_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              journal_ = s;
                     break;
                   }
                   case 16: {
      -              bitField0_ |= 0x00000002;
      +
                     position_ = input.readInt64();
                     break;
                   }
                   case 24: {
      -              bitField0_ |= 0x00000004;
      -              timestamp_ = input.readInt64();
      +              timestampPresentCase_ = 3;
      +              timestampPresent_ = input.readInt64();
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -9587,17 +9384,46 @@ public final class CanalPacket {
                     com.alibaba.otter.canal.protocol.CanalPacket.Dump.class, com.alibaba.otter.canal.protocol.CanalPacket.Dump.Builder.class);
           }
       
      -    private int bitField0_;
      +    private int timestampPresentCase_ = 0;
      +    private java.lang.Object timestampPresent_;
      +    public enum TimestampPresentCase
      +        implements com.google.protobuf.Internal.EnumLite {
      +      TIMESTAMP(3),
      +      TIMESTAMPPRESENT_NOT_SET(0);
      +      private final int value;
      +      private TimestampPresentCase(int value) {
      +        this.value = value;
      +      }
      +      /**
      +       * @deprecated Use {@link #forNumber(int)} instead.
      +       */
      +      @java.lang.Deprecated
      +      public static TimestampPresentCase valueOf(int value) {
      +        return forNumber(value);
      +      }
      +
      +      public static TimestampPresentCase forNumber(int value) {
      +        switch (value) {
      +          case 3: return TIMESTAMP;
      +          case 0: return TIMESTAMPPRESENT_NOT_SET;
      +          default: return null;
      +        }
      +      }
      +      public int getNumber() {
      +        return this.value;
      +      }
      +    };
      +
      +    public TimestampPresentCase
      +    getTimestampPresentCase() {
      +      return TimestampPresentCase.forNumber(
      +          timestampPresentCase_);
      +    }
      +
           public static final int JOURNAL_FIELD_NUMBER = 1;
           private volatile java.lang.Object journal_;
           /**
      -     * optional string journal = 1;
      -     */
      -    public boolean hasJournal() {
      -      return ((bitField0_ & 0x00000001) == 0x00000001);
      -    }
      -    /**
      -     * optional string journal = 1;
      +     * string journal = 1;
            */
           public java.lang.String getJournal() {
             java.lang.Object ref = journal_;
      @@ -9607,14 +9433,12 @@ public final class CanalPacket {
               com.google.protobuf.ByteString bs = 
                   (com.google.protobuf.ByteString) ref;
               java.lang.String s = bs.toStringUtf8();
      -        if (bs.isValidUtf8()) {
      -          journal_ = s;
      -        }
      +        journal_ = s;
               return s;
             }
           }
           /**
      -     * optional string journal = 1;
      +     * string journal = 1;
            */
           public com.google.protobuf.ByteString
               getJournalBytes() {
      @@ -9633,31 +9457,21 @@ public final class CanalPacket {
           public static final int POSITION_FIELD_NUMBER = 2;
           private long position_;
           /**
      -     * optional int64 position = 2;
      -     */
      -    public boolean hasPosition() {
      -      return ((bitField0_ & 0x00000002) == 0x00000002);
      -    }
      -    /**
      -     * optional int64 position = 2;
      +     * int64 position = 2;
            */
           public long getPosition() {
             return position_;
           }
       
           public static final int TIMESTAMP_FIELD_NUMBER = 3;
      -    private long timestamp_;
           /**
      -     * optional int64 timestamp = 3 [default = 0];
      -     */
      -    public boolean hasTimestamp() {
      -      return ((bitField0_ & 0x00000004) == 0x00000004);
      -    }
      -    /**
      -     * optional int64 timestamp = 3 [default = 0];
      +     * int64 timestamp = 3;
            */
           public long getTimestamp() {
      -      return timestamp_;
      +      if (timestampPresentCase_ == 3) {
      +        return (java.lang.Long) timestampPresent_;
      +      }
      +      return 0L;
           }
       
           private byte memoizedIsInitialized = -1;
      @@ -9674,14 +9488,15 @@ public final class CanalPacket {
           @java.lang.Override
           public void writeTo(com.google.protobuf.CodedOutputStream output)
                               throws java.io.IOException {
      -      if (((bitField0_ & 0x00000001) == 0x00000001)) {
      +      if (!getJournalBytes().isEmpty()) {
               com.google.protobuf.GeneratedMessageV3.writeString(output, 1, journal_);
             }
      -      if (((bitField0_ & 0x00000002) == 0x00000002)) {
      +      if (position_ != 0L) {
               output.writeInt64(2, position_);
             }
      -      if (((bitField0_ & 0x00000004) == 0x00000004)) {
      -        output.writeInt64(3, timestamp_);
      +      if (timestampPresentCase_ == 3) {
      +        output.writeInt64(
      +            3, (long)((java.lang.Long) timestampPresent_));
             }
             unknownFields.writeTo(output);
           }
      @@ -9692,16 +9507,17 @@ public final class CanalPacket {
             if (size != -1) return size;
       
             size = 0;
      -      if (((bitField0_ & 0x00000001) == 0x00000001)) {
      +      if (!getJournalBytes().isEmpty()) {
               size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, journal_);
             }
      -      if (((bitField0_ & 0x00000002) == 0x00000002)) {
      +      if (position_ != 0L) {
               size += com.google.protobuf.CodedOutputStream
                 .computeInt64Size(2, position_);
             }
      -      if (((bitField0_ & 0x00000004) == 0x00000004)) {
      +      if (timestampPresentCase_ == 3) {
               size += com.google.protobuf.CodedOutputStream
      -          .computeInt64Size(3, timestamp_);
      +          .computeInt64Size(
      +              3, (long)((java.lang.Long) timestampPresent_));
             }
             size += unknownFields.getSerializedSize();
             memoizedSize = size;
      @@ -9719,20 +9535,20 @@ public final class CanalPacket {
             com.alibaba.otter.canal.protocol.CanalPacket.Dump other = (com.alibaba.otter.canal.protocol.CanalPacket.Dump) obj;
       
             boolean result = true;
      -      result = result && (hasJournal() == other.hasJournal());
      -      if (hasJournal()) {
      -        result = result && getJournal()
      -            .equals(other.getJournal());
      -      }
      -      result = result && (hasPosition() == other.hasPosition());
      -      if (hasPosition()) {
      -        result = result && (getPosition()
      -            == other.getPosition());
      -      }
      -      result = result && (hasTimestamp() == other.hasTimestamp());
      -      if (hasTimestamp()) {
      -        result = result && (getTimestamp()
      -            == other.getTimestamp());
      +      result = result && getJournal()
      +          .equals(other.getJournal());
      +      result = result && (getPosition()
      +          == other.getPosition());
      +      result = result && getTimestampPresentCase().equals(
      +          other.getTimestampPresentCase());
      +      if (!result) return false;
      +      switch (timestampPresentCase_) {
      +        case 3:
      +          result = result && (getTimestamp()
      +              == other.getTimestamp());
      +          break;
      +        case 0:
      +        default:
             }
             result = result && unknownFields.equals(other.unknownFields);
             return result;
      @@ -9745,19 +9561,19 @@ public final class CanalPacket {
             }
             int hash = 41;
             hash = (19 * hash) + getDescriptor().hashCode();
      -      if (hasJournal()) {
      -        hash = (37 * hash) + JOURNAL_FIELD_NUMBER;
      -        hash = (53 * hash) + getJournal().hashCode();
      -      }
      -      if (hasPosition()) {
      -        hash = (37 * hash) + POSITION_FIELD_NUMBER;
      -        hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
      -            getPosition());
      -      }
      -      if (hasTimestamp()) {
      -        hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER;
      -        hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
      -            getTimestamp());
      +      hash = (37 * hash) + JOURNAL_FIELD_NUMBER;
      +      hash = (53 * hash) + getJournal().hashCode();
      +      hash = (37 * hash) + POSITION_FIELD_NUMBER;
      +      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
      +          getPosition());
      +      switch (timestampPresentCase_) {
      +        case 3:
      +          hash = (37 * hash) + TIMESTAMP_FIELD_NUMBER;
      +          hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
      +              getTimestamp());
      +          break;
      +        case 0:
      +        default:
             }
             hash = (29 * hash) + unknownFields.hashCode();
             memoizedHashCode = hash;
      @@ -9897,11 +9713,11 @@ public final class CanalPacket {
             public Builder clear() {
               super.clear();
               journal_ = "";
      -        bitField0_ = (bitField0_ & ~0x00000001);
      +
               position_ = 0L;
      -        bitField0_ = (bitField0_ & ~0x00000002);
      -        timestamp_ = 0L;
      -        bitField0_ = (bitField0_ & ~0x00000004);
      +
      +        timestampPresentCase_ = 0;
      +        timestampPresent_ = null;
               return this;
             }
       
      @@ -9928,21 +9744,12 @@ public final class CanalPacket {
             @java.lang.Override
             public com.alibaba.otter.canal.protocol.CanalPacket.Dump buildPartial() {
               com.alibaba.otter.canal.protocol.CanalPacket.Dump result = new com.alibaba.otter.canal.protocol.CanalPacket.Dump(this);
      -        int from_bitField0_ = bitField0_;
      -        int to_bitField0_ = 0;
      -        if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
      -          to_bitField0_ |= 0x00000001;
      -        }
               result.journal_ = journal_;
      -        if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
      -          to_bitField0_ |= 0x00000002;
      -        }
               result.position_ = position_;
      -        if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
      -          to_bitField0_ |= 0x00000004;
      +        if (timestampPresentCase_ == 3) {
      +          result.timestampPresent_ = timestampPresent_;
               }
      -        result.timestamp_ = timestamp_;
      -        result.bitField0_ = to_bitField0_;
      +        result.timestampPresentCase_ = timestampPresentCase_;
               onBuilt();
               return result;
             }
      @@ -9991,16 +9798,21 @@ public final class CanalPacket {
       
             public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.Dump other) {
               if (other == com.alibaba.otter.canal.protocol.CanalPacket.Dump.getDefaultInstance()) return this;
      -        if (other.hasJournal()) {
      -          bitField0_ |= 0x00000001;
      +        if (!other.getJournal().isEmpty()) {
                 journal_ = other.journal_;
                 onChanged();
               }
      -        if (other.hasPosition()) {
      +        if (other.getPosition() != 0L) {
                 setPosition(other.getPosition());
               }
      -        if (other.hasTimestamp()) {
      -          setTimestamp(other.getTimestamp());
      +        switch (other.getTimestampPresentCase()) {
      +          case TIMESTAMP: {
      +            setTimestamp(other.getTimestamp());
      +            break;
      +          }
      +          case TIMESTAMPPRESENT_NOT_SET: {
      +            break;
      +          }
               }
               this.mergeUnknownFields(other.unknownFields);
               onChanged();
      @@ -10030,17 +9842,25 @@ public final class CanalPacket {
               }
               return this;
             }
      -      private int bitField0_;
      +      private int timestampPresentCase_ = 0;
      +      private java.lang.Object timestampPresent_;
      +      public TimestampPresentCase
      +          getTimestampPresentCase() {
      +        return TimestampPresentCase.forNumber(
      +            timestampPresentCase_);
      +      }
      +
      +      public Builder clearTimestampPresent() {
      +        timestampPresentCase_ = 0;
      +        timestampPresent_ = null;
      +        onChanged();
      +        return this;
      +      }
      +
       
             private java.lang.Object journal_ = "";
             /**
      -       * optional string journal = 1;
      -       */
      -      public boolean hasJournal() {
      -        return ((bitField0_ & 0x00000001) == 0x00000001);
      -      }
      -      /**
      -       * optional string journal = 1;
      +       * string journal = 1;
              */
             public java.lang.String getJournal() {
               java.lang.Object ref = journal_;
      @@ -10048,16 +9868,14 @@ public final class CanalPacket {
                 com.google.protobuf.ByteString bs =
                     (com.google.protobuf.ByteString) ref;
                 java.lang.String s = bs.toStringUtf8();
      -          if (bs.isValidUtf8()) {
      -            journal_ = s;
      -          }
      +          journal_ = s;
                 return s;
               } else {
                 return (java.lang.String) ref;
               }
             }
             /**
      -       * optional string journal = 1;
      +       * string journal = 1;
              */
             public com.google.protobuf.ByteString
                 getJournalBytes() {
      @@ -10073,36 +9891,37 @@ public final class CanalPacket {
               }
             }
             /**
      -       * optional string journal = 1;
      +       * string journal = 1;
              */
             public Builder setJournal(
                 java.lang.String value) {
               if (value == null) {
           throw new NullPointerException();
         }
      -  bitField0_ |= 0x00000001;
      +  
               journal_ = value;
               onChanged();
               return this;
             }
             /**
      -       * optional string journal = 1;
      +       * string journal = 1;
              */
             public Builder clearJournal() {
      -        bitField0_ = (bitField0_ & ~0x00000001);
      +        
               journal_ = getDefaultInstance().getJournal();
               onChanged();
               return this;
             }
             /**
      -       * optional string journal = 1;
      +       * string journal = 1;
              */
             public Builder setJournalBytes(
                 com.google.protobuf.ByteString value) {
               if (value == null) {
           throw new NullPointerException();
         }
      -  bitField0_ |= 0x00000001;
      +  checkByteStringIsUtf8(value);
      +        
               journal_ = value;
               onChanged();
               return this;
      @@ -10110,71 +9929,63 @@ public final class CanalPacket {
       
             private long position_ ;
             /**
      -       * optional int64 position = 2;
      -       */
      -      public boolean hasPosition() {
      -        return ((bitField0_ & 0x00000002) == 0x00000002);
      -      }
      -      /**
      -       * optional int64 position = 2;
      +       * int64 position = 2;
              */
             public long getPosition() {
               return position_;
             }
             /**
      -       * optional int64 position = 2;
      +       * int64 position = 2;
              */
             public Builder setPosition(long value) {
      -        bitField0_ |= 0x00000002;
      +        
               position_ = value;
               onChanged();
               return this;
             }
             /**
      -       * optional int64 position = 2;
      +       * int64 position = 2;
              */
             public Builder clearPosition() {
      -        bitField0_ = (bitField0_ & ~0x00000002);
      +        
               position_ = 0L;
               onChanged();
               return this;
             }
       
      -      private long timestamp_ ;
             /**
      -       * optional int64 timestamp = 3 [default = 0];
      -       */
      -      public boolean hasTimestamp() {
      -        return ((bitField0_ & 0x00000004) == 0x00000004);
      -      }
      -      /**
      -       * optional int64 timestamp = 3 [default = 0];
      +       * int64 timestamp = 3;
              */
             public long getTimestamp() {
      -        return timestamp_;
      +        if (timestampPresentCase_ == 3) {
      +          return (java.lang.Long) timestampPresent_;
      +        }
      +        return 0L;
             }
             /**
      -       * optional int64 timestamp = 3 [default = 0];
      +       * int64 timestamp = 3;
              */
             public Builder setTimestamp(long value) {
      -        bitField0_ |= 0x00000004;
      -        timestamp_ = value;
      +        timestampPresentCase_ = 3;
      +        timestampPresent_ = value;
               onChanged();
               return this;
             }
             /**
      -       * optional int64 timestamp = 3 [default = 0];
      +       * int64 timestamp = 3;
              */
             public Builder clearTimestamp() {
      -        bitField0_ = (bitField0_ & ~0x00000004);
      -        timestamp_ = 0L;
      -        onChanged();
      +        if (timestampPresentCase_ == 3) {
      +          timestampPresentCase_ = 0;
      +          timestampPresent_ = null;
      +          onChanged();
      +        }
               return this;
             }
             @java.lang.Override
             public final Builder setUnknownFields(
                 final com.google.protobuf.UnknownFieldSet unknownFields) {
      -        return super.setUnknownFields(unknownFields);
      +        return super.setUnknownFieldsProto3(unknownFields);
             }
       
             @java.lang.Override
      @@ -10197,7 +10008,7 @@ public final class CanalPacket {
             return DEFAULT_INSTANCE;
           }
       
      -    @java.lang.Deprecated public static final com.google.protobuf.Parser
      +    private static final com.google.protobuf.Parser
               PARSER = new com.google.protobuf.AbstractParser() {
             @java.lang.Override
             public Dump parsePartialFrom(
      @@ -10229,39 +10040,27 @@ public final class CanalPacket {
             com.google.protobuf.MessageOrBuilder {
       
           /**
      -     * optional string destination = 1;
      -     */
      -    boolean hasDestination();
      -    /**
      -     * optional string destination = 1;
      +     * string destination = 1;
            */
           java.lang.String getDestination();
           /**
      -     * optional string destination = 1;
      +     * string destination = 1;
            */
           com.google.protobuf.ByteString
               getDestinationBytes();
       
           /**
      -     * optional string client_id = 2;
      -     */
      -    boolean hasClientId();
      -    /**
      -     * optional string client_id = 2;
      +     * string client_id = 2;
            */
           java.lang.String getClientId();
           /**
      -     * optional string client_id = 2;
      +     * string client_id = 2;
            */
           com.google.protobuf.ByteString
               getClientIdBytes();
       
           /**
      -     * optional int64 batch_id = 3;
      -     */
      -    boolean hasBatchId();
      -    /**
      -     * optional int64 batch_id = 3;
      +     * int64 batch_id = 3;
            */
           long getBatchId();
         }
      @@ -10308,24 +10107,24 @@ public final class CanalPacket {
                     done = true;
                     break;
                   case 10: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000001;
      -              destination_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              destination_ = s;
                     break;
                   }
                   case 18: {
      -              com.google.protobuf.ByteString bs = input.readBytes();
      -              bitField0_ |= 0x00000002;
      -              clientId_ = bs;
      +              java.lang.String s = input.readStringRequireUtf8();
      +
      +              clientId_ = s;
                     break;
                   }
                   case 24: {
      -              bitField0_ |= 0x00000004;
      +
                     batchId_ = input.readInt64();
                     break;
                   }
                   default: {
      -              if (!parseUnknownField(
      +              if (!parseUnknownFieldProto3(
                         input, unknownFields, extensionRegistry, tag)) {
                       done = true;
                     }
      @@ -10356,17 +10155,10 @@ public final class CanalPacket {
                     com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.class, com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.Builder.class);
           }
       
      -    private int bitField0_;
           public static final int DESTINATION_FIELD_NUMBER = 1;
           private volatile java.lang.Object destination_;
           /**
      -     * optional string destination = 1;
      -     */
      -    public boolean hasDestination() {
      -      return ((bitField0_ & 0x00000001) == 0x00000001);
      -    }
      -    /**
      -     * optional string destination = 1;
      +     * string destination = 1;
            */
           public java.lang.String getDestination() {
             java.lang.Object ref = destination_;
      @@ -10376,14 +10168,12 @@ public final class CanalPacket {
               com.google.protobuf.ByteString bs = 
                   (com.google.protobuf.ByteString) ref;
               java.lang.String s = bs.toStringUtf8();
      -        if (bs.isValidUtf8()) {
      -          destination_ = s;
      -        }
      +        destination_ = s;
               return s;
             }
           }
           /**
      -     * optional string destination = 1;
      +     * string destination = 1;
            */
           public com.google.protobuf.ByteString
               getDestinationBytes() {
      @@ -10402,13 +10192,7 @@ public final class CanalPacket {
           public static final int CLIENT_ID_FIELD_NUMBER = 2;
           private volatile java.lang.Object clientId_;
           /**
      -     * optional string client_id = 2;
      -     */
      -    public boolean hasClientId() {
      -      return ((bitField0_ & 0x00000002) == 0x00000002);
      -    }
      -    /**
      -     * optional string client_id = 2;
      +     * string client_id = 2;
            */
           public java.lang.String getClientId() {
             java.lang.Object ref = clientId_;
      @@ -10418,14 +10202,12 @@ public final class CanalPacket {
               com.google.protobuf.ByteString bs = 
                   (com.google.protobuf.ByteString) ref;
               java.lang.String s = bs.toStringUtf8();
      -        if (bs.isValidUtf8()) {
      -          clientId_ = s;
      -        }
      +        clientId_ = s;
               return s;
             }
           }
           /**
      -     * optional string client_id = 2;
      +     * string client_id = 2;
            */
           public com.google.protobuf.ByteString
               getClientIdBytes() {
      @@ -10444,13 +10226,7 @@ public final class CanalPacket {
           public static final int BATCH_ID_FIELD_NUMBER = 3;
           private long batchId_;
           /**
      -     * optional int64 batch_id = 3;
      -     */
      -    public boolean hasBatchId() {
      -      return ((bitField0_ & 0x00000004) == 0x00000004);
      -    }
      -    /**
      -     * optional int64 batch_id = 3;
      +     * int64 batch_id = 3;
            */
           public long getBatchId() {
             return batchId_;
      @@ -10470,13 +10246,13 @@ public final class CanalPacket {
           @java.lang.Override
           public void writeTo(com.google.protobuf.CodedOutputStream output)
                               throws java.io.IOException {
      -      if (((bitField0_ & 0x00000001) == 0x00000001)) {
      +      if (!getDestinationBytes().isEmpty()) {
               com.google.protobuf.GeneratedMessageV3.writeString(output, 1, destination_);
             }
      -      if (((bitField0_ & 0x00000002) == 0x00000002)) {
      +      if (!getClientIdBytes().isEmpty()) {
               com.google.protobuf.GeneratedMessageV3.writeString(output, 2, clientId_);
             }
      -      if (((bitField0_ & 0x00000004) == 0x00000004)) {
      +      if (batchId_ != 0L) {
               output.writeInt64(3, batchId_);
             }
             unknownFields.writeTo(output);
      @@ -10488,13 +10264,13 @@ public final class CanalPacket {
             if (size != -1) return size;
       
             size = 0;
      -      if (((bitField0_ & 0x00000001) == 0x00000001)) {
      +      if (!getDestinationBytes().isEmpty()) {
               size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, destination_);
             }
      -      if (((bitField0_ & 0x00000002) == 0x00000002)) {
      +      if (!getClientIdBytes().isEmpty()) {
               size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, clientId_);
             }
      -      if (((bitField0_ & 0x00000004) == 0x00000004)) {
      +      if (batchId_ != 0L) {
               size += com.google.protobuf.CodedOutputStream
                 .computeInt64Size(3, batchId_);
             }
      @@ -10514,21 +10290,12 @@ public final class CanalPacket {
             com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback other = (com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback) obj;
       
             boolean result = true;
      -      result = result && (hasDestination() == other.hasDestination());
      -      if (hasDestination()) {
      -        result = result && getDestination()
      -            .equals(other.getDestination());
      -      }
      -      result = result && (hasClientId() == other.hasClientId());
      -      if (hasClientId()) {
      -        result = result && getClientId()
      -            .equals(other.getClientId());
      -      }
      -      result = result && (hasBatchId() == other.hasBatchId());
      -      if (hasBatchId()) {
      -        result = result && (getBatchId()
      -            == other.getBatchId());
      -      }
      +      result = result && getDestination()
      +          .equals(other.getDestination());
      +      result = result && getClientId()
      +          .equals(other.getClientId());
      +      result = result && (getBatchId()
      +          == other.getBatchId());
             result = result && unknownFields.equals(other.unknownFields);
             return result;
           }
      @@ -10540,19 +10307,13 @@ public final class CanalPacket {
             }
             int hash = 41;
             hash = (19 * hash) + getDescriptor().hashCode();
      -      if (hasDestination()) {
      -        hash = (37 * hash) + DESTINATION_FIELD_NUMBER;
      -        hash = (53 * hash) + getDestination().hashCode();
      -      }
      -      if (hasClientId()) {
      -        hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER;
      -        hash = (53 * hash) + getClientId().hashCode();
      -      }
      -      if (hasBatchId()) {
      -        hash = (37 * hash) + BATCH_ID_FIELD_NUMBER;
      -        hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
      -            getBatchId());
      -      }
      +      hash = (37 * hash) + DESTINATION_FIELD_NUMBER;
      +      hash = (53 * hash) + getDestination().hashCode();
      +      hash = (37 * hash) + CLIENT_ID_FIELD_NUMBER;
      +      hash = (53 * hash) + getClientId().hashCode();
      +      hash = (37 * hash) + BATCH_ID_FIELD_NUMBER;
      +      hash = (53 * hash) + com.google.protobuf.Internal.hashLong(
      +          getBatchId());
             hash = (29 * hash) + unknownFields.hashCode();
             memoizedHashCode = hash;
             return hash;
      @@ -10687,11 +10448,11 @@ public final class CanalPacket {
             public Builder clear() {
               super.clear();
               destination_ = "";
      -        bitField0_ = (bitField0_ & ~0x00000001);
      +
               clientId_ = "";
      -        bitField0_ = (bitField0_ & ~0x00000002);
      +
               batchId_ = 0L;
      -        bitField0_ = (bitField0_ & ~0x00000004);
      +
               return this;
             }
       
      @@ -10718,21 +10479,9 @@ public final class CanalPacket {
             @java.lang.Override
             public com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback buildPartial() {
               com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback result = new com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback(this);
      -        int from_bitField0_ = bitField0_;
      -        int to_bitField0_ = 0;
      -        if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
      -          to_bitField0_ |= 0x00000001;
      -        }
               result.destination_ = destination_;
      -        if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
      -          to_bitField0_ |= 0x00000002;
      -        }
               result.clientId_ = clientId_;
      -        if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
      -          to_bitField0_ |= 0x00000004;
      -        }
               result.batchId_ = batchId_;
      -        result.bitField0_ = to_bitField0_;
               onBuilt();
               return result;
             }
      @@ -10781,17 +10530,15 @@ public final class CanalPacket {
       
             public Builder mergeFrom(com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback other) {
               if (other == com.alibaba.otter.canal.protocol.CanalPacket.ClientRollback.getDefaultInstance()) return this;
      -        if (other.hasDestination()) {
      -          bitField0_ |= 0x00000001;
      +        if (!other.getDestination().isEmpty()) {
                 destination_ = other.destination_;
                 onChanged();
               }
      -        if (other.hasClientId()) {
      -          bitField0_ |= 0x00000002;
      +        if (!other.getClientId().isEmpty()) {
                 clientId_ = other.clientId_;
                 onChanged();
               }
      -        if (other.hasBatchId()) {
      +        if (other.getBatchId() != 0L) {
                 setBatchId(other.getBatchId());
               }
               this.mergeUnknownFields(other.unknownFields);
      @@ -10822,17 +10569,10 @@ public final class CanalPacket {
               }
               return this;
             }
      -      private int bitField0_;
       
             private java.lang.Object destination_ = "";
             /**
      -       * optional string destination = 1;
      -       */
      -      public boolean hasDestination() {
      -        return ((bitField0_ & 0x00000001) == 0x00000001);
      -      }
      -      /**
      -       * optional string destination = 1;
      +       * string destination = 1;
              */
             public java.lang.String getDestination() {
               java.lang.Object ref = destination_;
      @@ -10840,16 +10580,14 @@ public final class CanalPacket {
                 com.google.protobuf.ByteString bs =
                     (com.google.protobuf.ByteString) ref;
                 java.lang.String s = bs.toStringUtf8();
      -          if (bs.isValidUtf8()) {
      -            destination_ = s;
      -          }
      +          destination_ = s;
                 return s;
               } else {
                 return (java.lang.String) ref;
               }
             }
             /**
      -       * optional string destination = 1;
      +       * string destination = 1;
              */
             public com.google.protobuf.ByteString
                 getDestinationBytes() {
      @@ -10865,36 +10603,37 @@ public final class CanalPacket {
               }
             }
             /**
      -       * optional string destination = 1;
      +       * string destination = 1;
              */
             public Builder setDestination(
                 java.lang.String value) {
               if (value == null) {
           throw new NullPointerException();
         }
      -  bitField0_ |= 0x00000001;
      +  
               destination_ = value;
               onChanged();
               return this;
             }
             /**
      -       * optional string destination = 1;
      +       * string destination = 1;
              */
             public Builder clearDestination() {
      -        bitField0_ = (bitField0_ & ~0x00000001);
      +        
               destination_ = getDefaultInstance().getDestination();
               onChanged();
               return this;
             }
             /**
      -       * optional string destination = 1;
      +       * string destination = 1;
              */
             public Builder setDestinationBytes(
                 com.google.protobuf.ByteString value) {
               if (value == null) {
           throw new NullPointerException();
         }
      -  bitField0_ |= 0x00000001;
      +  checkByteStringIsUtf8(value);
      +        
               destination_ = value;
               onChanged();
               return this;
      @@ -10902,13 +10641,7 @@ public final class CanalPacket {
       
             private java.lang.Object clientId_ = "";
             /**
      -       * optional string client_id = 2;
      -       */
      -      public boolean hasClientId() {
      -        return ((bitField0_ & 0x00000002) == 0x00000002);
      -      }
      -      /**
      -       * optional string client_id = 2;
      +       * string client_id = 2;
              */
             public java.lang.String getClientId() {
               java.lang.Object ref = clientId_;
      @@ -10916,16 +10649,14 @@ public final class CanalPacket {
                 com.google.protobuf.ByteString bs =
                     (com.google.protobuf.ByteString) ref;
                 java.lang.String s = bs.toStringUtf8();
      -          if (bs.isValidUtf8()) {
      -            clientId_ = s;
      -          }
      +          clientId_ = s;
                 return s;
               } else {
                 return (java.lang.String) ref;
               }
             }
             /**
      -       * optional string client_id = 2;
      +       * string client_id = 2;
              */
             public com.google.protobuf.ByteString
                 getClientIdBytes() {
      @@ -10941,36 +10672,37 @@ public final class CanalPacket {
               }
             }
             /**
      -       * optional string client_id = 2;
      +       * string client_id = 2;
              */
             public Builder setClientId(
                 java.lang.String value) {
               if (value == null) {
           throw new NullPointerException();
         }
      -  bitField0_ |= 0x00000002;
      +  
               clientId_ = value;
               onChanged();
               return this;
             }
             /**
      -       * optional string client_id = 2;
      +       * string client_id = 2;
              */
             public Builder clearClientId() {
      -        bitField0_ = (bitField0_ & ~0x00000002);
      +        
               clientId_ = getDefaultInstance().getClientId();
               onChanged();
               return this;
             }
             /**
      -       * optional string client_id = 2;
      +       * string client_id = 2;
              */
             public Builder setClientIdBytes(
                 com.google.protobuf.ByteString value) {
               if (value == null) {
           throw new NullPointerException();
         }
      -  bitField0_ |= 0x00000002;
      +  checkByteStringIsUtf8(value);
      +        
               clientId_ = value;
               onChanged();
               return this;
      @@ -10978,31 +10710,25 @@ public final class CanalPacket {
       
             private long batchId_ ;
             /**
      -       * optional int64 batch_id = 3;
      -       */
      -      public boolean hasBatchId() {
      -        return ((bitField0_ & 0x00000004) == 0x00000004);
      -      }
      -      /**
      -       * optional int64 batch_id = 3;
      +       * int64 batch_id = 3;
              */
             public long getBatchId() {
               return batchId_;
             }
             /**
      -       * optional int64 batch_id = 3;
      +       * int64 batch_id = 3;
              */
             public Builder setBatchId(long value) {
      -        bitField0_ |= 0x00000004;
      +        
               batchId_ = value;
               onChanged();
               return this;
             }
             /**
      -       * optional int64 batch_id = 3;
      +       * int64 batch_id = 3;
              */
             public Builder clearBatchId() {
      -        bitField0_ = (bitField0_ & ~0x00000004);
      +        
               batchId_ = 0L;
               onChanged();
               return this;
      @@ -11010,7 +10736,7 @@ public final class CanalPacket {
             @java.lang.Override
             public final Builder setUnknownFields(
                 final com.google.protobuf.UnknownFieldSet unknownFields) {
      -        return super.setUnknownFields(unknownFields);
      +        return super.setUnknownFieldsProto3(unknownFields);
             }
       
             @java.lang.Override
      @@ -11033,7 +10759,7 @@ public final class CanalPacket {
             return DEFAULT_INSTANCE;
           }
       
      -    @java.lang.Deprecated public static final com.google.protobuf.Parser
      +    private static final com.google.protobuf.Parser
               PARSER = new com.google.protobuf.AbstractParser() {
             @java.lang.Override
             public ClientRollback parsePartialFrom(
      @@ -11130,44 +10856,52 @@ public final class CanalPacket {
         static {
           java.lang.String[] descriptorData = {
             "\n\023CanalProtocol.proto\022 com.alibaba.otter" +
      -      ".canal.protocol\"\312\001\n\006Packet\022\030\n\014magic_numb" +
      -      "er\030\001 \001(\005:\00217\022\022\n\007version\030\002 \001(\005:\0011\022:\n\004type" +
      -      "\030\003 \001(\0162,.com.alibaba.otter.canal.protoco" +
      -      "l.PacketType\022H\n\013compression\030\004 \001(\0162-.com." +
      -      "alibaba.otter.canal.protocol.Compression" +
      -      ":\004NONE\022\014\n\004body\030\005 \001(\014\"<\n\tHeartBeat\022\026\n\016sen" +
      -      "d_timestamp\030\001 \001(\003\022\027\n\017start_timestamp\030\002 \001" +
      -      "(\003\"\217\001\n\tHandshake\022$\n\026communication_encodi" +
      -      "ng\030\001 \001(\t:\004utf8\022\r\n\005seeds\030\002 \001(\014\022M\n\026support" +
      -      "ed_compressions\030\003 \003(\0162-.com.alibaba.otte" +
      -      "r.canal.protocol.Compression\"\274\001\n\nClientA" +
      -      "uth\022\020\n\010username\030\001 \001(\t\022\020\n\010password\030\002 \001(\014\022" +
      -      "\033\n\020net_read_timeout\030\003 \001(\005:\0010\022\034\n\021net_writ" +
      -      "e_timeout\030\004 \001(\005:\0010\022\023\n\013destination\030\005 \001(\t\022" +
      -      "\021\n\tclient_id\030\006 \001(\t\022\016\n\006filter\030\007 \001(\t\022\027\n\017st" +
      -      "art_timestamp\030\010 \001(\003\"3\n\003Ack\022\025\n\nerror_code" +
      -      "\030\001 \001(\005:\0010\022\025\n\rerror_message\030\002 \001(\t\"E\n\tClie" +
      -      "ntAck\022\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030" +
      -      "\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003\"=\n\003Sub\022\023\n\013destin" +
      -      "ation\030\001 \001(\t\022\021\n\tclient_id\030\002 \001(\t\022\016\n\006filter" +
      -      "\030\007 \001(\t\"?\n\005Unsub\022\023\n\013destination\030\001 \001(\t\022\021\n\t" +
      -      "client_id\030\002 \001(\t\022\016\n\006filter\030\007 \001(\t\"\200\001\n\003Get\022" +
      -      "\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030\002 \001(\t\022" +
      -      "\022\n\nfetch_size\030\003 \001(\005\022\023\n\007timeout\030\004 \001(\003:\002-1" +
      -      "\022\017\n\004unit\030\005 \001(\005:\0012\022\027\n\010auto_ack\030\006 \001(\010:\005fal" +
      -      "se\".\n\010Messages\022\020\n\010batch_id\030\001 \001(\003\022\020\n\010mess" +
      -      "ages\030\002 \003(\014\"?\n\004Dump\022\017\n\007journal\030\001 \001(\t\022\020\n\010p" +
      -      "osition\030\002 \001(\003\022\024\n\ttimestamp\030\003 \001(\003:\0010\"J\n\016C" +
      -      "lientRollback\022\023\n\013destination\030\001 \001(\t\022\021\n\tcl" +
      -      "ient_id\030\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003*4\n\013Compr" +
      -      "ession\022\010\n\004NONE\020\001\022\010\n\004ZLIB\020\002\022\010\n\004GZIP\020\003\022\007\n\003" +
      -      "LZF\020\004*\305\001\n\nPacketType\022\r\n\tHANDSHAKE\020\001\022\030\n\024C" +
      -      "LIENTAUTHENTICATION\020\002\022\007\n\003ACK\020\003\022\020\n\014SUBSCR" +
      -      "IPTION\020\004\022\022\n\016UNSUBSCRIPTION\020\005\022\007\n\003GET\020\006\022\014\n" +
      -      "\010MESSAGES\020\007\022\r\n\tCLIENTACK\020\010\022\014\n\010SHUTDOWN\020\t" +
      -      "\022\010\n\004DUMP\020\n\022\r\n\tHEARTBEAT\020\013\022\022\n\016CLIENTROLLB" +
      -      "ACK\020\014B1\n com.alibaba.otter.canal.protoco" +
      -      "lB\013CanalPacketH\001"
      +      ".canal.protocol\"\205\002\n\006Packet\022\026\n\014magic_numb" +
      +      "er\030\001 \001(\005H\000\022\021\n\007version\030\002 \001(\005H\001\022:\n\004type\030\003 " +
      +      "\001(\0162,.com.alibaba.otter.canal.protocol.P" +
      +      "acketType\022D\n\013compression\030\004 \001(\0162-.com.ali" +
      +      "baba.otter.canal.protocol.CompressionH\002\022" +
      +      "\014\n\004body\030\005 \001(\014B\026\n\024magic_number_presentB\021\n" +
      +      "\017version_presentB\025\n\023compression_present\"" +
      +      "<\n\tHeartBeat\022\026\n\016send_timestamp\030\001 \001(\003\022\027\n\017" +
      +      "start_timestamp\030\002 \001(\003\"\255\001\n\tHandshake\022 \n\026c" +
      +      "ommunication_encoding\030\001 \001(\tH\000\022\r\n\005seeds\030\002" +
      +      " \001(\014\022M\n\026supported_compressions\030\003 \001(\0162-.c" +
      +      "om.alibaba.otter.canal.protocol.Compress" +
      +      "ionB \n\036communication_encoding_present\"\363\001" +
      +      "\n\nClientAuth\022\020\n\010username\030\001 \001(\t\022\020\n\010passwo" +
      +      "rd\030\002 \001(\014\022\032\n\020net_read_timeout\030\003 \001(\005H\000\022\033\n\021" +
      +      "net_write_timeout\030\004 \001(\005H\001\022\023\n\013destination" +
      +      "\030\005 \001(\t\022\021\n\tclient_id\030\006 \001(\t\022\016\n\006filter\030\007 \001(" +
      +      "\t\022\027\n\017start_timestamp\030\010 \001(\003B\032\n\030net_read_t" +
      +      "imeout_presentB\033\n\031net_write_timeout_pres" +
      +      "ent\"H\n\003Ack\022\024\n\nerror_code\030\001 \001(\005H\000\022\025\n\rerro" +
      +      "r_message\030\002 \001(\tB\024\n\022error_code_present\"E\n" +
      +      "\tClientAck\022\023\n\013destination\030\001 \001(\t\022\021\n\tclien" +
      +      "t_id\030\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003\"=\n\003Sub\022\023\n\013d" +
      +      "estination\030\001 \001(\t\022\021\n\tclient_id\030\002 \001(\t\022\016\n\006f" +
      +      "ilter\030\007 \001(\t\"?\n\005Unsub\022\023\n\013destination\030\001 \001(" +
      +      "\t\022\021\n\tclient_id\030\002 \001(\t\022\016\n\006filter\030\007 \001(\t\"\257\001\n" +
      +      "\003Get\022\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030\002" +
      +      " \001(\t\022\022\n\nfetch_size\030\003 \001(\005\022\021\n\007timeout\030\004 \001(" +
      +      "\003H\000\022\016\n\004unit\030\005 \001(\005H\001\022\022\n\010auto_ack\030\006 \001(\010H\002B" +
      +      "\021\n\017timeout_presentB\016\n\014unit_presentB\022\n\020au" +
      +      "to_ack_present\".\n\010Messages\022\020\n\010batch_id\030\001" +
      +      " \001(\003\022\020\n\010messages\030\002 \003(\014\"S\n\004Dump\022\017\n\007journa" +
      +      "l\030\001 \001(\t\022\020\n\010position\030\002 \001(\003\022\023\n\ttimestamp\030\003" +
      +      " \001(\003H\000B\023\n\021timestamp_present\"J\n\016ClientRol" +
      +      "lback\022\023\n\013destination\030\001 \001(\t\022\021\n\tclient_id\030" +
      +      "\002 \001(\t\022\020\n\010batch_id\030\003 \001(\003*U\n\013Compression\022\037" +
      +      "\n\033COMPRESSIONCOMPATIBLEPROTO2\020\000\022\010\n\004NONE\020" +
      +      "\001\022\010\n\004ZLIB\020\002\022\010\n\004GZIP\020\003\022\007\n\003LZF\020\004*\346\001\n\nPacke" +
      +      "tType\022\037\n\033PACKAGETYPECOMPATIBLEPROTO2\020\000\022\r" +
      +      "\n\tHANDSHAKE\020\001\022\030\n\024CLIENTAUTHENTICATION\020\002\022" +
      +      "\007\n\003ACK\020\003\022\020\n\014SUBSCRIPTION\020\004\022\022\n\016UNSUBSCRIP" +
      +      "TION\020\005\022\007\n\003GET\020\006\022\014\n\010MESSAGES\020\007\022\r\n\tCLIENTA" +
      +      "CK\020\010\022\014\n\010SHUTDOWN\020\t\022\010\n\004DUMP\020\n\022\r\n\tHEARTBEA" +
      +      "T\020\013\022\022\n\016CLIENTROLLBACK\020\014B1\n com.alibaba.o" +
      +      "tter.canal.protocolB\013CanalPacketH\001b\006prot" +
      +      "o3"
           };
           com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
               new com.google.protobuf.Descriptors.FileDescriptor.    InternalDescriptorAssigner() {
      @@ -11186,7 +10920,7 @@ public final class CanalPacket {
           internal_static_com_alibaba_otter_canal_protocol_Packet_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
               internal_static_com_alibaba_otter_canal_protocol_Packet_descriptor,
      -        new java.lang.String[] { "MagicNumber", "Version", "Type", "Compression", "Body", });
      +        new java.lang.String[] { "MagicNumber", "Version", "Type", "Compression", "Body", "MagicNumberPresent", "VersionPresent", "CompressionPresent", });
           internal_static_com_alibaba_otter_canal_protocol_HeartBeat_descriptor =
             getDescriptor().getMessageTypes().get(1);
           internal_static_com_alibaba_otter_canal_protocol_HeartBeat_fieldAccessorTable = new
      @@ -11198,19 +10932,19 @@ public final class CanalPacket {
           internal_static_com_alibaba_otter_canal_protocol_Handshake_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
               internal_static_com_alibaba_otter_canal_protocol_Handshake_descriptor,
      -        new java.lang.String[] { "CommunicationEncoding", "Seeds", "SupportedCompressions", });
      +        new java.lang.String[] { "CommunicationEncoding", "Seeds", "SupportedCompressions", "CommunicationEncodingPresent", });
           internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor =
             getDescriptor().getMessageTypes().get(3);
           internal_static_com_alibaba_otter_canal_protocol_ClientAuth_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
               internal_static_com_alibaba_otter_canal_protocol_ClientAuth_descriptor,
      -        new java.lang.String[] { "Username", "Password", "NetReadTimeout", "NetWriteTimeout", "Destination", "ClientId", "Filter", "StartTimestamp", });
      +        new java.lang.String[] { "Username", "Password", "NetReadTimeout", "NetWriteTimeout", "Destination", "ClientId", "Filter", "StartTimestamp", "NetReadTimeoutPresent", "NetWriteTimeoutPresent", });
           internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor =
             getDescriptor().getMessageTypes().get(4);
           internal_static_com_alibaba_otter_canal_protocol_Ack_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
               internal_static_com_alibaba_otter_canal_protocol_Ack_descriptor,
      -        new java.lang.String[] { "ErrorCode", "ErrorMessage", });
      +        new java.lang.String[] { "ErrorCode", "ErrorMessage", "ErrorCodePresent", });
           internal_static_com_alibaba_otter_canal_protocol_ClientAck_descriptor =
             getDescriptor().getMessageTypes().get(5);
           internal_static_com_alibaba_otter_canal_protocol_ClientAck_fieldAccessorTable = new
      @@ -11234,7 +10968,7 @@ public final class CanalPacket {
           internal_static_com_alibaba_otter_canal_protocol_Get_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
               internal_static_com_alibaba_otter_canal_protocol_Get_descriptor,
      -        new java.lang.String[] { "Destination", "ClientId", "FetchSize", "Timeout", "Unit", "AutoAck", });
      +        new java.lang.String[] { "Destination", "ClientId", "FetchSize", "Timeout", "Unit", "AutoAck", "TimeoutPresent", "UnitPresent", "AutoAckPresent", });
           internal_static_com_alibaba_otter_canal_protocol_Messages_descriptor =
             getDescriptor().getMessageTypes().get(9);
           internal_static_com_alibaba_otter_canal_protocol_Messages_fieldAccessorTable = new
      @@ -11246,7 +10980,7 @@ public final class CanalPacket {
           internal_static_com_alibaba_otter_canal_protocol_Dump_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
               internal_static_com_alibaba_otter_canal_protocol_Dump_descriptor,
      -        new java.lang.String[] { "Journal", "Position", "Timestamp", });
      +        new java.lang.String[] { "Journal", "Position", "Timestamp", "TimestampPresent", });
           internal_static_com_alibaba_otter_canal_protocol_ClientRollback_descriptor =
             getDescriptor().getMessageTypes().get(11);
           internal_static_com_alibaba_otter_canal_protocol_ClientRollback_fieldAccessorTable = new
      diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto
      index 9de91150..e4e35b6d 100644
      --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto
      +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/CanalProtocol.proto
      @@ -1,4 +1,4 @@
      -syntax = "proto2";
      +syntax = "proto3";
       package com.alibaba.otter.canal.protocol;
       
       option java_package = "com.alibaba.otter.canal.protocol";
      @@ -6,6 +6,7 @@ option java_outer_classname = "CanalPacket";
       option optimize_for = SPEED;
       
       enum Compression {
      +    COMPRESSIONCOMPATIBLEPROTO2 = 0;
           NONE = 1;
           ZLIB = 2;
           GZIP = 3;
      @@ -13,6 +14,8 @@ enum Compression {
       }
       
       enum PacketType {
      +    //compatible
      +    PACKAGETYPECOMPATIBLEPROTO2 = 0;
           HANDSHAKE = 1;
           CLIENTAUTHENTICATION = 2;
           ACK = 3;
      @@ -30,86 +33,122 @@ enum PacketType {
       }
       
       message Packet {
      -    optional int32 magic_number = 1 [default = 17];
      -    optional int32 version = 2 [default = 1];
      -    optional PacketType type = 3;
      -    optional Compression compression = 4 [default = NONE];
      -    optional bytes body = 5;
      +     //[default = 17];
      +     oneof magic_number_present {
      +         int32 magic_number = 1;
      +     }
      +     //[default = 1];
      +     oneof version_present {
      +          int32 version = 2;
      +     };
      +     PacketType type = 3;
      +     //[default = NONE];
      +     oneof compression_present {
      +          Compression compression = 4;
      +     }
      +
      +     bytes body = 5;
       }
       
       message HeartBeat {
      -    optional int64 send_timestamp = 1;
      -    optional int64 start_timestamp = 2;
      +     int64 send_timestamp = 1;
      +     int64 start_timestamp = 2;
       }
       
       message Handshake {
      -    optional string communication_encoding = 1 [default = "utf8"];
      -    optional bytes seeds = 2;
      -    repeated Compression supported_compressions = 3;
      +    //  [default = "utf8"];
      +    oneof communication_encoding_present {
      +        string communication_encoding = 1;
      +    }
      +     bytes seeds = 2;
      +     Compression supported_compressions = 3;
       }
       
       // client authentication
       message ClientAuth {
      -    optional string username = 1;
      -    optional bytes password = 2; // hashed password with seeds from Handshake message
      -    optional int32 net_read_timeout = 3 [default = 0]; // in seconds
      -    optional int32 net_write_timeout = 4 [default = 0]; // in seconds
      -    optional string destination = 5;
      -    optional string client_id = 6;
      -    optional string filter = 7;
      -    optional int64 start_timestamp = 8;
      +    string username = 1;
      +    bytes password = 2; // hashed password with seeds from Handshake message
      +    // [default = 0]
      +    oneof net_read_timeout_present {
      +         int32 net_read_timeout = 3; // in seconds
      +    }
      +    // [default = 0];
      +    oneof net_write_timeout_present {
      +        int32 net_write_timeout = 4; // in seconds
      +    }
      +    string destination = 5;
      +    string client_id = 6;
      +    string filter = 7;
      +    int64 start_timestamp = 8;
       }
       
       message Ack {
      -    optional int32 error_code = 1 [default = 0];
      -    optional string error_message = 2; // if something like compression is not supported, erorr_message will tell about it.
      +    //[default = 0]
      +    oneof error_code_present {
      +        int32 error_code = 1;
      +    }
      +    string error_message = 2; // if something like compression is not supported, erorr_message will tell about it.
       }
       
       message ClientAck {
      -    optional string destination = 1;
      -    optional string client_id = 2;
      -    optional int64 batch_id = 3;
      +    string destination = 1;
      +    string client_id = 2;
      +    int64 batch_id = 3;
       }
       
       // subscription
       message Sub {
      -    optional string destination = 1;
      -    optional string client_id = 2;
      -    optional string filter = 7;
      +    string destination = 1;
      +    string client_id = 2;
      +    string filter = 7;
       }
       
       // Unsubscription
       message Unsub {
      -    optional string destination = 1;
      -    optional string client_id = 2;
      -    optional string filter = 7;
      +    string destination = 1;
      +    string client_id = 2;
      +    string filter = 7;
       }
       
       //  PullRequest
       message Get {
      -    optional string destination = 1;
      -    optional string client_id = 2;
      -    optional int32 fetch_size = 3;
      -    optional int64 timeout = 4 [default = -1]; // 默认-1时代表不控制
      -    optional int32 unit = 5 [default = 2];// 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      -    optional bool auto_ack = 6 [default = false]; // 是否自动ack
      +    string destination = 1;
      +    string client_id = 2;
      +    int32 fetch_size = 3;
      +    //[default = -1]
      +    oneof timeout_present {
      +        int64 timeout = 4; // 默认-1时代表不控制
      +    }
      +    //[default = 2]
      +    oneof unit_present {
      +        int32 unit = 5;// 数字类型,0:纳秒,1:毫秒,2:微秒,3:秒,4:分钟,5:小时,6:天
      +    }
      +    //[default = false]
      +    oneof auto_ack_present {
      +        bool auto_ack = 6; // 是否自动ack
      +    }
      +
       }
       
       //
       message Messages {
      -	optional int64 batch_id = 1;
      +	int64 batch_id = 1;
           repeated bytes messages = 2;
       }
       
       // TBD when new packets are required
       message Dump{
      -    optional string journal = 1;
      -    optional int64  position = 2;
      -    optional int64 timestamp = 3 [default = 0];
      +    string journal = 1;
      +    int64  position = 2;
      +    // [default = 0]
      +    oneof timestamp_present {
      +        int64 timestamp = 3;
      +    }
      +
       }
       
       message ClientRollback{
      -    optional string destination = 1;
      -    optional string client_id = 2;
      -    optional int64 batch_id = 3;
      +    string destination = 1;
      +    string client_id = 2;
      +    int64 batch_id = 3;
       }
      \ No newline at end of file
      diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto b/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto
      index c755921d..1330bbc7 100644
      --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto
      +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto
      @@ -1,4 +1,4 @@
      -syntax = "proto2";
      +syntax = "proto3";
       package com.alibaba.otter.canal.protocol;
       
       option java_package = "com.alibaba.otter.canal.protocol";
      @@ -11,88 +11,101 @@ option optimize_for = SPEED;
        ****************************************************************/
       message Entry {
       	/**协议头部信息**/
      -	optional Header						header 				= 1;
      -	
      -	/**打散后的事件类型**/
      -	optional EntryType					entryType			= 2 [default = ROWDATA];
      -	
      +     Header						header 				= 1;
      +	///**打散后的事件类型**/ [default = ROWDATA]
      +	oneof entryType_present{
      +		EntryType					entryType			= 2;
      +	}
      +
       	/**传输的二进制数组**/
      -	optional bytes						storeValue			= 3;
      +	bytes						storeValue			= 3;
       }
       
       /**message Header**/
       message Header {
      -	/**协议的版本号**/
      -	optional int32 					version				= 1 [default = 1];	
      -	
      +	/**协议的版本号**/  //[default = 1]
      +	oneof version_present {
      +		int32 					version				= 1;
      +	}
      +
      +
       	/**binlog/redolog 文件名**/
      -	optional string					logfileName			= 2;
      -	
      +	string					logfileName			= 2;
      +
       	/**binlog/redolog 文件的偏移位置**/
      -	optional int64 					logfileOffset		= 3;
      -	
      +	int64 					logfileOffset		= 3;
      +
       	/**服务端serverId**/
      -	optional 	int64				serverId         	= 4;	
      -	
      +	int64				serverId         	= 4;
      +
       	/** 变更数据的编码 **/
      -	optional string					serverenCode		= 5;
      -	
      +	string					serverenCode		= 5;
      +
       	/**变更数据的执行时间 **/
      -	optional int64					executeTime			= 6;
      -	
      -	/** 变更数据的来源**/
      -	optional Type					sourceType			= 7 [default = MYSQL];
      -	
      +	int64					executeTime			= 6;
      +
      +	/** 变更数据的来源**/ //[default = MYSQL]
      +	oneof sourceType_present {
      +		Type					sourceType			= 7;
      +	}
      +
      +
       	/** 变更数据的schemaname**/
      -	optional string					schemaName			= 8;
      -	
      +	string					schemaName			= 8;
      +
       	/**变更数据的tablename**/
      -	optional string					tableName			= 9;	
      -	
      +	string					tableName			= 9;
      +
       	/**每个event的长度**/
      -	optional int64					eventLength         = 10;
      -	
      -	/**数据变更类型**/
      -	optional EventType 				eventType			= 11 [default = UPDATE];
      -	
      +	int64					eventLength         = 10;
      +
      +	/**数据变更类型**/  // [default = UPDATE]
      +	oneof eventType_present {
      +		EventType 				eventType			= 11;
      +	}
      +
      +
       	/**预留扩展**/
       	repeated Pair					props				= 12;
       
           /**当前事务的gitd**/
      -	optional string                 gtid                = 13;
      +	string                 gtid                = 13;
       }
       
       /**每个字段的数据结构**/
       message Column {
       	/**字段下标**/
      -	optional int32		index			= 		1;
      -	
      +	int32		index			= 		1;
      +
       	/**字段java中类型**/
      -	optional int32 		sqlType			= 		2;
      -	
      +	int32 		sqlType			= 		2;
      +
       	/**字段名称(忽略大小写),在mysql中是没有的**/
      -	optional string		name			=		3;
      -	
      +	string		name			=		3;
      +
       	/**是否是主键**/
      -	optional bool 		isKey			= 		4;
      -	
      +	bool 		isKey			= 		4;
      +
       	/**如果EventType=UPDATE,用于标识这个字段值是否有修改**/
      -	optional bool		updated			= 		5;
      -	
      -	/** 标识是否为空  **/
      -	optional bool		isNull			= 		6 [default = false];
      -	
      +	bool		updated			= 		5;
      +
      +	/** 标识是否为空  **/ //[default = false]
      +	oneof isNull_present {
      +		bool		isNull			= 		6;
      +	}
      +
      +
       	/**预留扩展**/
      -	repeated Pair		props			=		7;	
      -	
      +	repeated Pair		props			=		7;
      +
       	/** 字段值,timestamp,Datetime是一个时间格式的文本 **/
      -	optional string		value			= 		8;
      -	
      +	string		value			= 		8;
      +
       	/** 对应数据对象原始长度 **/
      -	optional int32		length			= 		9;
      -	
      +	int32		length			= 		9;
      +
       	/**字段mysql类型**/
      -	optional string		mysqlType		= 		10;
      +	string		mysqlType		= 		10;
       }
       
       message RowData {
      @@ -102,73 +115,81 @@ message RowData {
       
       	/** 字段信息,增量数据(修改后,新增后)  **/
       	repeated Column			afterColumns	= 		2;
      -	
      +
       	/**预留扩展**/
      -	repeated Pair			props			=		3;	
      +	repeated Pair			props			=		3;
       }
       
       /**message row 每行变更数据的数据结构**/
       message RowChange {
       
       	/**tableId,由数据库产生**/
      -	optional int64	 		tableId			=		1;
      -	
      -	/**数据变更类型**/
      -	optional EventType 		eventType		= 		2 [default = UPDATE];
      -	
      -	/** 标识是否是ddl语句  **/
      -	optional bool			isDdl			= 		10 [default = false];
      +	int64	 		tableId			=		1;
      +
      +
      +	/**数据变更类型**/ //[default = UPDATE]
      +	oneof eventType_present {
      +		EventType 		eventType		= 		2;
      +	}
      +
      +
      +	/** 标识是否是ddl语句  **/ // [default = false]
      +	oneof isDdl_present {
      +		bool			isDdl			= 		10;
      +	}
      +
       
       	/** ddl/query的sql语句  **/
      -	optional string			sql 			= 		11;
      -	
      +	string			sql 			= 		11;
      +
       	/** 一次数据库变更可能存在多行  **/
       	repeated RowData		rowDatas		= 		12;
      -	
      +
       	/**预留扩展**/
      -	repeated Pair			props			=		13;	
      -	
      +	repeated Pair			props			=		13;
      +
       	/** ddl/query的schemaName,会存在跨库ddl,需要保留执行ddl的当前schemaName  **/
      -	optional string			ddlSchemaName 	= 		14;
      +	string			ddlSchemaName 	= 		14;
       }
       
       /**开始事务的一些信息**/
       message TransactionBegin{
      -	
      +
       	/**已废弃,请使用header里的executeTime**/
      -	optional int64			executeTime		=		1;
      -	
      +	int64			executeTime		=		1;
      +
       	/**已废弃,Begin里不提供事务id**/
      -	optional string			transactionId	=		2;
      -	
      +	string			transactionId	=		2;
      +
       	/**预留扩展**/
      -	repeated Pair			props			=		3;	
      -	
      +	repeated Pair			props			=		3;
      +
       	/**执行的thread Id**/
      -	optional int64			threadId		=		4;
      +	int64			threadId		=		4;
       }
       
       /**结束事务的一些信息**/
       message TransactionEnd{
      -	
      +
       	/**已废弃,请使用header里的executeTime**/
      -	optional int64			executeTime		=		1;
      -	
      +	int64			executeTime		=		1;
      +
       	/**事务号**/
      -	optional string			transactionId	=		2;
      -	
      +	string			transactionId	=		2;
      +
       	/**预留扩展**/
      -	repeated Pair			props			=		3;	
      +	repeated Pair			props			=		3;
       }
       
       /**预留扩展**/
       message Pair{
      -	optional string 		key				= 			1;	
      -	optional string 		value			= 			2;	
      +	string 		key				= 			1;
      +	string 		value			= 			2;
       }
       
       /**打散后的事件类型,主要用于标识事务的开始,变更数据,结束**/
       enum EntryType{
      +	ENTRYTYPECOMPATIBLEPROTO2 = 0;
       	TRANSACTIONBEGIN 		=		1;
       	ROWDATA					=		2;
       	TRANSACTIONEND			=		3;
      @@ -179,6 +200,7 @@ enum EntryType{
       
       /** 事件类型 **/
       enum EventType {
      +	EVENTTYPECOMPATIBLEPROTO2 = 0;
           INSERT 		= 		1;
           UPDATE 		= 		2;
           DELETE 		= 		3;
      @@ -201,6 +223,7 @@ enum EventType {
       
       /**数据库类型**/
       enum Type {
      +	TYPECOMPATIBLEPROTO2 = 0;
           ORACLE		= 		1;
           MYSQL		= 		2;
           PGSQL		= 		3;
      diff --git a/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithNettyTest.java b/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithNettyTest.java
      index 3b3c0ee0..673918fe 100644
      --- a/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithNettyTest.java
      +++ b/server/src/test/java/com/alibaba/otter/canal/server/CanalServerWithNettyTest.java
      @@ -66,6 +66,8 @@ public class CanalServerWithNettyTest {
       
           @Test
           public void testAuth() {
      +
      +
               try {
                   SocketChannel channel = SocketChannel.open();
                   channel.connect(new InetSocketAddress("127.0.0.1", 1088));
      @@ -80,7 +82,7 @@ public class CanalServerWithNettyTest {
                   }
                   //
                   Handshake handshake = Handshake.parseFrom(p.getBody());
      -            System.out.println(handshake.getSupportedCompressionsList());
      +            System.out.println(handshake.getSupportedCompressions());
                   //
                   ClientAuth ca = ClientAuth.newBuilder()
                       .setUsername("")
      
      From e8e00324044f54a385345f99229c3c8cd38b5bd5 Mon Sep 17 00:00:00 2001
      From: mcy 
      Date: Fri, 14 Sep 2018 11:22:47 +0800
      Subject: [PATCH 134/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=89=81=E5=B9=B3Mes?=
       =?UTF-8?q?sage=E7=94=A8=E4=BA=8E=E5=8D=A1=E5=A4=AB=E5=8D=A1=E6=95=B0?=
       =?UTF-8?q?=E6=8D=AE=E6=8A=95=E9=80=92?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../client/adapter/CanalOuterAdapter.java     |   8 +-
       .../adapter/support/CanalClientConfig.java    |  10 +
       .../client/adapter/support/JdbcTypeUtil.java  |   4 +-
       .../client/adapter/support/MessageUtil.java   |  58 +++-
       .../client/adapter/hbase/HbaseAdapter.java    |  23 +-
       .../adapter/logger/LoggerAdapterExample.java  |  15 +-
       .../loader/AbstractCanalAdapterWorker.java    |  60 +++-
       .../loader/CanalAdapterKafkaWorker.java       |  99 +++---
       .../adapter/loader/CanalAdapterLoader.java    |   7 +-
       .../src/main/resources/canal-client.yml       |  31 +-
       .../client/kafka/KafkaCanalConnector.java     | 162 +++++-----
       .../client/kafka/KafkaCanalConnectors.java    |  27 +-
       .../kafka/CanalKafkaClientExample.java        |  63 ++--
       .../running/kafka/KafkaClientRunningTest.java |   9 +-
       deployer/src/main/resources/kafka.yml         |   5 +-
       .../otter/canal/protocol/FlatMessage.java     | 286 ++++++++++++++++++
       .../otter/canal/kafka/CanalKafkaProducer.java |  50 ++-
       .../otter/canal/kafka/KafkaProperties.java    |   9 +
       .../embedded/CanalServerWithEmbedded.java     |   7 +-
       19 files changed, 686 insertions(+), 247 deletions(-)
       create mode 100644 protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java
      
      diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java
      index 53368472..0871eb74 100644
      --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java
      +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java
      @@ -1,7 +1,9 @@
       package com.alibaba.otter.canal.client.adapter;
       
       import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
      +import com.alibaba.otter.canal.client.adapter.support.Dml;
       import com.alibaba.otter.canal.client.adapter.support.SPI;
      +import com.alibaba.otter.canal.protocol.FlatMessage;
       import com.alibaba.otter.canal.protocol.Message;
       
       /**
      @@ -23,9 +25,11 @@ public interface CanalOuterAdapter {
           /**
            * 往适配器中写入数据
            *
      -     * @param message message数据包
      +     * @param dml 数据包
            */
      -    void writeOut(Message message);
      +    void writeOut(Dml dml);
      +
      +    // void writeOut(FlatMessage flatMessage);
       
           /**
            * 外部适配器销毁接口
      diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java
      index f6fa7707..25b339fe 100644
      --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java
      +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java
      @@ -20,6 +20,8 @@ public class CanalClientConfig {
       
           private String              bootstrapServers;
       
      +    private Boolean             flatMessage = true;
      +
           private List    kafkaTopics;
       
           private List canalInstances;
      @@ -56,6 +58,14 @@ public class CanalClientConfig {
               this.bootstrapServers = bootstrapServers;
           }
       
      +    public Boolean getFlatMessage() {
      +        return flatMessage;
      +    }
      +
      +    public void setFlatMessage(Boolean flatMessage) {
      +        this.flatMessage = flatMessage;
      +    }
      +
           public List getKafkaTopics() {
               return kafkaTopics;
           }
      diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java
      index fd65589f..777770ef 100644
      --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java
      +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java
      @@ -21,7 +21,7 @@ public class JdbcTypeUtil {
       
           private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class);
       
      -    public static Object typeConvert(String tableName, String columnName, String value, int sqlType, String mysqlType) {
      +    public static Object typeConvert(String columnName, String value, int sqlType, String mysqlType) {
               if (value == null || value.equals("")) {
                   return null;
               }
      @@ -96,7 +96,7 @@ public class JdbcTypeUtil {
                   }
                   return res;
               } catch (Exception e) {
      -            logger.error("table: {} column: {}, failed convert type {} to {}", tableName, columnName, value, sqlType);
      +            logger.error("table: {} column: {}, failed convert type {} to {}", columnName, value, sqlType);
                   return value;
               }
           }
      diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java
      index 7f3722f1..4765ec30 100644
      --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java
      +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java
      @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.client.adapter.support;
       import java.util.*;
       
       import com.alibaba.otter.canal.protocol.CanalEntry;
      +import com.alibaba.otter.canal.protocol.FlatMessage;
       import com.alibaba.otter.canal.protocol.Message;
       
       /**
      @@ -63,8 +64,7 @@ public class MessageUtil {
       
                           for (CanalEntry.Column column : columns) {
                               row.put(column.getName(),
      -                            JdbcTypeUtil.typeConvert(dml.getTable(),
      -                                column.getName(),
      +                            JdbcTypeUtil.typeConvert(column.getName(),
                                       column.getValue(),
                                       column.getSqlType(),
                                       column.getMysqlType()));
      @@ -82,8 +82,7 @@ public class MessageUtil {
                               for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
                                   if (updateSet.contains(column.getName())) {
                                       rowOld.put(column.getName(),
      -                                    JdbcTypeUtil.typeConvert(dml.getTable(),
      -                                        column.getName(),
      +                                    JdbcTypeUtil.typeConvert(column.getName(),
                                               column.getValue(),
                                               column.getSqlType(),
                                               column.getMysqlType()));
      @@ -106,6 +105,57 @@ public class MessageUtil {
               }
           }
       
      +    public static Dml flatMessage2Dml(FlatMessage flatMessage) {
      +        if (flatMessage == null) {
      +            return null;
      +        }
      +        Dml dml = new Dml();
      +        dml.setDatabase(flatMessage.getDatabase());
      +        dml.setTable(flatMessage.getTable());
      +        dml.setType(flatMessage.getType());
      +        dml.setTs(flatMessage.getTs());
      +        dml.setSql(flatMessage.getSql());
      +        if (flatMessage.getSqlType() == null || flatMessage.getMysqlType() == null) {
      +            throw new RuntimeException("SqlType or mysqlType is null");
      +        }
      +        List> data = flatMessage.getData();
      +        if (data != null) {
      +            dml.setData(changeRows(data, flatMessage.getSqlType(), flatMessage.getMysqlType()));
      +        }
      +        List> old = flatMessage.getOld();
      +        if (old != null) {
      +            dml.setOld(changeRows(old, flatMessage.getSqlType(), flatMessage.getMysqlType()));
      +        }
      +        return dml;
      +    }
      +
      +    private static List> changeRows(List> rows, Map sqlTypes,
      +                                                        Map mysqlTypes) {
      +        List> result = new ArrayList<>();
      +        for (Map row : rows) {
      +            Map resultRow = new LinkedHashMap<>();
      +            for (Map.Entry entry : row.entrySet()) {
      +                String columnName = entry.getKey();
      +                String columnValue = entry.getValue();
      +
      +                Integer sqlType = sqlTypes.get(columnName);
      +                if (sqlType == null) {
      +                    continue;
      +                }
      +
      +                String mysqlType = mysqlTypes.get(columnName);
      +                if (mysqlType == null) {
      +                    continue;
      +                }
      +
      +                Object finalValue = JdbcTypeUtil.typeConvert(columnName, columnValue, sqlType, mysqlType);
      +                resultRow.put(columnName, finalValue);
      +            }
      +            result.add(resultRow);
      +        }
      +        return result;
      +    }
      +
           public interface Consumer {
       
               void accept(T t);
      diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java
      index 03103b8b..bbd8fb1c 100644
      --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java
      +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java
      @@ -4,6 +4,7 @@ import java.io.IOException;
       import java.util.HashMap;
       import java.util.Map;
       
      +import com.alibaba.otter.canal.protocol.FlatMessage;
       import org.apache.commons.lang.StringUtils;
       import org.apache.hadoop.conf.Configuration;
       import org.apache.hadoop.hbase.HBaseConfiguration;
      @@ -86,20 +87,14 @@ public class HbaseAdapter implements CanalOuterAdapter {
           }
       
           @Override
      -    public void writeOut(Message message) {
      -        MessageUtil.parse4Dml(message, new MessageUtil.Consumer() {
      -
      -            @Override
      -            public void accept(Dml dml) {
      -                if (dml == null) {
      -                    return;
      -                }
      -                String database = dml.getDatabase();
      -                String table = dml.getTable();
      -                MappingConfig config = mappingConfigCache.get(database + "-" + table);
      -                hbaseSyncService.sync(config, dml);
      -            }
      -        });
      +    public void writeOut(Dml dml) {
      +        if (dml == null) {
      +            return;
      +        }
      +        String database = dml.getDatabase();
      +        String table = dml.getTable();
      +        MappingConfig config = mappingConfigCache.get(database + "-" + table);
      +        hbaseSyncService.sync(config, dml);
           }
       
           @Override
      diff --git a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java
      index c6c1cb44..c72a0ee0 100644
      --- a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java
      +++ b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java
      @@ -6,9 +6,7 @@ import org.slf4j.LoggerFactory;
       import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
       import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration;
       import com.alibaba.otter.canal.client.adapter.support.Dml;
      -import com.alibaba.otter.canal.client.adapter.support.MessageUtil;
       import com.alibaba.otter.canal.client.adapter.support.SPI;
      -import com.alibaba.otter.canal.protocol.Message;
       
       /**
        * 外部适配器示例
      @@ -22,20 +20,13 @@ public class LoggerAdapterExample implements CanalOuterAdapter {
           private Logger logger = LoggerFactory.getLogger(this.getClass());
       
           @Override
      -    public void writeOut(Message message) {
      -        // 直接输出日志信息
      -        MessageUtil.parse4Dml(message, new MessageUtil.Consumer() {
      +    public void init(CanalOuterAdapterConfiguration configuration) {
       
      -            @Override
      -            public void accept(Dml dml) {
      -                logger.info(dml.toString());
      -            }
      -        });
           }
       
           @Override
      -    public void init(CanalOuterAdapterConfiguration configuration) {
      -
      +    public void writeOut(Dml dml) {
      +        logger.info(dml.toString());
           }
       
           @Override
      diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java
      index 9dd328e3..46e5922f 100644
      --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java
      +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java
      @@ -7,10 +7,13 @@ import java.util.concurrent.ExecutionException;
       import java.util.concurrent.ExecutorService;
       import java.util.concurrent.Future;
       
      -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
       import org.slf4j.Logger;
       import org.slf4j.LoggerFactory;
       
      +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
      +import com.alibaba.otter.canal.client.adapter.support.Dml;
      +import com.alibaba.otter.canal.client.adapter.support.MessageUtil;
      +import com.alibaba.otter.canal.protocol.FlatMessage;
       import com.alibaba.otter.canal.protocol.Message;
       
       /**
      @@ -47,9 +50,16 @@ public abstract class AbstractCanalAdapterWorker {
                       public Boolean call() {
                           try {
                               // 组内适配器穿行运行,尽量不要配置组内适配器
      -                        for (CanalOuterAdapter c : adapters) {
      +                        for (final CanalOuterAdapter c : adapters) {
                                   long begin = System.currentTimeMillis();
      -                            c.writeOut(message);
      +                            MessageUtil.parse4Dml(message, new MessageUtil.Consumer() {
      +
      +                                @Override
      +                                public void accept(Dml dml) {
      +                                    c.writeOut(dml);
      +                                }
      +                            });
      +
                                   if (logger.isDebugEnabled()) {
                                       logger.debug("{} elapsed time: {}",
                                           c.getClass().getName(),
      @@ -71,7 +81,49 @@ public abstract class AbstractCanalAdapterWorker {
                               logger.error("Outer adapter write failed");
                           }
                       } catch (InterruptedException | ExecutionException e) {
      -                    logger.error(e.getMessage(), e);
      +                    // ignore
      +                }
      +            }
      +        }
      +    }
      +
      +    protected void writeOut(final FlatMessage flatMessage) {
      +        List> futures = new ArrayList<>();
      +        // 组间适配器并行运行
      +        for (List outerAdapters : canalOuterAdapters) {
      +            final List adapters = outerAdapters;
      +            futures.add(groupInnerExecutorService.submit(new Callable() {
      +
      +                @Override
      +                public Boolean call() {
      +                    try {
      +                        // 组内适配器穿行运行,尽量不要配置组内适配器
      +                        for (CanalOuterAdapter c : adapters) {
      +                            long begin = System.currentTimeMillis();
      +                            Dml dml = MessageUtil.flatMessage2Dml(flatMessage);
      +                            c.writeOut(dml);
      +                            if (logger.isDebugEnabled()) {
      +                                logger.debug("{} elapsed time: {}",
      +                                    c.getClass().getName(),
      +                                    (System.currentTimeMillis() - begin));
      +                            }
      +                        }
      +                        return true;
      +                    } catch (Exception e) {
      +                        return false;
      +                    }
      +                }
      +            }));
      +
      +            // 等待所有适配器写入完成
      +            // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成
      +            for (Future f : futures) {
      +                try {
      +                    if (!f.get()) {
      +                        logger.error("Outer adapter write failed");
      +                    }
      +                } catch (InterruptedException | ExecutionException e) {
      +                    // ignore
                       }
                   }
               }
      diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java
      index 4a7bd7a3..d7324cda 100644
      --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java
      +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java
      @@ -12,6 +12,7 @@ import org.apache.kafka.common.errors.WakeupException;
       import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
       import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector;
       import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors;
      +import com.alibaba.otter.canal.protocol.FlatMessage;
       import com.alibaba.otter.canal.protocol.Message;
       
       /**
      @@ -26,14 +27,17 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
       
           private String              topic;
       
      -    public CanalAdapterKafkaWorker(String zkServers, String bootstrapServers, String topic, String groupId,
      -                                   List> canalOuterAdapters){
      +    private boolean             flatMessage;
      +
      +    public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId,
      +                                   List> canalOuterAdapters, boolean flatMessage){
               this.canalOuterAdapters = canalOuterAdapters;
               this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
               this.topic = topic;
               this.canalDestination = topic;
      -        connector = KafkaCanalConnectors.newKafkaConnector(zkServers, bootstrapServers, topic, null, groupId);
      -        connector.setSessionTimeout(5L, TimeUnit.MINUTES);
      +        this.flatMessage = flatMessage;
      +        connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage);
      +        // connector.setSessionTimeout(1L, TimeUnit.MINUTES);
       
               // super.initSwitcher(topic);
           }
      @@ -104,56 +108,47 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
                           try {
                               // switcher.get(); //等待开关开启
       
      -                        final Message message = connector.getWithoutAck();
      +                        List messages;
      +                        if (!flatMessage) {
      +                            messages = connector.getWithoutAck();
      +                        } else {
      +                            messages = connector.getFlatMessageWithoutAck(100L, TimeUnit.MILLISECONDS);
      +                        }
      +                        if (messages != null) {
      +                            for (final Object message : messages) {
      +                                executing.set(true);
      +                                if (message != null) {
      +                                    executor.submit(new Runnable() {
       
      -                        executing.set(true);
      -                        if (message != null) {
      -                            executor.submit(new Runnable() {
      +                                        @Override
      +                                        public void run() {
      +                                            try {
      +                                                if (message instanceof FlatMessage) {
      +                                                    writeOut((FlatMessage) message);
      +                                                } else {
      +                                                    writeOut((Message) message);
      +                                                }
      +                                            } catch (Exception e) {
      +                                                logger.error(e.getMessage(), e);
      +                                            } finally {
      +                                                executing.compareAndSet(true, false);
      +                                            }
      +                                        }
      +                                    });
       
      -                                @Override
      -                                public void run() {
      -                                    try {
      -                                        if (logger.isDebugEnabled()) {
      -                                            logger.debug("topic: {} batchId: {} batchSize: {} ",
      -                                                topic,
      -                                                message.getId(),
      -                                                message.getEntries().size());
      +                                    // 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端
      +                                    long currentTS = System.currentTimeMillis();
      +                                    while (executing.get()) {
      +                                        // 大于10秒未消费完ack一次keep alive
      +                                        if (System.currentTimeMillis() - currentTS > 10000) {
      +                                            connector.ack();
      +                                            currentTS = System.currentTimeMillis();
                                               }
      -                                        long begin = System.currentTimeMillis();
      -                                        writeOut(message);
      -                                        long now = System.currentTimeMillis();
      -                                        if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) {
      -                                            logger.error("topic: {} batchId {} elapsed time: {} ms",
      -                                                topic,
      -                                                message.getId(),
      -                                                now - begin);
      -                                        }
      -                                        if (logger.isDebugEnabled()) {
      -                                            logger.debug("topic: {} batchId {} elapsed time: {} ms",
      -                                                topic,
      -                                                message.getId(),
      -                                                now - begin);
      -                                        }
      -                                    } catch (Exception e) {
      -                                        logger.error(e.getMessage(), e);
      -                                    } finally {
      -                                        executing.compareAndSet(true, false);
                                           }
                                       }
      -                            });
      -
      -                            // 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端
      -                            long currentTS = System.currentTimeMillis();
      -                            while (executing.get()) {
      -                                // 大于1分钟未消费完ack一次keep alive
      -                                if (System.currentTimeMillis() - currentTS > 60000) {
      -                                    connector.ack();
      -                                    currentTS = System.currentTimeMillis();
      -                                }
                                   }
      -                        } else {
      -                            connector.ack();
                               }
      +                        connector.ack();
                           } catch (CommitFailedException e) {
                               logger.warn(e.getMessage());
                           } catch (Exception e) {
      @@ -161,19 +156,23 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
                               TimeUnit.SECONDS.sleep(1L);
                           }
                       }
      -            } catch (Exception e) {
      +            } catch (
      +
      +            Exception e) {
                       logger.error(e.getMessage(), e);
                   }
               }
       
               executor.shutdown();
       
      -        try {
      +        try
      +
      +        {
                   connector.unsubscribe();
               } catch (WakeupException e) {
                   // No-op. Continue process
               }
      -        connector.disconnnect();
      +        connector.disconnect();
               logger.info("=============> Disconnect topic: {} <=============", this.topic);
           }
       }
      diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java
      index 14efe493..15945b6c 100644
      --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java
      +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java
      @@ -59,6 +59,8 @@ public class CanalAdapterLoader {
               }
               String zkHosts = this.canalClientConfig.getZookeeperHosts();
       
      +        boolean flatMessage = this.canalClientConfig.getFlatMessage();
      +
               // if (zkHosts == null && sa == null) {
               // throw new RuntimeException("Blank config property: canalServerHost or
               // zookeeperHosts");
      @@ -104,11 +106,12 @@ public class CanalAdapterLoader {
                           canalOuterAdapterGroups.add(canalOuterAdapters);
       
                           // String zkServers = canalClientConfig.getZookeeperHosts();
      -                    CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts,
      +                    CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(
                               canalClientConfig.getBootstrapServers(),
                               kafkaTopic.getTopic(),
                               group.getGroupId(),
      -                        canalOuterAdapterGroups);
      +                        canalOuterAdapterGroups,
      +                        flatMessage);
                           canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker);
                           canalKafkaWorker.start();
                           logger.info("Start adapter for canal-client kafka topic: {} succeed",
      diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml
      index 12e016a7..6c8874c3 100644
      --- a/client-launcher/src/main/resources/canal-client.yml
      +++ b/client-launcher/src/main/resources/canal-client.yml
      @@ -1,21 +1,22 @@
      -canalServerHost: 127.0.0.1:11111
      +#canalServerHost: 127.0.0.1:11111
       #zookeeperHosts: slave1:2181
      -#bootstrapServers: slave1:6667,slave2:6667
      +bootstrapServers: slave1:6667,slave2:6667
      +flatMessage: true
       
      -canalInstances:
      -- instance: example
      -  adapterGroups:
      -  - outAdapters:
      -    - name: logger
      -    - name: hbase
      -      hosts: slave1:2181
      -      properties: {znodeParent: "/hbase-unsecure"}
      -#kafkaTopics:
      -#- topic: example
      -#  groups:
      -#  - groupId: example_g1
      -#    outAdapters:
      +#canalInstances:
      +#- instance: example
      +#  adapterGroups:
      +#  - outAdapters:
       #    - name: logger
       #    - name: hbase
       #      hosts: slave1:2181
       #      properties: {znodeParent: "/hbase-unsecure"}
      +kafkaTopics:
      +- topic: example
      +  groups:
      +  - groupId: egroup
      +    outAdapters:
      +    - name: logger
      +#    - name: hbase
      +#      hosts: slave1:2181
      +#      properties: {znodeParent: "/hbase-unsecure"}
      diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java
      index 7afb9c09..ad94ba98 100644
      --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java
      +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java
      @@ -1,19 +1,20 @@
       package com.alibaba.otter.canal.client.kafka;
       
      +import java.util.ArrayList;
       import java.util.Collections;
      +import java.util.List;
       import java.util.Properties;
       import java.util.concurrent.TimeUnit;
       
      +import org.apache.kafka.clients.consumer.ConsumerRecord;
       import org.apache.kafka.clients.consumer.ConsumerRecords;
       import org.apache.kafka.clients.consumer.KafkaConsumer;
       import org.apache.kafka.common.TopicPartition;
       import org.apache.kafka.common.serialization.StringDeserializer;
       
      -import com.alibaba.otter.canal.client.kafka.running.ClientRunningData;
      -import com.alibaba.otter.canal.common.utils.AddressUtils;
      -import com.alibaba.otter.canal.common.zookeeper.ZkClientx;
      +import com.alibaba.fastjson.JSON;
      +import com.alibaba.otter.canal.protocol.FlatMessage;
       import com.alibaba.otter.canal.protocol.Message;
      -import com.alibaba.otter.canal.protocol.exception.CanalClientException;
       
       /**
        * canal kafka 数据操作客户端
      @@ -24,18 +25,19 @@ import com.alibaba.otter.canal.protocol.exception.CanalClientException;
       public class KafkaCanalConnector {
       
           private KafkaConsumer kafkaConsumer;
      +    private KafkaConsumer  kafkaConsumer2;   // 用于扁平message的数据消费
           private String                         topic;
           private Integer                        partition;
           private Properties                     properties;
      -    // private ClientRunningMonitor runningMonitor; // 运行控制
      -    // private BooleanMutex mutex = new BooleanMutex(false);
      -    private ZkClientx                      zkClientx;
           private volatile boolean               connected = false;
           private volatile boolean               running   = false;
      +    private boolean                        flatMessage;
       
      -    public KafkaCanalConnector(String zkServers, String servers, String topic, Integer partition, String groupId){
      +    public KafkaCanalConnector(String servers, String topic, Integer partition, String groupId,
      +                               boolean flatMessage){
               this.topic = topic;
               this.partition = partition;
      +        this.flatMessage = flatMessage;
       
               properties = new Properties();
               properties.put("bootstrap.servers", servers);
      @@ -45,32 +47,13 @@ public class KafkaCanalConnector {
               properties.put("auto.offset.reset", "latest"); // 如果没有offset则从最后的offset开始读
               properties.put("request.timeout.ms", "40000"); // 必须大于session.timeout.ms的设置
               properties.put("session.timeout.ms", "30000"); // 默认为30秒
      -        properties.put("max.poll.records", "1"); // 一次只取一条message数据
      +        properties.put("max.poll.records", "100");
               properties.put("key.deserializer", StringDeserializer.class.getName());
      -        properties.put("value.deserializer", MessageDeserializer.class.getName());
      -
      -        if (zkServers != null) {
      -            zkClientx = new ZkClientx(zkServers);
      -
      -            ClientRunningData clientData = new ClientRunningData();
      -            clientData.setGroupId(groupId);
      -            clientData.setAddress(AddressUtils.getHostIp());
      -
      -            // runningMonitor = new ClientRunningMonitor();
      -            // runningMonitor.setTopic(topic);
      -            // runningMonitor.setZkClient(zkClientx);
      -            // runningMonitor.setClientData(clientData);
      -            // runningMonitor.setListener(new ClientRunningListener() {
      -            // public void processActiveEnter() {
      -            // mutex.set(true);
      -            // }
      -            //
      -            // public void processActiveExit() {
      -            // mutex.set(false);
      -            // }
      -            // });
      +        if (!flatMessage) {
      +            properties.put("value.deserializer", MessageDeserializer.class.getName());
      +        } else {
      +            properties.put("value.deserializer", StringDeserializer.class.getName());
               }
      -
           }
       
           /**
      @@ -93,57 +76,36 @@ public class KafkaCanalConnector {
                   return;
               }
       
      -        // if (runningMonitor != null) {
      -        // if (!runningMonitor.isStart()) {
      -        // runningMonitor.start();
      -        // }
      -        // }
      -
               connected = true;
       
      -        if (kafkaConsumer == null) {
      +        if (kafkaConsumer == null && !flatMessage) {
                   kafkaConsumer = new KafkaConsumer(properties);
               }
      +        if (kafkaConsumer2 == null && flatMessage) {
      +            kafkaConsumer2 = new KafkaConsumer(properties);
      +        }
           }
       
           /**
            * 关闭链接
            */
      -    public void disconnnect() {
      -        kafkaConsumer.close();
      +    public void disconnect() {
      +        if (kafkaConsumer != null) {
      +            kafkaConsumer.close();
      +        }
      +        if (kafkaConsumer2 != null) {
      +            kafkaConsumer2.close();
      +        }
       
               connected = false;
      -        // if (runningMonitor.isStart()) {
      -        // runningMonitor.stop();
      -        // }
           }
       
           private void waitClientRunning() {
      -        try {
      -            if (zkClientx != null) {
      -                if (!connected) {// 未调用connect
      -                    throw new CanalClientException("should connect first");
      -                }
      -
      -                running = true;
      -                // mutex.get();// 阻塞等待
      -            } else {
      -                // 单机模式直接设置为running
      -                running = true;
      -            }
      -        } catch (Exception e) {
      -            Thread.currentThread().interrupt();
      -            throw new CanalClientException(e);
      -        }
      +        running = true;
           }
       
           public boolean checkValid() {
      -        if (zkClientx != null) {
      -            // return mutex.state();
      -            return true;
      -        } else {
      -            return true;// 默认都放过
      -        }
      +        return true;// 默认都放过
           }
       
           /**
      @@ -156,10 +118,20 @@ public class KafkaCanalConnector {
               }
       
               if (partition == null) {
      -            kafkaConsumer.subscribe(Collections.singletonList(topic));
      +            if (kafkaConsumer != null) {
      +                kafkaConsumer.subscribe(Collections.singletonList(topic));
      +            }
      +            if (kafkaConsumer2 != null) {
      +                kafkaConsumer2.subscribe(Collections.singletonList(topic));
      +            }
               } else {
                   TopicPartition topicPartition = new TopicPartition(topic, partition);
      -            kafkaConsumer.assign(Collections.singletonList(topicPartition));
      +            if (kafkaConsumer != null) {
      +                kafkaConsumer.assign(Collections.singletonList(topicPartition));
      +            }
      +            if (kafkaConsumer2 != null) {
      +                kafkaConsumer2.assign(Collections.singletonList(topicPartition));
      +            }
               }
           }
       
      @@ -172,7 +144,12 @@ public class KafkaCanalConnector {
                   return;
               }
       
      -        kafkaConsumer.unsubscribe();
      +        if (kafkaConsumer != null) {
      +            kafkaConsumer.unsubscribe();
      +        }
      +        if (kafkaConsumer2 != null) {
      +            kafkaConsumer2.unsubscribe();
      +        }
           }
       
           /**
      @@ -180,22 +157,22 @@ public class KafkaCanalConnector {
            *
            * @return
            */
      -    public Message get() {
      +    public List get() {
               return get(100L, TimeUnit.MILLISECONDS);
           }
       
      -    public Message get(Long timeout, TimeUnit unit) {
      +    public List get(Long timeout, TimeUnit unit) {
               waitClientRunning();
               if (!running) {
                   return null;
               }
       
      -        Message message = getWithoutAck(timeout, unit);
      +        List messages = getWithoutAck(timeout, unit);
               this.ack();
      -        return message;
      +        return messages;
           }
       
      -    public Message getWithoutAck() {
      +    public List getWithoutAck() {
               return getWithoutAck(100L, TimeUnit.MILLISECONDS);
           }
       
      @@ -204,7 +181,7 @@ public class KafkaCanalConnector {
            *
            * @return
            */
      -    public Message getWithoutAck(Long timeout, TimeUnit unit) {
      +    public List getWithoutAck(Long timeout, TimeUnit unit) {
               waitClientRunning();
               if (!running) {
                   return null;
      @@ -213,7 +190,33 @@ public class KafkaCanalConnector {
               ConsumerRecords records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据
       
               if (!records.isEmpty()) {
      -            return records.iterator().next().value();
      +            // return records.iterator().next().value();
      +            List messages = new ArrayList<>();
      +            for (ConsumerRecord record : records) {
      +                messages.add(record.value());
      +            }
      +            return messages;
      +        }
      +        return null;
      +    }
      +
      +    public List getFlatMessageWithoutAck(Long timeout, TimeUnit unit) {
      +        waitClientRunning();
      +        if (!running) {
      +            return null;
      +        }
      +
      +        ConsumerRecords records = kafkaConsumer2.poll(unit.toMillis(timeout));
      +
      +        if (!records.isEmpty()) {
      +            List flatMessages = new ArrayList<>();
      +            for (ConsumerRecord record : records) {
      +                String flatMessageJson = record.value();
      +                FlatMessage flatMessage = JSON.parseObject(flatMessageJson, FlatMessage.class);
      +                flatMessages.add(flatMessage);
      +            }
      +
      +            return flatMessages;
               }
               return null;
           }
      @@ -227,7 +230,12 @@ public class KafkaCanalConnector {
                   return;
               }
       
      -        kafkaConsumer.commitSync();
      +        if (kafkaConsumer != null) {
      +            kafkaConsumer.commitSync();
      +        }
      +        if (kafkaConsumer2 != null) {
      +            kafkaConsumer2.commitAsync();
      +        }
           }
       
           public void stopRunning() {
      diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnectors.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnectors.java
      index 0634a747..436fde8f 100644
      --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnectors.java
      +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnectors.java
      @@ -17,8 +17,9 @@ public class KafkaCanalConnectors {
            * @param groupId
            * @return
            */
      -    public static KafkaCanalConnector newKafkaConnector(String servers, String topic, Integer partition, String groupId) {
      -        return new KafkaCanalConnector(null, servers, topic, partition, groupId);
      +    public static KafkaCanalConnector newKafkaConnector(String servers, String topic, Integer partition,
      +                                                        String groupId) {
      +        return new KafkaCanalConnector(servers, topic, partition, groupId, false);
           }
       
           /**
      @@ -30,33 +31,21 @@ public class KafkaCanalConnectors {
            * @return
            */
           public static KafkaCanalConnector newKafkaConnector(String servers, String topic, String groupId) {
      -        return new KafkaCanalConnector(null, servers, topic, null, groupId);
      +        return new KafkaCanalConnector(servers, topic, null, groupId, false);
           }
       
           /**
            * 创建kafka客户端链接
            *
      -     * @param zkServers
            * @param servers
            * @param topic
            * @param partition
            * @param groupId
      +     * @param flatMessage
            * @return
            */
      -    public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, Integer partition, String groupId) {
      -        return new KafkaCanalConnector(zkServers, servers, topic, partition, groupId);
      -    }
      -
      -    /**
      -     * 创建kafka客户端链接
      -     *
      -     * @param zkServers
      -     * @param servers
      -     * @param topic
      -     * @param groupId
      -     * @return
      -     */
      -    public static KafkaCanalConnector newKafkaConnector(String zkServers, String servers, String topic, String groupId) {
      -        return new KafkaCanalConnector(zkServers, servers, topic, null, groupId);
      +    public static KafkaCanalConnector newKafkaConnector( String servers, String topic,
      +                                                        Integer partition, String groupId,boolean flatMessage) {
      +        return new KafkaCanalConnector(servers, topic, partition, groupId, flatMessage);
           }
       }
      diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java
      index 0a53a1df..93f443cd 100644
      --- a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java
      +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java
      @@ -1,5 +1,6 @@
       package com.alibaba.otter.canal.client.running.kafka;
       
      +import java.util.List;
       import java.util.concurrent.TimeUnit;
       
       import org.apache.kafka.common.errors.WakeupException;
      @@ -19,32 +20,33 @@ import com.alibaba.otter.canal.protocol.Message;
        */
       public class CanalKafkaClientExample {
       
      -    protected final static Logger logger = LoggerFactory.getLogger(CanalKafkaClientExample.class);
      +    protected final static Logger           logger  = LoggerFactory.getLogger(CanalKafkaClientExample.class);
       
      -    private KafkaCanalConnector connector;
      +    private KafkaCanalConnector             connector;
       
      -    private static volatile boolean running = false;
      +    private static volatile boolean         running = false;
       
      -    private Thread thread = null;
      +    private Thread                          thread  = null;
       
           private Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() {
       
      -        public void uncaughtException(Thread t, Throwable e) {
      -            logger.error("parse events has an error", e);
      -        }
      -    };
      +                                                        public void uncaughtException(Thread t, Throwable e) {
      +                                                            logger.error("parse events has an error", e);
      +                                                        }
      +                                                    };
       
      -    public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId) {
      -        connector = KafkaCanalConnectors.newKafkaConnector(zkServers, servers, topic, partition, groupId);
      +    public CanalKafkaClientExample(String zkServers, String servers, String topic, Integer partition, String groupId){
      +        connector = KafkaCanalConnectors.newKafkaConnector(servers, topic, partition, groupId, false);
           }
       
           public static void main(String[] args) {
               try {
      -            final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(AbstractKafkaTest.zkServers,
      -                    AbstractKafkaTest.servers,
      -                    AbstractKafkaTest.topic,
      -                    AbstractKafkaTest.partition,
      -                    AbstractKafkaTest.groupId);
      +            final CanalKafkaClientExample kafkaCanalClientExample = new CanalKafkaClientExample(
      +                AbstractKafkaTest.zkServers,
      +                AbstractKafkaTest.servers,
      +                AbstractKafkaTest.topic,
      +                AbstractKafkaTest.partition,
      +                AbstractKafkaTest.groupId);
                   logger.info("## start the kafka consumer: {}-{}", AbstractKafkaTest.topic, AbstractKafkaTest.groupId);
                   kafkaCanalClientExample.start();
                   logger.info("## the canal kafka consumer is running now ......");
      @@ -107,21 +109,24 @@ public class CanalKafkaClientExample {
                       connector.subscribe();
                       while (running) {
                           try {
      -                        Message message = connector.getWithoutAck(1L, TimeUnit.SECONDS); // 获取message
      -                        if (message == null) {
      +                        List messages = connector.getWithoutAck(1L, TimeUnit.SECONDS); // 获取message
      +
      +                        if (messages == null) {
                                   continue;
                               }
      -                        long batchId = message.getId();
      -                        int size = message.getEntries().size();
      -                        if (batchId == -1 || size == 0) {
      -                            // try {
      -                            // Thread.sleep(1000);
      -                            // } catch (InterruptedException e) {
      -                            // }
      -                        } else {
      -                            // printSummary(message, batchId, size);
      -                            // printEntry(message.getEntries());
      -                            logger.info(message.toString());
      +                        for (Message message : messages) {
      +                            long batchId = message.getId();
      +                            int size = message.getEntries().size();
      +                            if (batchId == -1 || size == 0) {
      +                                // try {
      +                                // Thread.sleep(1000);
      +                                // } catch (InterruptedException e) {
      +                                // }
      +                            } else {
      +                                // printSummary(message, batchId, size);
      +                                // printEntry(message.getEntries());
      +                                logger.info(message.toString());
      +                            }
                               }
       
                               connector.ack(); // 提交确认
      @@ -139,6 +144,6 @@ public class CanalKafkaClientExample {
               } catch (WakeupException e) {
                   // No-op. Continue process
               }
      -        connector.disconnnect();
      +        connector.disconnect();
           }
       }
      diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java
      index ebc4d549..fcdc0bf6 100644
      --- a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java
      +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java
      @@ -1,5 +1,6 @@
       package com.alibaba.otter.canal.client.running.kafka;
       
      +import java.util.List;
       import java.util.concurrent.ExecutorService;
       import java.util.concurrent.Executors;
       import java.util.concurrent.TimeUnit;
      @@ -39,9 +40,9 @@ public class KafkaClientRunningTest extends AbstractKafkaTest {
                       connector.subscribe();
                       while (running) {
                           try {
      -                        Message message = connector.getWithoutAck(3L, TimeUnit.SECONDS);
      -                        if (message != null) {
      -                            System.out.println(message);
      +                        List messages = connector.getWithoutAck(3L, TimeUnit.SECONDS);
      +                        if (messages != null) {
      +                            System.out.println(messages);
                               }
                               connector.ack();
                           } catch (WakeupException e) {
      @@ -49,7 +50,7 @@ public class KafkaClientRunningTest extends AbstractKafkaTest {
                           }
                       }
                       connector.unsubscribe();
      -                connector.disconnnect();
      +                connector.disconnect();
                   }
               });
       
      diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml
      index db4a9abe..735f723d 100644
      --- a/deployer/src/main/resources/kafka.yml
      +++ b/deployer/src/main/resources/kafka.yml
      @@ -8,8 +8,11 @@ bufferMemory: 33554432
       canalBatchSize: 50
       # Canal get数据的超时时间, 单位: 毫秒, 空为不限超时
       canalGetTimeout: 100
      +flatMessage: true
       
       canalDestinations:
         - canalDestination: example
           topic: example
      -    partition:
      \ No newline at end of file
      +    partition:
      +
      +
      diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java
      new file mode 100644
      index 00000000..e6aab531
      --- /dev/null
      +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java
      @@ -0,0 +1,286 @@
      +package com.alibaba.otter.canal.protocol;
      +
      +import java.io.Serializable;
      +import java.util.*;
      +
      +import com.google.common.collect.Table;
      +import com.google.protobuf.ByteString;
      +
      +/**
      + * @author machengyuan 2018-9-13 下午10:31:14
      + * @version 1.0.0
      + */
      +public class FlatMessage implements Serializable {
      +
      +    private static final long         serialVersionUID = -3386650678735860050L;
      +
      +    private long                      id;
      +    private String                    database;
      +    private String                    table;
      +    private Boolean                   isDdl;
      +    private String                    type;
      +    private Long                      ts;
      +    private String                    sql;
      +    private Map      sqlType;
      +    private Map       mysqlType;
      +    private List> data;
      +    private List> old;
      +
      +    public FlatMessage() {
      +    }
      +
      +    public FlatMessage(long id){
      +        this.id = id;
      +    }
      +
      +    public long getId() {
      +        return id;
      +    }
      +
      +    public void setId(long id) {
      +        this.id = id;
      +    }
      +
      +    public String getDatabase() {
      +        return database;
      +    }
      +
      +    public void setDatabase(String database) {
      +        this.database = database;
      +    }
      +
      +    public String getTable() {
      +        return table;
      +    }
      +
      +    public void setTable(String table) {
      +        this.table = table;
      +    }
      +
      +    public Boolean getIsDdl() {
      +        return isDdl;
      +    }
      +
      +    public void setIsDdl(Boolean isDdl) {
      +        this.isDdl = isDdl;
      +    }
      +
      +    public String getType() {
      +        return type;
      +    }
      +
      +    public void setType(String type) {
      +        this.type = type;
      +    }
      +
      +    public Long getTs() {
      +        return ts;
      +    }
      +
      +    public void setTs(Long ts) {
      +        this.ts = ts;
      +    }
      +
      +    public String getSql() {
      +        return sql;
      +    }
      +
      +    public void setSql(String sql) {
      +        this.sql = sql;
      +    }
      +
      +    public Map getSqlType() {
      +        return sqlType;
      +    }
      +
      +    public void setSqlType(Map sqlType) {
      +        this.sqlType = sqlType;
      +    }
      +
      +    public Map getMysqlType() {
      +        return mysqlType;
      +    }
      +
      +    public void setMysqlType(Map mysqlType) {
      +        this.mysqlType = mysqlType;
      +    }
      +
      +    public List> getData() {
      +        return data;
      +    }
      +
      +    public void setData(List> data) {
      +        this.data = data;
      +    }
      +
      +    public List> getOld() {
      +        return old;
      +    }
      +
      +    public void setOld(List> old) {
      +        this.old = old;
      +    }
      +
      +    public static List messageConverter(Message message) {
      +        try {
      +            if (message == null) {
      +                return null;
      +            }
      +
      +            List flatMessages = new ArrayList<>();
      +
      +            List rawEntries = message.getRawEntries();
      +
      +            for (ByteString byteString : rawEntries) {
      +                CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString);
      +                if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN
      +                    || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {
      +                    continue;
      +                }
      +
      +                CanalEntry.RowChange rowChange;
      +                try {
      +                    rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue());
      +                } catch (Exception e) {
      +                    throw new RuntimeException(
      +                        "ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
      +                        e);
      +                }
      +
      +                CanalEntry.EventType eventType = rowChange.getEventType();
      +
      +                FlatMessage flatMessage = new FlatMessage(message.getId());
      +                flatMessages.add(flatMessage);
      +                flatMessage.setDatabase(entry.getHeader().getSchemaName());
      +                flatMessage.setTable(entry.getHeader().getTableName());
      +                flatMessage.setIsDdl(rowChange.getIsDdl());
      +                flatMessage.setType(eventType.toString());
      +                flatMessage.setTs(System.currentTimeMillis());
      +                flatMessage.setSql(rowChange.getSql());
      +
      +                if (!rowChange.getIsDdl()) {
      +                    Map sqlType = new LinkedHashMap<>();
      +                    Map mysqlType = new LinkedHashMap<>();
      +                    List> data = new ArrayList<>();
      +                    List> old = new ArrayList<>();
      +
      +                    Set updateSet = new HashSet<>();
      +                    for (CanalEntry.RowData rowData : rowChange.getRowDatasList()) {
      +                        if (eventType != CanalEntry.EventType.INSERT && eventType != CanalEntry.EventType.UPDATE
      +                            && eventType != CanalEntry.EventType.DELETE) {
      +                            continue;
      +                        }
      +
      +                        Map row = new LinkedHashMap<>();
      +                        List columns;
      +
      +                        if (eventType == CanalEntry.EventType.DELETE) {
      +                            columns = rowData.getBeforeColumnsList();
      +                        } else {
      +                            columns = rowData.getAfterColumnsList();
      +                        }
      +
      +                        for (CanalEntry.Column column : columns) {
      +                            sqlType.put(column.getName(), column.getSqlType());
      +                            mysqlType.put(column.getName(), column.getMysqlType());
      +                            row.put(column.getName(), column.getValue());
      +                            // 获取update为true的字段
      +                            if (column.getUpdated()) {
      +                                updateSet.add(column.getName());
      +                            }
      +                        }
      +                        if (!row.isEmpty()) {
      +                            data.add(row);
      +                        }
      +
      +                        if (eventType == CanalEntry.EventType.UPDATE) {
      +                            Map rowOld = new LinkedHashMap<>();
      +                            for (CanalEntry.Column column : rowData.getBeforeColumnsList()) {
      +                                if (updateSet.contains(column.getName())) {
      +                                    rowOld.put(column.getName(), column.getValue());
      +                                }
      +                            }
      +                            // update操作将记录修改前的值
      +                            if (!rowOld.isEmpty()) {
      +                                old.add(rowOld);
      +                            }
      +                        }
      +                    }
      +                    if (!sqlType.isEmpty()) {
      +                        flatMessage.setSqlType(sqlType);
      +                    }
      +                    if (!mysqlType.isEmpty()) {
      +                        flatMessage.setMysqlType(mysqlType);
      +                    }
      +                    if (!data.isEmpty()) {
      +                        flatMessage.setData(data);
      +                    }
      +                    if (!old.isEmpty()) {
      +                        flatMessage.setOld(old);
      +                    }
      +                }
      +            }
      +            return flatMessages;
      +        } catch (Exception e) {
      +            throw new RuntimeException(e);
      +        }
      +    }
      +
      +    public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum,
      +                                                 Table pkHashConfig) {
      +        FlatMessage[] partitionMessages = new FlatMessage[partitionsNum];
      +
      +        String pk = pkHashConfig.get(flatMessage.getDatabase(), flatMessage.getTable());
      +        if (pk == null || flatMessage.getIsDdl()) {
      +            partitionMessages[0] = flatMessage;
      +        } else {
      +            if (flatMessage.getData() != null) {
      +                int idx = 0;
      +                for (Map row : flatMessage.getData()) {
      +                    String value = row.get(pk);
      +                    if (value == null) {
      +                        value = "";
      +                    }
      +                    int hash = value.hashCode();
      +                    int pkHash = Math.abs(hash) % partitionsNum;
      +
      +                    FlatMessage flatMessageTmp = partitionMessages[pkHash];
      +                    if (flatMessageTmp == null) {
      +                        flatMessageTmp = new FlatMessage(flatMessage.getId());
      +                        partitionMessages[pkHash] = flatMessageTmp;
      +                        flatMessageTmp.setDatabase(flatMessage.getDatabase());
      +                        flatMessageTmp.setTable(flatMessage.getTable());
      +                        flatMessageTmp.setIsDdl(flatMessage.getIsDdl());
      +                        flatMessageTmp.setType(flatMessage.getType());
      +                        flatMessageTmp.setSql(flatMessage.getSql());
      +                        flatMessageTmp.setSqlType(flatMessage.getSqlType());
      +                        flatMessageTmp.setMysqlType(flatMessage.getMysqlType());
      +                    }
      +                    List> data = flatMessageTmp.getData();
      +                    if (data == null) {
      +                        data = new ArrayList<>();
      +                        flatMessageTmp.setData(data);
      +                    }
      +                    data.add(row);
      +                    if (flatMessage.getOld() != null && !flatMessage.getOld().isEmpty()) {
      +                        List> old = flatMessageTmp.getOld();
      +                        if (old == null) {
      +                            old = new ArrayList<>();
      +                            flatMessageTmp.setOld(old);
      +                        }
      +                        old.add(flatMessage.getOld().get(idx));
      +                    }
      +                    idx++;
      +                }
      +            }
      +        }
      +        return partitionMessages;
      +    }
      +
      +    @Override
      +    public String toString() {
      +        return "FlatMessage{" + "id=" + id + ", database='" + database + '\'' + ", table='" + table + '\'' + ", isDdl="
      +               + isDdl + ", type='" + type + '\'' + ", ts=" + ts + ", sql='" + sql + '\'' + ", sqlType=" + sqlType
      +               + ", mysqlType=" + mysqlType + ", data=" + data + ", old=" + old + '}';
      +    }
      +}
      diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      index 0685f76e..22f15229 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      @@ -1,5 +1,6 @@
       package com.alibaba.otter.canal.kafka;
       
      +import java.util.List;
       import java.util.Properties;
       import java.util.concurrent.Future;
       
      @@ -11,6 +12,8 @@ import org.apache.kafka.common.serialization.StringSerializer;
       import org.slf4j.Logger;
       import org.slf4j.LoggerFactory;
       
      +import com.alibaba.fastjson.JSON;
      +import com.alibaba.otter.canal.protocol.FlatMessage;
       import com.alibaba.otter.canal.protocol.Message;
       
       /**
      @@ -25,7 +28,12 @@ public class CanalKafkaProducer {
       
           private Producer producer;
       
      +    private Producer  producer2;                                                 // 用于扁平message的数据投递
      +
      +    private KafkaProperties           kafkaProperties;
      +
           public void init(KafkaProperties kafkaProperties) {
      +        this.kafkaProperties = kafkaProperties;
               Properties properties = new Properties();
               properties.put("bootstrap.servers", kafkaProperties.getServers());
               properties.put("acks", "all");
      @@ -34,15 +42,26 @@ public class CanalKafkaProducer {
               properties.put("linger.ms", kafkaProperties.getLingerMs());
               properties.put("buffer.memory", kafkaProperties.getBufferMemory());
               properties.put("key.serializer", StringSerializer.class.getName());
      -        properties.put("value.serializer", MessageSerializer.class.getName());
      -        producer = new KafkaProducer(properties);
      +        if (!kafkaProperties.getFlatMessage()) {
      +            properties.put("value.serializer", MessageSerializer.class.getName());
      +            producer = new KafkaProducer(properties);
      +        } else {
      +            properties.put("value.serializer", StringSerializer.class.getName());
      +            producer2 = new KafkaProducer(properties);
      +        }
      +
               // producer.initTransactions();
           }
       
           public void stop() {
               try {
                   logger.info("## stop the kafka producer");
      -            producer.close();
      +            if (producer != null) {
      +                producer.close();
      +            }
      +            if (producer2 != null) {
      +                producer2.close();
      +            }
               } catch (Throwable e) {
                   logger.warn("##something goes wrong when stopping kafka producer:", e);
               } finally {
      @@ -53,14 +72,27 @@ public class CanalKafkaProducer {
           public void send(KafkaProperties.Topic topic, Message message, Callback callback) {
               try {
                   // producer.beginTransaction();
      -            ProducerRecord record;
      -            if (topic.getPartition() != null) {
      -                record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message);
      +            if (!kafkaProperties.getFlatMessage()) {
      +                ProducerRecord record;
      +                if (topic.getPartition() != null) {
      +                    record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message);
      +                } else {
      +                    record = new ProducerRecord(topic.getTopic(), message);
      +                }
      +                Future future = producer.send(record);
      +                future.get();
                   } else {
      -                record = new ProducerRecord(topic.getTopic(), message);
      +                // 发送扁平数据json
      +                List flatMessages = FlatMessage.messageConverter(message);
      +                if (flatMessages != null) {
      +                    for (FlatMessage flatMessage : flatMessages) {
      +                        ProducerRecord record = new ProducerRecord(topic.getTopic(),
      +                            JSON.toJSONString(flatMessage));
      +                        producer2.send(record);
      +                    }
      +                }
                   }
      -            Future future = producer.send(record);
      -            future.get();
      +
                   // producer.commitTransaction();
                   callback.commit();
                   if (logger.isDebugEnabled()) {
      diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java
      index 40f11bf5..82707ba4 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java
      @@ -21,6 +21,7 @@ public class KafkaProperties {
           private boolean                filterTransactionEntry = true;
           private int                    canalBatchSize         = 50;
           private Long                   canalGetTimeout;
      +    private boolean                flatMessage            = true;
       
           private List canalDestinations      = new ArrayList();
       
      @@ -160,6 +161,14 @@ public class KafkaProperties {
               this.canalGetTimeout = canalGetTimeout;
           }
       
      +    public boolean getFlatMessage() {
      +        return flatMessage;
      +    }
      +
      +    public void setFlatMessage(boolean flatMessage) {
      +        this.flatMessage = flatMessage;
      +    }
      +
           public List getCanalDestinations() {
               return canalDestinations;
           }
      diff --git a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java
      index 9ac5a834..a4f74c90 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java
      @@ -311,9 +311,10 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C
                   }
       
                   if (CollectionUtils.isEmpty(events.getEvents())) {
      -                logger.debug("getWithoutAck successfully, clientId:{} batchSize:{} but result is null",
      -                    clientIdentity.getClientId(),
      -                    batchSize);
      +                // logger.debug("getWithoutAck successfully, clientId:{} batchSize:{} but result
      +                // is null",
      +                // clientIdentity.getClientId(),
      +                // batchSize);
                       return new Message(-1, true, new ArrayList()); // 返回空包,避免生成batchId,浪费性能
                   } else {
                       // 记录到流式信息
      
      From db56effd911fffc60fa068993d3e9008c4707754 Mon Sep 17 00:00:00 2001
      From: mcy 
      Date: Fri, 14 Sep 2018 11:45:52 +0800
      Subject: [PATCH 135/360] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../client/adapter/loader/CanalAdapterKafkaWorker.java    | 8 ++------
       1 file changed, 2 insertions(+), 6 deletions(-)
      
      diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java
      index d7324cda..72320bb5 100644
      --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java
      +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java
      @@ -156,18 +156,14 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker {
                               TimeUnit.SECONDS.sleep(1L);
                           }
                       }
      -            } catch (
      -
      -            Exception e) {
      +            } catch (Exception e) {
                       logger.error(e.getMessage(), e);
                   }
               }
       
               executor.shutdown();
       
      -        try
      -
      -        {
      +        try {
                   connector.unsubscribe();
               } catch (WakeupException e) {
                   // No-op. Continue process
      
      From 1516cf779db1bcd311c1aa2266403b7399685d00 Mon Sep 17 00:00:00 2001
      From: winger 
      Date: Fri, 14 Sep 2018 14:06:32 +0800
      Subject: [PATCH 136/360] =?UTF-8?q?=E5=B0=86=E5=BD=93=E5=89=8D=E7=9A=84gti?=
       =?UTF-8?q?d=EF=BC=8Csequence=20no=20=E5=92=8Clast=20committed=E4=BF=A1?=
       =?UTF-8?q?=E6=81=AF=E5=A2=9E=E5=8A=A0=E8=87=B3Entry(trx=20begin/end,=20ro?=
       =?UTF-8?q?wdata)=20Header=20=E4=B8=AD,=E5=AE=A2=E6=88=B7=E7=AB=AF?=
       =?UTF-8?q?=E5=8F=AF=E4=BB=8Eproperty=E4=B8=AD=E8=8E=B7=E5=8F=96=E7=9B=B8?=
       =?UTF-8?q?=E5=85=B3=E5=80=BC?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../taobao/tddl/dbsync/binlog/LogContext.java | 11 ++++++
       .../taobao/tddl/dbsync/binlog/LogDecoder.java | 24 +++++++------
       .../tddl/dbsync/binlog/event/LogHeader.java   | 35 ++++++++++++++++---
       .../inbound/mysql/dbsync/LogEventConvert.java | 21 +++++++++--
       4 files changed, 72 insertions(+), 19 deletions(-)
      
      diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java
      index d8699629..0668ac4e 100644
      --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java
      +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogContext.java
      @@ -5,6 +5,7 @@ import java.util.Map;
       
       import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
       import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.GtidLogEvent;
       import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
       
       /**
      @@ -23,6 +24,8 @@ public final class LogContext {
       
           private GTIDSet                           gtidSet;
       
      +    private GtidLogEvent                      gtidLogEvent; // save current gtid log event
      +
           public LogContext(){
               this.formatDescription = FormatDescriptionLogEvent.FORMAT_DESCRIPTION_EVENT_5_x;
           }
      @@ -71,4 +74,12 @@ public final class LogContext {
           public void setGtidSet(GTIDSet gtidSet) {
               this.gtidSet = gtidSet;
           }
      +
      +    public GtidLogEvent getGtidLogEvent() {
      +        return gtidLogEvent;
      +    }
      +
      +    public void setGtidLogEvent(GtidLogEvent gtidLogEvent) {
      +        this.gtidLogEvent = gtidLogEvent;
      +    }
       }
      diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java
      index cfe51a1c..6670f259 100644
      --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java
      +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java
      @@ -161,19 +161,20 @@ public final class LogDecoder {
                   buffer.limit(header.getEventLen() - LogEvent.BINLOG_CHECKSUM_LEN);
               }
               GTIDSet gtidSet = context.getGtidSet();
      +        GtidLogEvent gtidLogEvent = context.getGtidLogEvent();
               switch (header.getType()) {
                   case LogEvent.QUERY_EVENT: {
                       QueryLogEvent event = new QueryLogEvent(header, buffer, descriptionEvent);
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.XID_EVENT: {
                       XidLogEvent event = new XidLogEvent(header, buffer, descriptionEvent);
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.TABLE_MAP_EVENT: {
      @@ -271,14 +272,14 @@ public final class LogDecoder {
                       RandLogEvent event = new RandLogEvent(header, buffer, descriptionEvent);
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.USER_VAR_EVENT: {
                       UserVarLogEvent event = new UserVarLogEvent(header, buffer, descriptionEvent);
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.FORMAT_DESCRIPTION_EVENT: {
      @@ -329,7 +330,6 @@ public final class LogDecoder {
                       HeartbeatLogEvent event = new HeartbeatLogEvent(header, buffer, descriptionEvent);
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
      -                header.putGtidStr(context.getGtidSet());
                       return event;
                   }
                   case LogEvent.IGNORABLE_LOG_EVENT: {
      @@ -342,7 +342,7 @@ public final class LogDecoder {
                       RowsQueryLogEvent event = new RowsQueryLogEvent(header, buffer, descriptionEvent);
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.WRITE_ROWS_EVENT: {
      @@ -350,7 +350,7 @@ public final class LogDecoder {
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
                       event.fillTable(context);
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.UPDATE_ROWS_EVENT: {
      @@ -358,7 +358,7 @@ public final class LogDecoder {
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
                       event.fillTable(context);
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.DELETE_ROWS_EVENT: {
      @@ -366,7 +366,7 @@ public final class LogDecoder {
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
                       event.fillTable(context);
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.GTID_LOG_EVENT:
      @@ -377,8 +377,10 @@ public final class LogDecoder {
                       if (gtidSet != null) {
                           gtidSet.update(event.getGtidStr());
                           // update latest gtid
      -                    header.putGtidStr(gtidSet);
      +                    header.putGtid(gtidSet, event);
                       }
      +                // update current gtid event to context
      +                context.setGtidLogEvent(event);
                       return event;
                   }
                   case LogEvent.PREVIOUS_GTIDS_LOG_EVENT: {
      @@ -409,7 +411,7 @@ public final class LogDecoder {
                       AnnotateRowsEvent event = new AnnotateRowsEvent(header, buffer, descriptionEvent);
                       /* updating position in context */
                       logPosition.position = header.getLogPos();
      -                header.putGtidStr(context.getGtidSet());
      +                header.putGtid(context.getGtidSet(), gtidLogEvent);
                       return event;
                   }
                   case LogEvent.BINLOG_CHECKPOINT_EVENT: {
      diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java
      index 77e48ddb..ab273f1b 100644
      --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java
      +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/LogHeader.java
      @@ -4,6 +4,9 @@ import com.alibaba.otter.canal.parse.driver.mysql.packets.GTIDSet;
       import com.taobao.tddl.dbsync.binlog.LogBuffer;
       import com.taobao.tddl.dbsync.binlog.LogEvent;
       
      +import java.util.HashMap;
      +import java.util.Map;
      +
       /**
        * The Common-Header, documented in the table @ref Table_common_header "below",
        * always has the same form and length within one version of MySQL. Each event
      @@ -121,7 +124,12 @@ public final class LogHeader {
            */
           protected String    logFileName;
       
      -    protected String    gtidStr;
      +    protected Map gtidMap = new HashMap<>();
      +
      +    private static final String CURRENT_GTID_STRING = "curt_gtid";
      +    private static final String CURRENT_GTID_SN = "curt_gtid_sn";
      +    private static final String CURRENT_GTID_LAST_COMMIT = "curt_gtid_lct";
      +    private static final String GTID_SET_STRING = "gtid_str";
       
           /* for Start_event_v3 */
           public LogHeader(final int type){
      @@ -292,13 +300,30 @@ public final class LogHeader {
               }
           }
       
      -    public String getGtidStr() {
      -        return gtidStr;
      +    public String getGtidSetStr() {
      +        return gtidMap.get(GTID_SET_STRING);
           }
       
      -    public void putGtidStr(GTIDSet gtidSet) {
      +    public String getCurrentGtid() {
      +        return gtidMap.get(CURRENT_GTID_STRING);
      +    }
      +
      +    public String getCurrentGtidSn() {
      +        return gtidMap.get(CURRENT_GTID_SN);
      +    }
      +
      +    public String getCurrentGtidLastCommit() {
      +        return gtidMap.get(CURRENT_GTID_LAST_COMMIT);
      +    }
      +
      +    public void putGtid(GTIDSet gtidSet, GtidLogEvent event) {
               if (gtidSet != null) {
      -            this.gtidStr = gtidSet.toString();
      +            gtidMap.put(GTID_SET_STRING, gtidSet.toString());
      +            if (event != null) {
      +                gtidMap.put(CURRENT_GTID_STRING, event.getGtidStr());
      +                gtidMap.put(CURRENT_GTID_SN, String.valueOf(event.getSequenceNumber()));
      +                gtidMap.put(CURRENT_GTID_LAST_COMMIT, String.valueOf(event.getLastCommitted()));
      +            }
               }
           }
       }
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      index 55090f65..7fcd1a1f 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      @@ -545,7 +545,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                       rowsCount++;
                       rowChangeBuider.addRowDatas(rowDataBuilder.build());
                   }
      -
                   TableMapLogEvent table = event.getTable();
                   Header header = createHeader(event.getHeader(),
                       table.getDbName(),
      @@ -834,8 +833,24 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
               }
               headerBuilder.setEventLength(logHeader.getEventLen());
               // enable gtid position
      -        if (StringUtils.isNotEmpty(logHeader.getGtidStr())) {
      -            headerBuilder.setGtid(logHeader.getGtidStr());
      +        if (StringUtils.isNotEmpty(logHeader.getGtidSetStr())) {
      +            headerBuilder.setGtid(logHeader.getGtidSetStr());
      +        }
      +        // add current gtid
      +        if (StringUtils.isNotEmpty(logHeader.getCurrentGtid())) {
      +            Pair pair = createSpecialPair("curtGtid", logHeader.getCurrentGtid());
      +            headerBuilder.addProps(pair);
      +        }
      +        // add current gtid sequence no
      +        if (StringUtils.isNotEmpty(logHeader.getCurrentGtidSn())) {
      +            Pair pair = createSpecialPair("curtGtidSn", logHeader.getCurrentGtidSn());
      +            headerBuilder.addProps(pair);
      +        }
      +
      +        // add current gtid last committed
      +        if (StringUtils.isNotEmpty(logHeader.getCurrentGtidLastCommit())) {
      +            Pair pair = createSpecialPair("curtGtidLct", logHeader.getCurrentGtidLastCommit());
      +            headerBuilder.addProps(pair);
               }
       
               // add rowsCount suppport
      
      From 6e84b98651df5ff5a9450b25f5edca7fc77101ac Mon Sep 17 00:00:00 2001
      From: winger 
      Date: Fri, 14 Sep 2018 14:22:55 +0800
      Subject: [PATCH 137/360] =?UTF-8?q?example=20=E5=A2=9E=E5=8A=A0=E8=8E=B7?=
       =?UTF-8?q?=E5=8F=96=E5=BD=93=E5=89=8Dgtid=E7=9B=B8=E5=85=B3=E4=BF=A1?=
       =?UTF-8?q?=E6=81=AF=E7=A4=BA=E4=BE=8B?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../example/AbstractCanalClientTest.java      | 52 +++++++++++++++++++
       1 file changed, 52 insertions(+)
      
      diff --git a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java
      index 599b5da7..9cb5eb00 100644
      --- a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java
      +++ b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java
      @@ -5,6 +5,7 @@ import java.text.SimpleDateFormat;
       import java.util.Date;
       import java.util.List;
       
      +import com.alibaba.otter.canal.protocol.CanalEntry;
       import org.apache.commons.lang.StringUtils;
       import org.apache.commons.lang.SystemUtils;
       import org.slf4j.Logger;
      @@ -299,4 +300,55 @@ public class AbstractCanalClientTest {
               this.connector = connector;
           }
       
      +    /**
      +     * 获取当前Entry的 GTID信息示例
      +     * @param header
      +     * @return
      +     */
      +    public static String getCurrentGtid(CanalEntry.Header header) {
      +        List props = header.getPropsList();
      +        if (props != null && props.size() > 0) {
      +            for (CanalEntry.Pair pair : props) {
      +                if ("curtGtid".equals(pair.getKey())) {
      +                    return pair.getValue();
      +                }
      +            }
      +        }
      +        return "";
      +    }
      +
      +    /**
      +     * 获取当前Entry的 GTID Sequence No信息示例
      +     * @param header
      +     * @return
      +     */
      +    public static String getCurrentGtidSn(CanalEntry.Header header) {
      +        List props = header.getPropsList();
      +        if (props != null && props.size() > 0) {
      +            for (CanalEntry.Pair pair : props) {
      +                if ("curtGtidSn".equals(pair.getKey())) {
      +                    return pair.getValue();
      +                }
      +            }
      +        }
      +        return "";
      +    }
      +
      +    /**
      +     * 获取当前Entry的 GTID Last Committed信息示例
      +     * @param header
      +     * @return
      +     */
      +    public static String getCurrentGtidLct(CanalEntry.Header header) {
      +        List props = header.getPropsList();
      +        if (props != null && props.size() > 0) {
      +            for (CanalEntry.Pair pair : props) {
      +                if ("curtGtidLct".equals(pair.getKey())) {
      +                    return pair.getValue();
      +                }
      +            }
      +        }
      +        return "";
      +    }
      +
       }
      
      From 6682c56b9f1f15b544daeb2fda51964c371d8c6b Mon Sep 17 00:00:00 2001
      From: Chuanyi Li 
      Date: Sat, 15 Sep 2018 13:21:38 +0800
      Subject: [PATCH 138/360] Canal metrics group mode support.
      
      ---
       .../mysql/AbstractMysqlEventParser.java       |  6 ++
       .../parse/inbound/mysql/MysqlEventParser.java |  7 --
       .../prometheus/impl/ParserCollector.java      | 96 +++++++++++++------
       3 files changed, 75 insertions(+), 34 deletions(-)
      
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
      index e36893a3..00e302e6 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java
      @@ -38,6 +38,8 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
           protected boolean              filterRows                = false;
           protected boolean              filterTableError          = false;
           protected boolean              useDruidDdlFilter         = true;
      +    // instance received binlog bytes
      +    protected final AtomicLong     receivedBinlogBytes       = new AtomicLong(0L);
           private final AtomicLong       eventsPublishBlockingTime = new AtomicLong(0L);
       
           protected BinlogParser buildParser() {
      @@ -204,4 +206,8 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser {
               return this.eventsPublishBlockingTime;
           }
       
      +    public AtomicLong getReceivedBinlogBytes() {
      +        return this.receivedBinlogBytes;
      +    }
      +
       }
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
      index b585d9ab..a5c8d870 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
      @@ -71,9 +71,6 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
           private int                  dumpErrorCount                    = 0;                 // binlogDump失败异常计数
           private int                  dumpErrorCountThreshold           = 2;                 // binlogDump失败异常计数阀值
       
      -    // instance received binlog bytes
      -    private final AtomicLong     receivedBinlogBytes               = new AtomicLong(0L);
      -
           protected ErosaConnection buildErosaConnection() {
               return buildMysqlConnection(this.runningInfo);
           }
      @@ -907,8 +904,4 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
               this.dumpErrorCountThreshold = dumpErrorCountThreshold;
           }
       
      -    public AtomicLong getReceivedBinlogBytes() {
      -        return this.receivedBinlogBytes;
      -    }
      -
       }
      diff --git a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java
      index 867e1e89..660bf1ca 100644
      --- a/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java
      +++ b/prometheus/src/main/java/com/alibaba/otter/canal/prometheus/impl/ParserCollector.java
      @@ -1,14 +1,15 @@
       package com.alibaba.otter.canal.prometheus.impl;
       
       import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST;
      -import static com.alibaba.otter.canal.prometheus.CanalInstanceExports.DEST_LABELS_LIST;
      +
      +import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser;
      +import com.alibaba.otter.canal.parse.inbound.mysql.AbstractMysqlEventParser;
       import io.prometheus.client.Collector;
       import io.prometheus.client.CounterMetricFamily;
       import io.prometheus.client.GaugeMetricFamily;
       
       import java.util.ArrayList;
       import java.util.Arrays;
      -import java.util.Collections;
       import java.util.List;
       import java.util.concurrent.ConcurrentHashMap;
       import java.util.concurrent.ConcurrentMap;
      @@ -19,7 +20,6 @@ import org.slf4j.LoggerFactory;
       
       import com.alibaba.otter.canal.instance.core.CanalInstance;
       import com.alibaba.otter.canal.parse.CanalEventParser;
      -import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser;
       import com.alibaba.otter.canal.prometheus.InstanceRegistry;
       import com.google.common.base.Preconditions;
       
      @@ -34,11 +34,13 @@ public class ParserCollector extends Collector implements InstanceRegistry {
           private static final String                              RECEIVED_BINLOG       = "canal_instance_received_binlog_bytes";
           private static final String                              PARSER_MODE           = "canal_instance_parser_mode";
           private static final String                              MODE_LABEL            = "parallel";
      +    private static final String                              PARSER_LABEL          = "parser";
           private static final String                              PUBLISH_BLOCKING_HELP = "Publish blocking time of dump thread in milliseconds";
           private static final String                              RECEIVED_BINLOG_HELP  = "Received binlog bytes";
           private static final String                              MODE_HELP             = "Parser mode(parallel/serial) of instance";
           private final List                               modeLabels            = Arrays.asList(DEST, MODE_LABEL);
      -    private final ConcurrentMap instances             = new ConcurrentHashMap();
      +    private final List                               parserLabels          = Arrays.asList(DEST, PARSER_LABEL);
      +    private final ConcurrentMap instances             = new ConcurrentHashMap<>();
       
           private ParserCollector() {}
       
      @@ -52,53 +54,87 @@ public class ParserCollector extends Collector implements InstanceRegistry {
       
           @Override
           public List collect() {
      -        List mfs = new ArrayList();
      -        boolean hasParallel = false;
      +        List mfs = new ArrayList<>();
               CounterMetricFamily bytesCounter = new CounterMetricFamily(RECEIVED_BINLOG,
      -                RECEIVED_BINLOG_HELP, DEST_LABELS_LIST);
      +                RECEIVED_BINLOG_HELP, parserLabels);
               GaugeMetricFamily modeGauge = new GaugeMetricFamily(PARSER_MODE,
                       MODE_HELP, modeLabels);
               CounterMetricFamily blockingCounter = new CounterMetricFamily(PUBLISH_BLOCKING,
      -                PUBLISH_BLOCKING_HELP, DEST_LABELS_LIST);
      +                PUBLISH_BLOCKING_HELP, parserLabels);
               for (ParserMetricsHolder emh : instances.values()) {
      -            if (emh.isParallel) {
      -                blockingCounter.addMetric(emh.destLabelValues, (emh.eventsPublishBlockingTime.doubleValue() / NANO_PER_MILLI));
      -                hasParallel = true;
      +            if (emh instanceof GroupParserMetricsHolder) {
      +                GroupParserMetricsHolder group = (GroupParserMetricsHolder) emh;
      +                for (ParserMetricsHolder semh :  group.holders) {
      +                    singleCollect(bytesCounter, blockingCounter, modeGauge, semh);
      +                }
      +            }
      +            else {
      +                singleCollect(bytesCounter, blockingCounter, modeGauge, emh);
                   }
      -            modeGauge.addMetric(emh.modeLabelValues, 1);
      -            bytesCounter.addMetric(emh.destLabelValues, emh.receivedBinlogBytes.doubleValue());
      -
               }
               mfs.add(bytesCounter);
               mfs.add(modeGauge);
      -        if (hasParallel) {
      +        if (!blockingCounter.samples.isEmpty()) {
                   mfs.add(blockingCounter);
               }
               return mfs;
           }
       
      +    private void singleCollect(CounterMetricFamily bytesCounter, CounterMetricFamily blockingCounter, GaugeMetricFamily modeGauge, ParserMetricsHolder holder) {
      +        if (holder.isParallel) {
      +            blockingCounter.addMetric(holder.parserLabelValues, (holder.eventsPublishBlockingTime.doubleValue() / NANO_PER_MILLI));
      +        }
      +        modeGauge.addMetric(holder.modeLabelValues, 1);
      +        bytesCounter.addMetric(holder.parserLabelValues, holder.receivedBinlogBytes.doubleValue());
      +    }
      +
           @Override
           public void register(CanalInstance instance) {
               final String destination = instance.getDestination();
      -        ParserMetricsHolder holder = new ParserMetricsHolder();
      +        ParserMetricsHolder holder;
               CanalEventParser parser = instance.getEventParser();
      -        if (!(parser instanceof MysqlEventParser)) {
      -            throw new IllegalArgumentException("CanalEventParser must be MysqlEventParser");
      +        if (parser instanceof AbstractMysqlEventParser) {
      +            holder = singleHolder(destination, (AbstractMysqlEventParser)parser, "0");
      +        } else if (parser instanceof GroupEventParser) {
      +            holder = groupHolder(destination, (GroupEventParser)parser);
      +        } else {
      +            throw new IllegalArgumentException("CanalEventParser must be either AbstractMysqlEventParser or GroupEventParser.");
               }
      -        MysqlEventParser mysqlParser = (MysqlEventParser) parser;
      -        holder.destLabelValues = Collections.singletonList(destination);
      -        holder.modeLabelValues = Arrays.asList(destination, Boolean.toString(mysqlParser.isParallel()));
      -        holder.eventsPublishBlockingTime = mysqlParser.getEventsPublishBlockingTime();
      -        holder.receivedBinlogBytes = mysqlParser.getReceivedBinlogBytes();
      -        holder.isParallel = mysqlParser.isParallel();
      -        Preconditions.checkNotNull(holder.eventsPublishBlockingTime);
      -        Preconditions.checkNotNull(holder.receivedBinlogBytes);
      +        Preconditions.checkNotNull(holder);
               ParserMetricsHolder old = instances.put(destination, holder);
               if (old != null) {
                   logger.warn("Remove stale ParserCollector for instance {}.", destination);
               }
           }
       
      +    private ParserMetricsHolder singleHolder(String destination, AbstractMysqlEventParser parser, String id) {
      +        ParserMetricsHolder holder = new ParserMetricsHolder();
      +        holder.parserLabelValues = Arrays.asList(destination, id);
      +        holder.modeLabelValues = Arrays.asList(destination, Boolean.toString(parser.isParallel()));
      +        holder.eventsPublishBlockingTime = parser.getEventsPublishBlockingTime();
      +        holder.receivedBinlogBytes = parser.getReceivedBinlogBytes();
      +        holder.isParallel = parser.isParallel();
      +        Preconditions.checkNotNull(holder.eventsPublishBlockingTime);
      +        Preconditions.checkNotNull(holder.receivedBinlogBytes);
      +        return holder;
      +    }
      +
      +    private GroupParserMetricsHolder groupHolder(String destination, GroupEventParser group) {
      +        List parsers = group.getEventParsers();
      +        GroupParserMetricsHolder groupHolder = new GroupParserMetricsHolder();
      +        int num = parsers.size();
      +        for (int i = 0; i < num; i ++) {
      +            CanalEventParser parser = parsers.get(i);
      +            if (parser instanceof AbstractMysqlEventParser) {
      +                ParserMetricsHolder single = singleHolder(destination, (AbstractMysqlEventParser)parser, Integer.toString(i + 1));
      +                groupHolder.holders.add(single);
      +            } else {
      +                logger.warn("Null or non AbstractMysqlEventParser, ignore.");
      +            }
      +        }
      +        return groupHolder;
      +    }
      +
           @Override
           public void unregister(CanalInstance instance) {
               final String destination = instance.getDestination();
      @@ -106,11 +142,17 @@ public class ParserCollector extends Collector implements InstanceRegistry {
           }
       
           private class ParserMetricsHolder {
      -        private List destLabelValues;
      +        private List parserLabelValues;
               private List modeLabelValues;
      +        // metrics for single parser
               private AtomicLong   receivedBinlogBytes;
               private AtomicLong   eventsPublishBlockingTime;
      +        // parser mode
               private boolean      isParallel;
           }
       
      +    private class GroupParserMetricsHolder extends ParserMetricsHolder {
      +        private final List holders = new ArrayList<>();
      +    }
      +
       }
      
      From cae6df1457a6d4871aa55ec82885dd6f74286b63 Mon Sep 17 00:00:00 2001
      From: Chuanyi Li 
      Date: Sat, 15 Sep 2018 13:28:27 +0800
      Subject: [PATCH 139/360] Update grafana tmpl.
      
      ---
       .../metrics/Canal_instances_tmpl.json         | 455 ++++++++++--------
       1 file changed, 263 insertions(+), 192 deletions(-)
      
      diff --git a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json
      index 0d7c94d2..141810c0 100644
      --- a/deployer/src/main/resources/metrics/Canal_instances_tmpl.json
      +++ b/deployer/src/main/resources/metrics/Canal_instances_tmpl.json
      @@ -1,4 +1,34 @@
       {
      +  "__inputs": [
      +    {
      +      "name": "DS_PROMETHEUS",
      +      "label": "prometheus",
      +      "description": "",
      +      "type": "datasource",
      +      "pluginId": "prometheus",
      +      "pluginName": "Prometheus"
      +    }
      +  ],
      +  "__requires": [
      +    {
      +      "type": "grafana",
      +      "id": "grafana",
      +      "name": "Grafana",
      +      "version": "5.2.2"
      +    },
      +    {
      +      "type": "panel",
      +      "id": "graph",
      +      "name": "Graph",
      +      "version": "5.0.0"
      +    },
      +    {
      +      "type": "datasource",
      +      "id": "prometheus",
      +      "name": "Prometheus",
      +      "version": "5.0.0"
      +    }
      +  ],
         "annotations": {
           "list": [
             {
      @@ -15,8 +45,8 @@
         "editable": true,
         "gnetId": null,
         "graphTooltip": 0,
      -  "id": 8,
      -  "iteration": 1534740332233,
      +  "id": null,
      +  "iteration": 1536989235272,
         "links": [],
         "panels": [
           {
      @@ -189,7 +219,7 @@
             "steppedLine": false,
             "targets": [
               {
      -          "expr": "rate(canal_instance_received_binlog_bytes{destination=~\"$destination\"}[2m]) / 1024",
      +          "expr": "rate(canal_instance_received_binlog_bytes{destination=~\"$destination\", parser=\"0\"}[2m]) / 1024",
                 "format": "time_series",
                 "interval": "15s",
                 "intervalFactor": 2,
      @@ -203,6 +233,22 @@
                 "intervalFactor": 2,
                 "legendFormat": "outbound",
                 "refId": "B"
      +        },
      +        {
      +          "expr": "rate(canal_instance_received_binlog_bytes{destination=~\"$destination\", parser=\"1\"}[2m]) / 1024",
      +          "format": "time_series",
      +          "interval": "15s",
      +          "intervalFactor": 2,
      +          "legendFormat": "inbound-1",
      +          "refId": "C"
      +        },
      +        {
      +          "expr": "rate(canal_instance_received_binlog_bytes{destination=~\"$destination\", parser=\"2\"}[2m]) / 1024",
      +          "format": "time_series",
      +          "interval": "15s",
      +          "intervalFactor": 2,
      +          "legendFormat": "inbound-2",
      +          "refId": "D"
               }
             ],
             "thresholds": [],
      @@ -399,20 +445,36 @@
             "steppedLine": false,
             "targets": [
               {
      -          "expr": "clamp_max(rate(canal_instance_sink_blocking_time{destination=~\"$destination\"}[2m]), 1000) / 10",
      +          "expr": "clamp_max(rate(canal_instance_publish_blocking_time{destination=~\"$destination\", parser=\"0\"}[2m]), 1000) / 10",
                 "format": "time_series",
                 "interval": "15s",
                 "intervalFactor": 2,
                 "legendFormat": "dump",
      -          "refId": "A"
      +          "refId": "B"
               },
               {
      -          "expr": "clamp_max(rate(canal_instance_publish_blocking_time{destination=~\"$destination\"}[2m]), 1000) / 10",
      +          "expr": "clamp_max(rate(canal_instance_sink_blocking_time{destination=~\"$destination\"}[2m]), 1000) / 10",
                 "format": "time_series",
                 "interval": "15s",
                 "intervalFactor": 2,
                 "legendFormat": "sink",
      -          "refId": "B"
      +          "refId": "A"
      +        },
      +        {
      +          "expr": "clamp_max(rate(canal_instance_publish_blocking_time{destination=~\"$destination\", parser=\"1\"}[2m]), 1000) / 10",
      +          "format": "time_series",
      +          "interval": "15s",
      +          "intervalFactor": 2,
      +          "legendFormat": "dump-1",
      +          "refId": "C"
      +        },
      +        {
      +          "expr": "clamp_max(rate(canal_instance_publish_blocking_time{destination=~\"$destination\", parser=\"2\"}[2m]), 1000) / 10",
      +          "format": "time_series",
      +          "interval": "15s",
      +          "intervalFactor": 2,
      +          "legendFormat": "dump-2",
      +          "refId": "D"
               }
             ],
             "thresholds": [],
      @@ -465,7 +527,7 @@
             },
             "id": 32,
             "panels": [],
      -      "title": "Server",
      +      "title": "Throughput",
             "type": "row"
           },
           {
      @@ -660,180 +722,6 @@
               "alignLevel": null
             }
           },
      -    {
      -      "aliasColors": {},
      -      "bars": false,
      -      "dashLength": 10,
      -      "dashes": false,
      -      "datasource": "$datasource",
      -      "description": "Canal instance ringbuffer内未释放的events数量。",
      -      "fill": 1,
      -      "gridPos": {
      -        "h": 5,
      -        "w": 6,
      -        "x": 12,
      -        "y": 7
      -      },
      -      "id": 20,
      -      "legend": {
      -        "avg": false,
      -        "current": false,
      -        "max": false,
      -        "min": false,
      -        "show": true,
      -        "total": false,
      -        "values": false
      -      },
      -      "lines": true,
      -      "linewidth": 1,
      -      "links": [],
      -      "nullPointMode": "null",
      -      "percentage": false,
      -      "pointradius": 5,
      -      "points": false,
      -      "renderer": "flot",
      -      "seriesOverrides": [],
      -      "spaceLength": 10,
      -      "stack": false,
      -      "steppedLine": false,
      -      "targets": [
      -        {
      -          "expr": "canal_instance_store_produce_seq{destination=~\"$destination\"} - canal_instance_store_consume_seq{destination=~\"$destination\"}",
      -          "format": "time_series",
      -          "interval": "15s",
      -          "intervalFactor": 2,
      -          "legendFormat": "events",
      -          "refId": "A"
      -        }
      -      ],
      -      "thresholds": [],
      -      "timeFrom": null,
      -      "timeShift": null,
      -      "title": "Store remain events",
      -      "tooltip": {
      -        "shared": true,
      -        "sort": 0,
      -        "value_type": "individual"
      -      },
      -      "type": "graph",
      -      "xaxis": {
      -        "buckets": null,
      -        "mode": "time",
      -        "name": null,
      -        "show": true,
      -        "values": []
      -      },
      -      "yaxes": [
      -        {
      -          "decimals": null,
      -          "format": "none",
      -          "label": "",
      -          "logBase": 1,
      -          "max": null,
      -          "min": null,
      -          "show": true
      -        },
      -        {
      -          "decimals": null,
      -          "format": "short",
      -          "label": null,
      -          "logBase": 1,
      -          "max": null,
      -          "min": null,
      -          "show": true
      -        }
      -      ],
      -      "yaxis": {
      -        "align": false,
      -        "alignLevel": null
      -      }
      -    },
      -    {
      -      "aliasColors": {},
      -      "bars": false,
      -      "dashLength": 10,
      -      "dashes": false,
      -      "datasource": "$datasource",
      -      "description": "Canal instance ringbuffer 内未释放events占用内存。",
      -      "fill": 1,
      -      "gridPos": {
      -        "h": 5,
      -        "w": 6,
      -        "x": 18,
      -        "y": 7
      -      },
      -      "id": 22,
      -      "legend": {
      -        "avg": false,
      -        "current": false,
      -        "max": false,
      -        "min": false,
      -        "show": true,
      -        "total": false,
      -        "values": false
      -      },
      -      "lines": true,
      -      "linewidth": 1,
      -      "links": [],
      -      "nullPointMode": "null",
      -      "percentage": false,
      -      "pointradius": 5,
      -      "points": false,
      -      "renderer": "flot",
      -      "seriesOverrides": [],
      -      "spaceLength": 10,
      -      "stack": false,
      -      "steppedLine": false,
      -      "targets": [
      -        {
      -          "expr": "(canal_instance_store_produce_mem{destination=~\"$destination\"} - canal_instance_store_consume_mem{destination=~\"$destination\"}) / 1024",
      -          "format": "time_series",
      -          "interval": "15s",
      -          "intervalFactor": 2,
      -          "legendFormat": "memsize",
      -          "refId": "A"
      -        }
      -      ],
      -      "thresholds": [],
      -      "timeFrom": null,
      -      "timeShift": null,
      -      "title": "Store remain mem",
      -      "tooltip": {
      -        "shared": true,
      -        "sort": 0,
      -        "value_type": "individual"
      -      },
      -      "type": "graph",
      -      "xaxis": {
      -        "buckets": null,
      -        "mode": "time",
      -        "name": null,
      -        "show": true,
      -        "values": []
      -      },
      -      "yaxes": [
      -        {
      -          "format": "deckbytes",
      -          "label": null,
      -          "logBase": 1,
      -          "max": null,
      -          "min": null,
      -          "show": true
      -        },
      -        {
      -          "format": "short",
      -          "label": null,
      -          "logBase": 1,
      -          "max": null,
      -          "min": null,
      -          "show": true
      -        }
      -      ],
      -      "yaxis": {
      -        "align": false,
      -        "alignLevel": null
      -      }
      -    },
           {
             "collapsed": false,
             "gridPos": {
      @@ -938,7 +826,7 @@
             "bars": false,
             "dashLength": 10,
             "dashes": false,
      -      "datasource": "$datasource",
      +      "datasource": "${DS_PROMETHEUS}",
             "description": "client 请求的GET与ACK包的QPS。",
             "fill": 1,
             "gridPos": {
      @@ -1218,6 +1106,193 @@
               "align": false,
               "alignLevel": null
             }
      +    },
      +    {
      +      "collapsed": false,
      +      "gridPos": {
      +        "h": 1,
      +        "w": 24,
      +        "x": 0,
      +        "y": 18
      +      },
      +      "id": 36,
      +      "panels": [],
      +      "title": "Store",
      +      "type": "row"
      +    },
      +    {
      +      "aliasColors": {},
      +      "bars": false,
      +      "dashLength": 10,
      +      "dashes": false,
      +      "datasource": "$datasource",
      +      "description": "Canal instance ringbuffer内未释放的events数量。",
      +      "fill": 1,
      +      "gridPos": {
      +        "h": 5,
      +        "w": 6,
      +        "x": 0,
      +        "y": 19
      +      },
      +      "id": 20,
      +      "legend": {
      +        "avg": false,
      +        "current": false,
      +        "max": false,
      +        "min": false,
      +        "show": true,
      +        "total": false,
      +        "values": false
      +      },
      +      "lines": true,
      +      "linewidth": 1,
      +      "links": [],
      +      "nullPointMode": "null",
      +      "percentage": false,
      +      "pointradius": 5,
      +      "points": false,
      +      "renderer": "flot",
      +      "seriesOverrides": [],
      +      "spaceLength": 10,
      +      "stack": false,
      +      "steppedLine": false,
      +      "targets": [
      +        {
      +          "expr": "canal_instance_store_produce_seq{destination=~\"$destination\"} - canal_instance_store_consume_seq{destination=~\"$destination\"}",
      +          "format": "time_series",
      +          "interval": "15s",
      +          "intervalFactor": 2,
      +          "legendFormat": "events",
      +          "refId": "A"
      +        }
      +      ],
      +      "thresholds": [],
      +      "timeFrom": null,
      +      "timeShift": null,
      +      "title": "Store remain events",
      +      "tooltip": {
      +        "shared": true,
      +        "sort": 0,
      +        "value_type": "individual"
      +      },
      +      "type": "graph",
      +      "xaxis": {
      +        "buckets": null,
      +        "mode": "time",
      +        "name": null,
      +        "show": true,
      +        "values": []
      +      },
      +      "yaxes": [
      +        {
      +          "decimals": null,
      +          "format": "none",
      +          "label": "",
      +          "logBase": 1,
      +          "max": null,
      +          "min": null,
      +          "show": true
      +        },
      +        {
      +          "decimals": null,
      +          "format": "short",
      +          "label": null,
      +          "logBase": 1,
      +          "max": null,
      +          "min": null,
      +          "show": true
      +        }
      +      ],
      +      "yaxis": {
      +        "align": false,
      +        "alignLevel": null
      +      }
      +    },
      +    {
      +      "aliasColors": {},
      +      "bars": false,
      +      "dashLength": 10,
      +      "dashes": false,
      +      "datasource": "$datasource",
      +      "description": "Canal instance ringbuffer 内未释放events占用内存。",
      +      "fill": 1,
      +      "gridPos": {
      +        "h": 5,
      +        "w": 6,
      +        "x": 6,
      +        "y": 19
      +      },
      +      "id": 22,
      +      "legend": {
      +        "avg": false,
      +        "current": false,
      +        "max": false,
      +        "min": false,
      +        "show": true,
      +        "total": false,
      +        "values": false
      +      },
      +      "lines": true,
      +      "linewidth": 1,
      +      "links": [],
      +      "nullPointMode": "null",
      +      "percentage": false,
      +      "pointradius": 5,
      +      "points": false,
      +      "renderer": "flot",
      +      "seriesOverrides": [],
      +      "spaceLength": 10,
      +      "stack": false,
      +      "steppedLine": false,
      +      "targets": [
      +        {
      +          "expr": "(canal_instance_store_produce_mem{destination=~\"$destination\"} - canal_instance_store_consume_mem{destination=~\"$destination\"}) / 1024",
      +          "format": "time_series",
      +          "interval": "15s",
      +          "intervalFactor": 2,
      +          "legendFormat": "memsize",
      +          "refId": "A"
      +        }
      +      ],
      +      "thresholds": [],
      +      "timeFrom": null,
      +      "timeShift": null,
      +      "title": "Store remain mem",
      +      "tooltip": {
      +        "shared": true,
      +        "sort": 0,
      +        "value_type": "individual"
      +      },
      +      "type": "graph",
      +      "xaxis": {
      +        "buckets": null,
      +        "mode": "time",
      +        "name": null,
      +        "show": true,
      +        "values": []
      +      },
      +      "yaxes": [
      +        {
      +          "format": "deckbytes",
      +          "label": null,
      +          "logBase": 1,
      +          "max": null,
      +          "min": null,
      +          "show": true
      +        },
      +        {
      +          "format": "short",
      +          "label": null,
      +          "logBase": 1,
      +          "max": null,
      +          "min": null,
      +          "show": true
      +        }
      +      ],
      +      "yaxis": {
      +        "align": false,
      +        "alignLevel": null
      +      }
           }
         ],
         "refresh": false,
      @@ -1230,9 +1305,8 @@
           "list": [
             {
               "current": {
      -          "tags": [],
      -          "text": "Prometheus",
      -          "value": "Prometheus"
      +          "text": "prometheus",
      +          "value": "prometheus"
               },
               "hide": 0,
               "label": "datasource",
      @@ -1245,10 +1319,7 @@
             },
             {
               "allValue": null,
      -        "current": {
      -          "text": "example",
      -          "value": "example"
      -        },
      +        "current": {},
               "datasource": "$datasource",
               "hide": 0,
               "includeAll": false,
      @@ -1269,7 +1340,7 @@
           ]
         },
         "time": {
      -    "from": "now-1h",
      +    "from": "now-6h",
           "to": "now"
         },
         "timepicker": {
      @@ -1300,5 +1371,5 @@
         "timezone": "",
         "title": "Canal instances",
         "uid": "8vh8NGpiz",
      -  "version": 1
      +  "version": 103
       }
      \ No newline at end of file
      
      From 7005c11ac2b64abad4704c1bab1212d657f5e614 Mon Sep 17 00:00:00 2001
      From: mcy 
      Date: Mon, 17 Sep 2018 11:10:50 +0800
      Subject: [PATCH 140/360] =?UTF-8?q?fix=E5=BA=8F=E5=88=97=E5=8C=96=E7=9A=84?=
       =?UTF-8?q?=E4=B8=80=E4=BA=9B=E9=97=AE=E9=A2=98,=20kafka=E7=94=9F=E4=BA=A7?=
       =?UTF-8?q?=E7=AB=AF=E7=BB=9F=E4=B8=80=E4=BD=BF=E7=94=A8=E5=BC=82=E6=AD=A5?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../canal/client/impl/SimpleCanalConnector.java   | 15 +++++++--------
       .../canal/client/kafka/MessageDeserializer.java   |  3 ++-
       .../otter/canal/kafka/CanalKafkaProducer.java     |  4 ++--
       3 files changed, 11 insertions(+), 11 deletions(-)
      
      diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      index b7951912..a73baadc 100644
      --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      @@ -180,8 +180,8 @@ public class SimpleCanalConnector implements CanalConnector {
       
                   Ack ackBody = Ack.parseFrom(ack.getBody());
                   if (ackBody.getErrorCode() > 0) {
      -                throw new CanalClientException("something goes wrong when doing authentication: "
      -                                               + ackBody.getErrorMessage());
      +                throw new CanalClientException(
      +                    "something goes wrong when doing authentication: " + ackBody.getErrorMessage());
                   }
       
                   connected = true;
      @@ -323,7 +323,9 @@ public class SimpleCanalConnector implements CanalConnector {
               Packet p = Packet.parseFrom(data);
               switch (p.getType()) {
                   case MESSAGES: {
      -                if (!p.getCompression().equals(Compression.NONE)) {
      +                // if (!p.getCompression().equals(Compression.NONE)) {
      +                if (!p.getCompression().equals(Compression.NONE)
      +                    && !p.getCompression().equals(Compression.COMPRESSIONCOMPATIBLEPROTO2)) {
                           throw new CanalClientException("compression is not supported in this connector");
                       }
       
      @@ -360,11 +362,8 @@ public class SimpleCanalConnector implements CanalConnector {
                   .setBatchId(batchId)
                   .build();
               try {
      -            writeWithHeader(Packet.newBuilder()
      -                .setType(PacketType.CLIENTACK)
      -                .setBody(ca.toByteString())
      -                .build()
      -                .toByteArray());
      +            writeWithHeader(
      +                Packet.newBuilder().setType(PacketType.CLIENTACK).setBody(ca.toByteString()).build().toByteArray());
               } catch (IOException e) {
                   throw new CanalClientException(e);
               }
      diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java
      index e716d2d6..b5640cfd 100644
      --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java
      +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java
      @@ -36,7 +36,8 @@ public class MessageDeserializer implements Deserializer {
                       CanalPacket.Packet p = CanalPacket.Packet.parseFrom(data);
                       switch (p.getType()) {
                           case MESSAGES: {
      -                        if (!p.getCompression().equals(CanalPacket.Compression.NONE)) {
      +                        if (!p.getCompression().equals(CanalPacket.Compression.NONE)
      +                                && !p.getCompression().equals(CanalPacket.Compression.COMPRESSIONCOMPATIBLEPROTO2)) {
                                   throw new CanalClientException("compression is not supported in this connector");
                               }
       
      diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      index 22f15229..84656fb1 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      @@ -79,8 +79,8 @@ public class CanalKafkaProducer {
                       } else {
                           record = new ProducerRecord(topic.getTopic(), message);
                       }
      -                Future future = producer.send(record);
      -                future.get();
      +
      +                producer.send(record);
                   } else {
                       // 发送扁平数据json
                       List flatMessages = FlatMessage.messageConverter(message);
      
      From 9748fceda8cefb01e673da5575c914e9799d6ffd Mon Sep 17 00:00:00 2001
      From: WithLin 
      Date: Mon, 17 Sep 2018 11:11:07 +0800
      Subject: [PATCH 141/360] fix proto3 bug
      
      ---
       .../alibaba/otter/canal/client/impl/SimpleCanalConnector.java   | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      index b7951912..b8fcc36a 100644
      --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      @@ -323,7 +323,7 @@ public class SimpleCanalConnector implements CanalConnector {
               Packet p = Packet.parseFrom(data);
               switch (p.getType()) {
                   case MESSAGES: {
      -                if (!p.getCompression().equals(Compression.NONE)) {
      +                if (!p.getCompression().equals(Compression.NONE) && !p.getCompression().equals(Compression.COMPRESSIONCOMPATIBLEPROTO2)) {
                           throw new CanalClientException("compression is not supported in this connector");
                       }
       
      
      From 1a8d6e65cf3b471c6ca90f28f13fed6a3464fd15 Mon Sep 17 00:00:00 2001
      From: WithLin 
      Date: Mon, 17 Sep 2018 11:17:55 +0800
      Subject: [PATCH 142/360] FIx
      
      ---
       .../alibaba/otter/canal/client/impl/SimpleCanalConnector.java   | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      index b8fcc36a..956c06af 100644
      --- a/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      +++ b/client/src/main/java/com/alibaba/otter/canal/client/impl/SimpleCanalConnector.java
      @@ -323,7 +323,7 @@ public class SimpleCanalConnector implements CanalConnector {
               Packet p = Packet.parseFrom(data);
               switch (p.getType()) {
                   case MESSAGES: {
      -                if (!p.getCompression().equals(Compression.NONE) && !p.getCompression().equals(Compression.COMPRESSIONCOMPATIBLEPROTO2)) {
      +                if (!p.getCompression().equals(Compression.COMPRESSIONCOMPATIBLEPROTO2)) {
                           throw new CanalClientException("compression is not supported in this connector");
                       }
       
      
      From 19f765df77410c2e56a893a1d77aa98b084cef6c Mon Sep 17 00:00:00 2001
      From: zhikuodai 
      Date: Wed, 19 Sep 2018 15:16:38 +0800
      Subject: [PATCH 143/360] =?UTF-8?q?=E5=8D=95=E8=AF=8D=E6=8B=BC=E5=86=99?=
       =?UTF-8?q?=E4=BF=AE=E6=AD=A3?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../memory/MemoryEventStoreWithBuffer.java    | 24 +++++++++----------
       1 file changed, 12 insertions(+), 12 deletions(-)
      
      diff --git a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java
      index 6c6d08ff..5fdb0323 100644
      --- a/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java
      +++ b/store/src/main/java/com/alibaba/otter/canal/store/memory/MemoryEventStoreWithBuffer.java
      @@ -35,16 +35,16 @@ import com.alibaba.otter.canal.store.model.Events;
        */
       public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge implements CanalEventStore, CanalStoreScavenge {
       
      -    private static final long INIT_SQEUENCE = -1;
      +    private static final long INIT_SEQUENCE = -1;
           private int               bufferSize    = 16 * 1024;
           private int               bufferMemUnit = 1024;                         // memsize的单位,默认为1kb大小
           private int               indexMask;
           private Event[]           entries;
       
           // 记录下put/get/ack操作的三个下标
      -    private AtomicLong        putSequence   = new AtomicLong(INIT_SQEUENCE); // 代表当前put操作最后一次写操作发生的位置
      -    private AtomicLong        getSequence   = new AtomicLong(INIT_SQEUENCE); // 代表当前get操作读取的最后一条的位置
      -    private AtomicLong        ackSequence   = new AtomicLong(INIT_SQEUENCE); // 代表当前ack操作的最后一条的位置
      +    private AtomicLong        putSequence   = new AtomicLong(INIT_SEQUENCE); // 代表当前put操作最后一次写操作发生的位置
      +    private AtomicLong        getSequence   = new AtomicLong(INIT_SEQUENCE); // 代表当前get操作读取的最后一条的位置
      +    private AtomicLong        ackSequence   = new AtomicLong(INIT_SEQUENCE); // 代表当前ack操作的最后一条的位置
       
           // 记录下put/get/ack操作的三个memsize大小
           private AtomicLong        putMemSize    = new AtomicLong(0);
      @@ -358,17 +358,17 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple
               lock.lock();
               try {
                   long firstSeqeuence = ackSequence.get();
      -            if (firstSeqeuence == INIT_SQEUENCE && firstSeqeuence < putSequence.get()) {
      +            if (firstSeqeuence == INIT_SEQUENCE && firstSeqeuence < putSequence.get()) {
                       // 没有ack过数据
                       Event event = entries[getIndex(firstSeqeuence + 1)]; // 最后一次ack为-1,需要移动到下一条,included
                                                                            // = false
                       return CanalEventUtils.createPosition(event, false);
      -            } else if (firstSeqeuence > INIT_SQEUENCE && firstSeqeuence < putSequence.get()) {
      +            } else if (firstSeqeuence > INIT_SEQUENCE && firstSeqeuence < putSequence.get()) {
                       // ack未追上put操作
                       Event event = entries[getIndex(firstSeqeuence + 1)]; // 最后一次ack的位置数据
                                                                            // + 1
                       return CanalEventUtils.createPosition(event, true);
      -            } else if (firstSeqeuence > INIT_SQEUENCE && firstSeqeuence == putSequence.get()) {
      +            } else if (firstSeqeuence > INIT_SEQUENCE && firstSeqeuence == putSequence.get()) {
                       // 已经追上,store中没有数据
                       Event event = entries[getIndex(firstSeqeuence)]; // 最后一次ack的位置数据,和last为同一条,included
                                                                        // = false
      @@ -387,10 +387,10 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple
               lock.lock();
               try {
                   long latestSequence = putSequence.get();
      -            if (latestSequence > INIT_SQEUENCE && latestSequence != ackSequence.get()) {
      +            if (latestSequence > INIT_SEQUENCE && latestSequence != ackSequence.get()) {
                       Event event = entries[(int) putSequence.get() & indexMask]; // 最后一次写入的数据,最后一条未消费的数据
                       return CanalEventUtils.createPosition(event, true);
      -            } else if (latestSequence > INIT_SQEUENCE && latestSequence == ackSequence.get()) {
      +            } else if (latestSequence > INIT_SEQUENCE && latestSequence == ackSequence.get()) {
                       // ack已经追上了put操作
                       Event event = entries[(int) putSequence.get() & indexMask]; // 最后一次写入的数据,included
                                                                                   // =
      @@ -473,9 +473,9 @@ public class MemoryEventStoreWithBuffer extends AbstractCanalStoreScavenge imple
               final ReentrantLock lock = this.lock;
               lock.lock();
               try {
      -            putSequence.set(INIT_SQEUENCE);
      -            getSequence.set(INIT_SQEUENCE);
      -            ackSequence.set(INIT_SQEUENCE);
      +            putSequence.set(INIT_SEQUENCE);
      +            getSequence.set(INIT_SEQUENCE);
      +            ackSequence.set(INIT_SEQUENCE);
       
                   putMemSize.set(0);
                   getMemSize.set(0);
      
      From 09df602f3bd060a297bb7362409b344523cb5b8f Mon Sep 17 00:00:00 2001
      From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= 
      Date: Thu, 20 Sep 2018 11:30:10 +0800
      Subject: [PATCH 144/360] support filterTransactionEntry
      
      ---
       .../parse/inbound/mysql/MysqlEventParser.java | 25 ++++++-----
       .../otter/canal/protocol/FlatMessage.java     |  9 +++-
       .../otter/canal/kafka/CanalKafkaProducer.java |  2 -
       .../otter/canal/kafka/MessageSerializer.java  |  7 ++--
       .../server/netty/handler/SessionHandler.java  |  3 +-
       .../canal/sink/entry/EntryEventSink.java      | 42 ++++++++++---------
       6 files changed, 47 insertions(+), 41 deletions(-)
      
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
      index a5c8d870..cf102d3d 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java
      @@ -8,7 +8,6 @@ import java.util.HashMap;
       import java.util.List;
       import java.util.Map;
       import java.util.TimerTask;
      -import java.util.concurrent.atomic.AtomicLong;
       
       import org.apache.commons.lang.StringUtils;
       import org.springframework.util.CollectionUtils;
      @@ -49,27 +48,27 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE
       
           private CanalHAController    haController                      = null;
       
      -    private int                  defaultConnectionTimeoutInSeconds = 30;                // sotimeout
      +    private int                  defaultConnectionTimeoutInSeconds = 30;       // sotimeout
           private int                  receiveBufferSize                 = 64 * 1024;
           private int                  sendBufferSize                    = 64 * 1024;
           // 数据库信息
      -    protected AuthenticationInfo masterInfo;                                            // 主库
      -    protected AuthenticationInfo standbyInfo;                                           // 备库
      +    protected AuthenticationInfo masterInfo;                                   // 主库
      +    protected AuthenticationInfo standbyInfo;                                  // 备库
           // binlog信息
           protected EntryPosition      masterPosition;
           protected EntryPosition      standbyPosition;
      -    private long                 slaveId;                                               // 链接到mysql的slave
      +    private long                 slaveId;                                      // 链接到mysql的slave
           // 心跳检查信息
      -    private String               detectingSQL;                                          // 心跳sql
      -    private MysqlConnection      metaConnection;                                        // 查询meta信息的链接
      -    private TableMetaCache       tableMetaCache;                                        // 对应meta
      -    private int                  fallbackIntervalInSeconds         = 60;                // 切换回退时间
      -    private BinlogFormat[]       supportBinlogFormats;                                  // 支持的binlogFormat,如果设置会执行强校验
      -    private BinlogImage[]        supportBinlogImages;                                   // 支持的binlogImage,如果设置会执行强校验
      +    private String               detectingSQL;                                 // 心跳sql
      +    private MysqlConnection      metaConnection;                               // 查询meta信息的链接
      +    private TableMetaCache       tableMetaCache;                               // 对应meta
      +    private int                  fallbackIntervalInSeconds         = 60;       // 切换回退时间
      +    private BinlogFormat[]       supportBinlogFormats;                         // 支持的binlogFormat,如果设置会执行强校验
      +    private BinlogImage[]        supportBinlogImages;                          // 支持的binlogImage,如果设置会执行强校验
       
           // update by yishun.chen,特殊异常处理参数
      -    private int                  dumpErrorCount                    = 0;                 // binlogDump失败异常计数
      -    private int                  dumpErrorCountThreshold           = 2;                 // binlogDump失败异常计数阀值
      +    private int                  dumpErrorCount                    = 0;        // binlogDump失败异常计数
      +    private int                  dumpErrorCountThreshold           = 2;        // binlogDump失败异常计数阀值
       
           protected ErosaConnection buildErosaConnection() {
               return buildMysqlConnection(this.runningInfo);
      diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java
      index e6aab531..d7150b79 100644
      --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java
      +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java
      @@ -1,7 +1,12 @@
       package com.alibaba.otter.canal.protocol;
       
       import java.io.Serializable;
      -import java.util.*;
      +import java.util.ArrayList;
      +import java.util.HashSet;
      +import java.util.LinkedHashMap;
      +import java.util.List;
      +import java.util.Map;
      +import java.util.Set;
       
       import com.google.common.collect.Table;
       import com.google.protobuf.ByteString;
      @@ -243,6 +248,8 @@ public class FlatMessage implements Serializable {
                           }
                           int hash = value.hashCode();
                           int pkHash = Math.abs(hash) % partitionsNum;
      +                    // math.abs可能返回负值,这里再取反,把出现负值的数据还是写到固定的分区,仍然可以保证消费顺序
      +                    pkHash = Math.abs(pkHash);
       
                           FlatMessage flatMessageTmp = partitionMessages[pkHash];
                           if (flatMessageTmp == null) {
      diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      index 84656fb1..4942a37b 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      @@ -2,12 +2,10 @@ package com.alibaba.otter.canal.kafka;
       
       import java.util.List;
       import java.util.Properties;
      -import java.util.concurrent.Future;
       
       import org.apache.kafka.clients.producer.KafkaProducer;
       import org.apache.kafka.clients.producer.Producer;
       import org.apache.kafka.clients.producer.ProducerRecord;
      -import org.apache.kafka.clients.producer.RecordMetadata;
       import org.apache.kafka.common.serialization.StringSerializer;
       import org.slf4j.Logger;
       import org.slf4j.LoggerFactory;
      diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java
      index 49db5aa1..86b31502 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java
      @@ -28,6 +28,7 @@ public class MessageSerializer implements Serializer {
           }
       
           @Override
      +    @SuppressWarnings("deprecation")
           public byte[] serialize(String topic, Message data) {
               try {
                   if (data != null) {
      @@ -47,11 +48,9 @@ public class MessageSerializer implements Serializer {
                               messageSize += 1 * rowEntries.size();
                               // packet size
                               int size = 0;
      -                        size += CodedOutputStream.computeEnumSize(3,
      -                            PacketType.MESSAGES.getNumber());
      +                        size += CodedOutputStream.computeEnumSize(3, PacketType.MESSAGES.getNumber());
                               size += CodedOutputStream.computeTagSize(5)
      -                                + CodedOutputStream.computeRawVarint32Size(messageSize)
      -                                + messageSize;
      +                                + CodedOutputStream.computeRawVarint32Size(messageSize) + messageSize;
                               // build data
                               byte[] body = new byte[size];
                               CodedOutputStream output = CodedOutputStream.newInstance(body);
      diff --git a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java
      index fe8c5538..d060c026 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/server/netty/handler/SessionHandler.java
      @@ -3,7 +3,6 @@ package com.alibaba.otter.canal.server.netty.handler;
       import java.util.List;
       import java.util.concurrent.TimeUnit;
       
      -import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator;
       import org.apache.commons.lang.StringUtils;
       import org.apache.commons.lang.exception.ExceptionUtils;
       import org.jboss.netty.buffer.ChannelBuffer;
      @@ -34,6 +33,7 @@ import com.alibaba.otter.canal.protocol.ClientIdentity;
       import com.alibaba.otter.canal.protocol.Message;
       import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded;
       import com.alibaba.otter.canal.server.netty.NettyUtils;
      +import com.alibaba.otter.canal.server.netty.listener.ChannelFutureAggregator;
       import com.google.protobuf.ByteString;
       import com.google.protobuf.CodedOutputStream;
       import com.google.protobuf.WireFormat;
      @@ -50,6 +50,7 @@ public class SessionHandler extends SimpleChannelHandler {
               this.embeddedServer = embeddedServer;
           }
       
      +    @SuppressWarnings("deprecation")
           public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
               logger.info("message receives in session handler...");
               long start = System.nanoTime();
      diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java
      index 7afde6b6..76124ec4 100644
      --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java
      +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java
      @@ -33,10 +33,13 @@ public class EntryEventSink extends AbstractCanalEventSink eventStore;
      -    protected boolean              filterTransactionEntry        = false;                                        // 是否需要过滤事务头/尾
      +    protected boolean              filterTransactionEntry        = false;                                        // 是否需要尽可能过滤事务头/尾
           protected boolean              filterEmtryTransactionEntry   = true;                                         // 是否需要过滤空的事务头/尾
           protected long                 emptyTransactionInterval      = 5 * 1000;                                     // 空的事务输出的频率
      -    protected long                 emptyTransctionThresold       = 8192;                                         // 超过1024个事务头,输出一个
      +    protected long                 emptyTransctionThresold       = 8192;                                         // 超过8192个事务头,输出一个
      +
      +    protected volatile long        lastTransactionTimestamp      = 0L;
      +    protected AtomicLong           lastTransactionCount          = new AtomicLong(0L);
           protected volatile long        lastEmptyTransactionTimestamp = 0L;
           protected AtomicLong           lastEmptyTransactionCount     = new AtomicLong(0L);
           private AtomicLong             eventsSinkBlockingTime        = new AtomicLong(0L);
      @@ -74,17 +77,7 @@ public class EntryEventSink extends AbstractCanalEventSink entrys, InetSocketAddress remoteAddress, String destination)
                                                                                                                  throws CanalSinkException,
                                                                                                                  InterruptedException {
      -        List rowDatas = entrys;
      -        if (filterTransactionEntry) {
      -            rowDatas = new ArrayList();
      -            for (CanalEntry.Entry entry : entrys) {
      -                if (entry.getEntryType() == EntryType.ROWDATA) {
      -                    rowDatas.add(entry);
      -                }
      -            }
      -        }
      -
      -        return sinkData(rowDatas, remoteAddress);
      +        return sinkData(entrys, remoteAddress);
           }
       
           private boolean sinkData(List entrys, InetSocketAddress remoteAddress)
      @@ -97,17 +90,26 @@ public class EntryEventSink extends AbstractCanalEventSink emptyTransactionInterval
      +                    || lastTransactionCount.incrementAndGet() > emptyTransctionThresold) {
      +                    lastTransactionCount.set(0L);
      +                    lastTransactionTimestamp = currentTimestamp;
      +                    continue;
      +                }
      +            }
      +
                   hasRowData |= (entry.getEntryType() == EntryType.ROWDATA);
                   hasHeartBeat |= (entry.getEntryType() == EntryType.HEARTBEAT);
      +            Event event = new Event(new LogIdentity(remoteAddress, -1L), entry);
      +            events.add(event);
               }
       
      -        if (hasRowData) {
      -            // 存在row记录
      -            return doSink(events);
      -        } else if (hasHeartBeat) {
      -            // 存在heartbeat记录,直接跳给后续处理
      +        if (hasRowData || hasHeartBeat) {
      +            // 存在row记录 或者 存在heartbeat记录,直接跳给后续处理
                   return doSink(events);
               } else {
                   // 需要过滤的数据
      
      From d5aa324ce455471cf2ea62d519cecf0062628001 Mon Sep 17 00:00:00 2001
      From: duhengforever 
      Date: Thu, 20 Sep 2018 15:48:52 +0800
      Subject: [PATCH 145/360] Add RocketMQ canal connector
      
      ---
       .../adapter/support/CanalClientConfig.java    |  12 +-
       .../adapter/loader/CanalAdapterLoader.java    |  43 ++--
       .../loader/CanalAdapterRocketMQWorker.java    | 165 +++++++++++++
       .../src/main/resources/canal-client.yml       |  26 +--
       client/pom.xml                                | 217 +++++++++---------
       .../otter/canal/client/DeserializerUtil.java  |  42 ++++
       .../client/kafka/MessageDeserializer.java     |  39 +---
       .../client/rocketmq/ConsumerBatchMessage.java |  47 ++++
       .../rocketmq/RocketMQCanalConnector.java      | 197 ++++++++++++++++
       .../RocketMQCanalConnectorProvider.java       |  20 ++
       .../rocketmq/RocketMQCanalListener.java       |  11 +
       deployer/pom.xml                              |   2 +-
       .../otter/canal/deployer/CanalLauncher.java   |  25 +-
       deployer/src/main/resources/canal.properties  |   4 +-
       .../resources/example/instance.properties     |   9 +-
       .../src/main/resources/{kafka.yml => mq.yml}  |   2 +-
       server/pom.xml                                |   5 +
       .../canal/common/CanalMessageSerializer.java  |  70 ++++++
       .../MQProperties.java}                        |  12 +-
       .../otter/canal/kafka/CanalKafkaProducer.java |  39 +---
       .../otter/canal/kafka/MessageSerializer.java  |  56 +----
       .../canal/rocketmq/CanalRocketMQProducer.java |  58 +++++
       .../CanalMQStarter.java}                      |  80 +++----
       .../otter/canal/spi/CanalMQProducer.java      |  28 +++
       24 files changed, 881 insertions(+), 328 deletions(-)
       create mode 100644 client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java
       create mode 100644 client/src/main/java/com/alibaba/otter/canal/client/DeserializerUtil.java
       create mode 100644 client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java
       create mode 100644 client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java
       create mode 100644 client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java
       create mode 100644 client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java
       rename deployer/src/main/resources/{kafka.yml => mq.yml} (85%)
       create mode 100644 server/src/main/java/com/alibaba/otter/canal/common/CanalMessageSerializer.java
       rename server/src/main/java/com/alibaba/otter/canal/{kafka/KafkaProperties.java => common/MQProperties.java} (92%)
       create mode 100644 server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java
       rename server/src/main/java/com/alibaba/otter/canal/{kafka/CanalKafkaStarter.java => server/CanalMQStarter.java} (66%)
       create mode 100644 server/src/main/java/com/alibaba/otter/canal/spi/CanalMQProducer.java
      
      diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java
      index f6fa7707..e0658138 100644
      --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java
      +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java
      @@ -20,7 +20,7 @@ public class CanalClientConfig {
       
           private String              bootstrapServers;
       
      -    private List    kafkaTopics;
      +    private List       mqTopics;
       
           private List canalInstances;
       
      @@ -56,12 +56,12 @@ public class CanalClientConfig {
               this.bootstrapServers = bootstrapServers;
           }
       
      -    public List getKafkaTopics() {
      -        return kafkaTopics;
      +    public List getMqTopics() {
      +        return mqTopics;
           }
       
      -    public void setKafkaTopics(List kafkaTopics) {
      -        this.kafkaTopics = kafkaTopics;
      +    public void setMqTopics(List mqTopics) {
      +        this.mqTopics = mqTopics;
           }
       
           public List getCanalInstances() {
      @@ -110,7 +110,7 @@ public class CanalClientConfig {
               }
           }
       
      -    public static class KafkaTopic {
      +    public static class MQTopic {
       
               private String      topic;
       
      diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java
      index 98973f11..a705fad7 100644
      --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java
      +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java
      @@ -17,34 +17,34 @@ import java.util.concurrent.ExecutorService;
       import java.util.concurrent.Executors;
       
       /**
      - * 外部适配器的加载器
      + * RocktMQ外部适配器的加载器
        *
      - * @author machengyuan 2018-8-19 下午11:45:49
        * @version 1.0.0
        */
       public class CanalAdapterLoader {
       
      -    private static final Logger                  logger            = LoggerFactory.getLogger(CanalAdapterLoader.class);
      +    private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class);
       
      -    private CanalClientConfig                    canalClientConfig;
      +    private CanalClientConfig canalClientConfig;
       
      -    private Map      canalWorkers      = new HashMap<>();
      +    private Map canalWorkers = new HashMap<>();
       
           private Map canalKafkaWorkers = new HashMap<>();
      +    private Map canalMQWorker = new HashMap<>();
       
      -    private ExtensionLoader   loader;
      +    private ExtensionLoader loader;
       
      -    public CanalAdapterLoader(CanalClientConfig canalClientConfig){
      +    public CanalAdapterLoader(CanalClientConfig canalClientConfig) {
               this.canalClientConfig = canalClientConfig;
           }
       
           /**
      -     * 初始化canal-client、 canal-client-kafka的适配器
      +     * 初始化canal-client、 canal-client-rocketmq的适配器
            */
           public void init() {
      -        // canal instances 和 kafka topics 配置不能同时为空
      -        if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getKafkaTopics() == null) {
      -            throw new RuntimeException("Blank config property: canalInstances or canalKafkaTopics");
      +        // canal instances 和 rocketmq topics 配置不能同时为空
      +        if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getMqTopics() == null) {
      +            throw new RuntimeException("Blank config property: canalInstances or canalMQTopics");
               }
       
               loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class,
      @@ -87,10 +87,10 @@ public class CanalAdapterLoader {
                   }
               }
       
      -        // 初始化canal-client-kafka的适配器
      -        if (canalClientConfig.getKafkaTopics() != null) {
      -            for (CanalClientConfig.KafkaTopic kafkaTopic : canalClientConfig.getKafkaTopics()) {
      -                for (CanalClientConfig.Group group : kafkaTopic.getGroups()) {
      +        // 初始化canal-client-rocketmq的适配器
      +        if (canalClientConfig.getMqTopics() != null) {
      +            for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) {
      +                for (CanalClientConfig.Group group : topic.getGroups()) {
                           List> canalOuterAdapterGroups = new ArrayList<>();
       
                           List canalOuterAdapters = new ArrayList<>();
      @@ -102,16 +102,15 @@ public class CanalAdapterLoader {
                           }
                           canalOuterAdapterGroups.add(canalOuterAdapters);
       
      -                    // String zkServers = canalClientConfig.getZookeeperHosts();
      -                    CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts,
      +                    CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(
                               canalClientConfig.getBootstrapServers(),
      -                        kafkaTopic.getTopic(),
      +                        topic.getTopic(),
                               group.getGroupId(),
                               canalOuterAdapterGroups);
      -                    canalKafkaWorkers.put(kafkaTopic.getTopic() + "-" + group.getGroupId(), canalKafkaWorker);
      -                    canalKafkaWorker.start();
      -                    logger.info("Start adapter for canal-client kafka topic: {} succeed",
      -                        kafkaTopic.getTopic() + "-" + group.getGroupId());
      +                    canalMQWorker.put(topic.getTopic() + "-" + group.getGroupId(), rocketMQWorker);
      +                    rocketMQWorker.start();
      +                    logger.info("Start adapter for canal-client rocketmq topic: {} succeed",
      +                        topic.getTopic() + "-" + group.getGroupId());
                       }
                   }
               }
      diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java
      new file mode 100644
      index 00000000..029d9ada
      --- /dev/null
      +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java
      @@ -0,0 +1,165 @@
      +package com.alibaba.otter.canal.client.adapter.loader;
      +
      +import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter;
      +import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector;
      +import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider;
      +import com.alibaba.otter.canal.protocol.Message;
      +import java.util.List;
      +import java.util.concurrent.ExecutorService;
      +import java.util.concurrent.Executors;
      +import java.util.concurrent.TimeUnit;
      +import java.util.concurrent.atomic.AtomicBoolean;
      +import org.apache.kafka.clients.consumer.CommitFailedException;
      +import org.apache.kafka.common.errors.WakeupException;
      +
      +/**
      + * kafka对应的client适配器工作线程
      + *
      + * @author machengyuan 2018-8-19 下午11:30:49
      + * @version 1.0.0
      + */
      +public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker {
      +
      +    private RocketMQCanalConnector connector;
      +
      +    private String topic;
      +
      +    public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId,
      +        List> canalOuterAdapters) {
      +        this.canalOuterAdapters = canalOuterAdapters;
      +        this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size());
      +        this.topic = topic;
      +        this.canalDestination = topic;
      +        connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId);
      +    }
      +
      +    @Override
      +    public void start() {
      +        if (!running) {
      +            thread = new Thread(new Runnable() {
      +
      +                @Override
      +                public void run() {
      +                    process();
      +                }
      +            });
      +            thread.setUncaughtExceptionHandler(handler);
      +            running = true;
      +            thread.start();
      +        }
      +    }
      +
      +    @Override
      +    public void stop() {
      +        try {
      +            if (!running) {
      +                return;
      +            }
      +
      +            connector.stopRunning();
      +            running = false;
      +
      +            // if (switcher != null && !switcher.state()) {
      +            // switcher.set(true);
      +            // }
      +
      +            if (thread != null) {
      +                try {
      +                    thread.join();
      +                } catch (InterruptedException e) {
      +                    // ignore
      +                }
      +            }
      +            groupInnerExecutorService.shutdown();
      +            logger.info("topic {} connectors' worker thread dead!", this.topic);
      +            for (List outerAdapters : canalOuterAdapters) {
      +                for (CanalOuterAdapter adapter : outerAdapters) {
      +                    adapter.destroy();
      +                }
      +            }
      +            logger.info("topic {} all connectors destroyed!", this.topic);
      +        } catch (Exception e) {
      +            logger.error(e.getMessage(), e);
      +        }
      +    }
      +
      +    private void process() {
      +        while (!running)
      +            ;
      +        ExecutorService executor = Executors.newSingleThreadExecutor();
      +        final AtomicBoolean executing = new AtomicBoolean(true);
      +        while (running) {
      +            try {
      +                logger.info("=============> Start to connect topic: {} <=============", this.topic);
      +                connector.connect();
      +                logger.info("=============> Start to subscribe topic: {}<=============", this.topic);
      +                connector.subscribe();
      +                logger.info("=============> Subscribe topic: {} succeed<=============", this.topic);
      +                while (running) {
      +                    try {
      +                        // switcher.get(); //等待开关开启
      +
      +                        final Message message = connector.getWithoutAck(1);
      +
      +                        executing.set(true);
      +                        if (message != null) {
      +                            executor.submit(new Runnable() {
      +
      +                                @Override
      +                                public void run() {
      +                                    try {
      +                                        if (logger.isDebugEnabled()) {
      +                                            logger.debug("topic: {} batchId: {} batchSize: {} ",
      +                                                topic,
      +                                                message.getId(),
      +                                                message.getEntries().size());
      +                                        }
      +                                        long begin = System.currentTimeMillis();
      +                                        writeOut(message);
      +                                        long now = System.currentTimeMillis();
      +                                        if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) {
      +                                            logger.error("topic: {} batchId {} elapsed time: {} ms",
      +                                                topic,
      +                                                message.getId(),
      +                                                now - begin);
      +                                        }
      +                                        if (logger.isDebugEnabled()) {
      +                                            logger.debug("topic: {} batchId {} elapsed time: {} ms",
      +                                                topic,
      +                                                message.getId(),
      +                                                now - begin);
      +                                        }
      +                                        connector.ack(message.getId());
      +                                    } catch (Exception e) {
      +                                        logger.error(e.getMessage(), e);
      +                                    } finally {
      +                                        executing.compareAndSet(true, false);
      +                                    }
      +                                }
      +                            });
      +                        } else {
      +                            logger.debug("Message is null");
      +                        }
      +                    } catch (CommitFailedException e) {
      +                        logger.warn(e.getMessage());
      +                    } catch (Exception e) {
      +                        logger.error(e.getMessage(), e);
      +                        TimeUnit.SECONDS.sleep(1L);
      +                    }
      +                }
      +            } catch (Exception e) {
      +                logger.error(e.getMessage(), e);
      +            }
      +        }
      +
      +        executor.shutdown();
      +
      +        try {
      +            connector.unsubscribe();
      +        } catch (WakeupException e) {
      +            // No-op. Continue process
      +        }
      +        connector.stopRunning();
      +        logger.info("=============> Disconnect topic: {} <=============", this.topic);
      +    }
      +}
      diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml
      index 12e016a7..a5fc4813 100644
      --- a/client-launcher/src/main/resources/canal-client.yml
      +++ b/client-launcher/src/main/resources/canal-client.yml
      @@ -2,20 +2,20 @@ canalServerHost: 127.0.0.1:11111
       #zookeeperHosts: slave1:2181
       #bootstrapServers: slave1:6667,slave2:6667
       
      -canalInstances:
      -- instance: example
      -  adapterGroups:
      -  - outAdapters:
      -    - name: logger
      -    - name: hbase
      -      hosts: slave1:2181
      -      properties: {znodeParent: "/hbase-unsecure"}
      -#kafkaTopics:
      -#- topic: example
      -#  groups:
      -#  - groupId: example_g1
      -#    outAdapters:
      +#canalInstances:
      +#- instance: example
      +#  adapterGroups:
      +#  - outAdapters:
       #    - name: logger
       #    - name: hbase
       #      hosts: slave1:2181
       #      properties: {znodeParent: "/hbase-unsecure"}
      +mqTopics:
      +- topic: example
      +  groups:
      +  - groupId: example_g1
      +    outAdapters:
      +    - name: logger
      +#    - name: hbase
      +#      hosts: slave1:2181
      +#      properties: {znodeParent: "/hbase-unsecure"}
      diff --git a/client/pom.xml b/client/pom.xml
      index 2e48f9c5..b44e3129 100644
      --- a/client/pom.xml
      +++ b/client/pom.xml
      @@ -1,108 +1,115 @@
      -
      -	4.0.0
      -	
      -		com.alibaba.otter
      -		canal
      -		1.1.1-SNAPSHOT
      -		../pom.xml
      -	
      -	com.alibaba.otter
      -	canal.client
      -	jar
      -	canal client module for otter ${project.version}
      -	
      -		
      -			com.alibaba.otter
      -			canal.protocol
      -			${project.version}
      -		
      -		
      -		
      -			org.apache.kafka
      -			kafka-clients
      -			1.1.1
      -			provided
      -		
      +
      +    4.0.0
      +    
      +        com.alibaba.otter
      +        canal
      +        1.1.1-SNAPSHOT
      +        ../pom.xml
      +    
      +    com.alibaba.otter
      +    canal.client
      +    jar
      +    canal client module for otter ${project.version}
      +    
      +        
      +            com.alibaba.otter
      +            canal.protocol
      +            ${project.version}
      +        
      +        
      +        
      +            org.apache.kafka
      +            kafka-clients
      +            1.1.1
      +            provided
      +        
      +        
      +            org.apache.rocketmq
      +            rocketmq-client
      +            4.3.0
      +        
      +        
      +        
      +            junit
      +            junit
      +        
      +    
       
      -		
      -		
      -			junit
      -			junit
      -		
      -	
      -	
      -	
      -		
      -			dev
      -			
      -				true
      -				
      -					env
      -					!javadoc
      -				
      -			
      -		
      +    
      +        
      +            dev
      +            
      +                true
      +                
      +                    env
      +                    !javadoc
      +                
      +            
      +        
       
      -		
      -			javadoc
      -			
      -				
      -					env
      -					javadoc
      -				
      -			
      -			
      -				
      -					
      -						org.apache.maven.plugins
      -						maven-javadoc-plugin
      -						2.9.1
      -						
      -							
      -								attach-javadocs
      -								package
      -								
      -									jar
      -								
      -							
      -						
      -						
      -							true
      -							public
      -							true
      -							
      ${project.artifactId}-${project.version}
      -
      ${project.artifactId}-${project.version}
      - ${project.artifactId}-${project.version} - - https://github.com/alibaba/canal - - ${project.build.directory}/apidocs/apidocs/${project.version} -
      -
      - - org.apache.maven.plugins - maven-scm-publish-plugin - 1.0-beta-2 - - - attach-javadocs - package - - publish-scm - - - - - ${project.build.directory}/scmpublish - Publishing javadoc for ${project.artifactId}:${project.version} - ${project.build.directory}/apidocs - true - scm:git:git@github.com:alibaba/canal.git - gh-pages - - -
      -
      -
      -
      + + javadoc + + + env + javadoc + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + package + + jar + + + + + true + public + true +
      ${project.artifactId}-${project.version}
      +
      ${project.artifactId}-${project.version}
      + ${project.artifactId}-${project.version} + + https://github.com/alibaba/canal + + ${project.build.directory}/apidocs/apidocs/${project.version} + +
      +
      + + org.apache.maven.plugins + maven-scm-publish-plugin + 1.0-beta-2 + + + attach-javadocs + package + + publish-scm + + + + + ${project.build.directory}/scmpublish + Publishing javadoc for ${project.artifactId}:${project.version} + + ${project.build.directory}/apidocs + true + scm:git:git@github.com:alibaba/canal.git + gh-pages + + +
      +
      +
      +
      diff --git a/client/src/main/java/com/alibaba/otter/canal/client/DeserializerUtil.java b/client/src/main/java/com/alibaba/otter/canal/client/DeserializerUtil.java new file mode 100644 index 00000000..54b1bb7a --- /dev/null +++ b/client/src/main/java/com/alibaba/otter/canal/client/DeserializerUtil.java @@ -0,0 +1,42 @@ +package com.alibaba.otter.canal.client; + +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.CanalPacket; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import com.google.protobuf.ByteString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DeserializerUtil { + private static Logger logger = LoggerFactory.getLogger(DeserializerUtil.class); + + public static Message deserializer(byte[] data) { + try { + if (data == null) { + return null; + } else { + CanalPacket.Packet p = CanalPacket.Packet.parseFrom(data); + switch (p.getType()) { + case MESSAGES: { + if (!p.getCompression().equals(CanalPacket.Compression.NONE)) { + throw new CanalClientException("compression is not supported in this connector"); + } + + CanalPacket.Messages messages = CanalPacket.Messages.parseFrom(p.getBody()); + Message result = new Message(messages.getBatchId()); + for (ByteString byteString : messages.getMessagesList()) { + result.addEntry(CanalEntry.Entry.parseFrom(byteString)); + } + return result; + } + default: + break; + } + } + } catch (Exception e) { + logger.error("Error when deserializing byte[] to message ", e); + } + return null; + } +} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java index e716d2d6..af381283 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/MessageDeserializer.java @@ -1,17 +1,12 @@ package com.alibaba.otter.canal.client.kafka; +import com.alibaba.otter.canal.client.DeserializerUtil; +import com.alibaba.otter.canal.protocol.Message; import java.util.Map; - import org.apache.kafka.common.serialization.Deserializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.protocol.CanalEntry; -import com.alibaba.otter.canal.protocol.CanalPacket; -import com.alibaba.otter.canal.protocol.Message; -import com.alibaba.otter.canal.protocol.exception.CanalClientException; -import com.google.protobuf.ByteString; - /** * Kafka Message类的反序列化 * @@ -27,34 +22,8 @@ public class MessageDeserializer implements Deserializer { } @Override - public Message deserialize(String topic, byte[] data) { - try { - if (data == null) { - return null; - } - else { - CanalPacket.Packet p = CanalPacket.Packet.parseFrom(data); - switch (p.getType()) { - case MESSAGES: { - if (!p.getCompression().equals(CanalPacket.Compression.NONE)) { - throw new CanalClientException("compression is not supported in this connector"); - } - - CanalPacket.Messages messages = CanalPacket.Messages.parseFrom(p.getBody()); - Message result = new Message(messages.getBatchId()); - for (ByteString byteString : messages.getMessagesList()) { - result.addEntry(CanalEntry.Entry.parseFrom(byteString)); - } - return result; - } - default: - break; - } - } - } catch (Exception e) { - logger.error("Error when deserializing byte[] to message "); - } - return null; + public Message deserialize(String topic1, byte[] data) { + return DeserializerUtil.deserializer(data); } @Override diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java new file mode 100644 index 00000000..ef83a798 --- /dev/null +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java @@ -0,0 +1,47 @@ +package com.alibaba.otter.canal.client.rocketmq; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class ConsumerBatchMessage { + private final BlockingQueue data; + private CountDownLatch latch; + private boolean hasFailure = false; + + public ConsumerBatchMessage(BlockingQueue data) { + this.data = data; + latch = new CountDownLatch(data.size()); + } + + public boolean waitFinish(long timeout) throws InterruptedException { + return latch.await(timeout, TimeUnit.MILLISECONDS); + } + + public boolean isSuccess() { + return !hasFailure; + } + + public BlockingQueue getData() { + return data; + } + + /** + * Countdown if the sub message is successful. + */ + public void ack() { + latch.countDown(); + } + + /** + * Countdown and fail-fast if the sub message is failed. + */ + public void fail() { + hasFailure = true; + // fail fast + long count = latch.getCount(); + for (int i = 0; i < count; i++) { + latch.countDown(); + } + } +} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java new file mode 100644 index 00000000..e6407308 --- /dev/null +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java @@ -0,0 +1,197 @@ +package com.alibaba.otter.canal.client.rocketmq; + +import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.otter.canal.client.DeserializerUtil; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang.StringUtils; +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; +import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.common.message.MessageExt; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class RocketMQCanalConnector implements CanalConnector { + private static final Logger logger = LoggerFactory.getLogger(RocketMQCanalConnector.class); + + private String nameServer; + private String topic; + private String groupName; + private volatile boolean connected = false; + private DefaultMQPushConsumer rocketMQConsumer; + private BlockingQueue> messageBlockingQueue; + Map> messageCache; + private long batchProcessTimeout = 10000; + + public RocketMQCanalConnector(String nameServer, String topic, String groupName) { + this.nameServer = nameServer; + this.topic = topic; + this.groupName = groupName; + messageBlockingQueue = new LinkedBlockingQueue<>(); + messageCache = new ConcurrentHashMap<>(); + } + + @Override + public void connect() throws CanalClientException { + rocketMQConsumer = new DefaultMQPushConsumer(groupName); + } + + @Override + public void disconnect() throws CanalClientException { + rocketMQConsumer.shutdown(); + } + + @Override + public boolean checkValid() throws CanalClientException { + return connected; + } + + @Override + public synchronized void subscribe(String filter) throws CanalClientException { + if (connected) { + return; + } + try { + rocketMQConsumer.subscribe(topic, "*"); + if (rocketMQConsumer == null) { + rocketMQConsumer = new DefaultMQPushConsumer(groupName); + if (!StringUtils.isBlank(nameServer)) { + rocketMQConsumer.setNamesrvAddr(nameServer); + } + } + rocketMQConsumer.registerMessageListener(new MessageListenerOrderly() { + @Override + public ConsumeOrderlyStatus consumeMessage(List messageExts, + ConsumeOrderlyContext context) { + context.setAutoCommit(true); + boolean isSuccess = process(messageExts); + if (isSuccess) { + return ConsumeOrderlyStatus.SUCCESS; + } else { + return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT; + } + } + }); + rocketMQConsumer.start(); + } catch (MQClientException ex) { + connected = false; + logger.error("Start RocketMQ consumer error", ex); + } + connected = true; + } + + private boolean process(List messageExts) { + BlockingQueue messageList = new LinkedBlockingQueue<>(); + for (MessageExt messageExt : messageExts) { + byte[] data = messageExt.getBody(); + Message message = DeserializerUtil.deserializer(data); + try { + messageList.put(message); + } catch (InterruptedException ex) { + logger.error("Add message error"); + } + } + ConsumerBatchMessage batchMessage = new ConsumerBatchMessage<>(messageList); + try { + messageBlockingQueue.put(batchMessage); + } catch (InterruptedException e) { + logger.error("Put message to queue error", e); + throw new RuntimeException(e); + } + boolean isCompleted; + try { + isCompleted = batchMessage.waitFinish(batchProcessTimeout); + } catch (InterruptedException e) { + logger.error("Interrupted when waiting messages to be finished.", e); + throw new RuntimeException(e); + } + boolean isSuccess = batchMessage.isSuccess(); + return isCompleted && isSuccess; + } + + @Override + public void subscribe() throws CanalClientException { + this.subscribe(null); + } + + @Override + public void unsubscribe() throws CanalClientException { + this.rocketMQConsumer.unsubscribe(this.topic); + } + + @Override + public Message get(int batchSize) throws CanalClientException { + Message message = getWithoutAck(batchSize); + ack(message.getId()); + return message; + } + + @Override + public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { + Message message = getWithoutAck(batchSize, timeout, unit); + ack(message.getId()); + return message; + } + + private Message getMessage(ConsumerBatchMessage consumerBatchMessage) { + BlockingQueue messageList = consumerBatchMessage.getData(); + if (messageList != null & messageList.size() > 0) { + Message message = messageList.poll(); + messageCache.put(message.getId(), consumerBatchMessage); + return message; + } + return null; + } + + @Override + public Message getWithoutAck(int batchSize) throws CanalClientException { + ConsumerBatchMessage batchMessage = messageBlockingQueue.poll(); + if (batchMessage != null){ + return getMessage(batchMessage); + } + return null; + } + + @Override + public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { + try { + ConsumerBatchMessage batchMessage = messageBlockingQueue.poll(timeout, unit); + return getMessage(batchMessage); + } catch (InterruptedException ex) { + logger.warn("Get message timeout", ex); + throw new CanalClientException("failed to fetch the data after " + timeout); + } + } + + @Override + public void ack(long batchId) throws CanalClientException { + ConsumerBatchMessage batchMessage = messageCache.get(batchId); + batchMessage.ack(); + } + + @Override + public void rollback(long batchId) throws CanalClientException { + + } + + @Override + public void rollback() throws CanalClientException { + + } + + @Override + public void stopRunning() throws CanalClientException { + this.rocketMQConsumer.shutdown(); + connected = false; + } + +} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java new file mode 100644 index 00000000..3b876f49 --- /dev/null +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java @@ -0,0 +1,20 @@ +package com.alibaba.otter.canal.client.rocketmq; + +/** + * RocketMQ connector provider. + */ +public class RocketMQCanalConnectorProvider { + /** + * Create RocketMQ connector + * + * @param nameServers name servers for RocketMQ + * @param topic + * @param groupId + * @return {@link RocketMQCanalConnector} + */ + public static RocketMQCanalConnector newRocketMQConnector(String nameServers, String topic, + String groupId) { + return new RocketMQCanalConnector(nameServers, topic, groupId); + } + +} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java new file mode 100644 index 00000000..c30cd61f --- /dev/null +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java @@ -0,0 +1,11 @@ +package com.alibaba.otter.canal.client.rocketmq; + +import java.util.List; +import org.apache.rocketmq.common.message.MessageExt; + +/** + * RocketMQ message listener + */ +public interface RocketMQCanalListener { + boolean onReceive(List messageExts); +} diff --git a/deployer/pom.xml b/deployer/pom.xml index 3580869b..690b8c91 100644 --- a/deployer/pom.xml +++ b/deployer/pom.xml @@ -40,7 +40,7 @@ **/canal.properties **/spring/** **/example/** - **/kafka.yml + **/mq.yml diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 2524186a..4388de47 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -1,24 +1,26 @@ package com.alibaba.otter.canal.deployer; +import com.alibaba.otter.canal.kafka.CanalKafkaProducer; +import com.alibaba.otter.canal.rocketmq.CanalRocketMQProducer; +import com.alibaba.otter.canal.server.CanalMQStarter; +import com.alibaba.otter.canal.spi.CanalMQProducer; import java.io.FileInputStream; import java.util.Properties; -import com.alibaba.otter.canal.kafka.CanalKafkaStarter; -import com.alibaba.otter.canal.server.CanalServerStarter; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * canal独立版本启动的入口类 - * + * * @author jianghang 2012-11-6 下午05:20:49 * @version 1.0.0 */ public class CanalLauncher { private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(CanalLauncher.class); + private static final Logger logger = LoggerFactory.getLogger(CanalLauncher.class); public static void main(String[] args) throws Throwable { try { @@ -54,15 +56,18 @@ public class CanalLauncher { }); - CanalServerStarter canalServerStarter = null; + CanalMQProducer canalMQProducer = null; String serverMode = properties.getProperty(CanalConstants.CANAL_SERVER_MODE, "tcp"); if (serverMode.equalsIgnoreCase("kafka")) { - canalServerStarter = new CanalKafkaStarter(); - } else if (serverMode.equalsIgnoreCase("rocketMQ")) { - // 预留rocketMQ启动 + canalMQProducer = new CanalKafkaProducer(); + } else if (serverMode.equalsIgnoreCase("rocketmq")) { + canalMQProducer = new CanalRocketMQProducer(); } - if (canalServerStarter != null) { - canalServerStarter.init(); + if (canalMQProducer != null) { + CanalMQStarter canalServerStarter = new CanalMQStarter(canalMQProducer); + if (canalServerStarter != null) { + canalServerStarter.init(); + } } } catch (Throwable e) { logger.error("## Something goes wrong when starting up the canal Server:", e); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 3d580bae..e2ad657b 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -9,8 +9,8 @@ canal.zkServers= # flush data to zk canal.zookeeper.flush.period = 1000 canal.withoutNetty = false -# tcp, kafka, rocketMQ -canal.serverMode = tcp +# tcp, kafka, RocketMQ +canal.serverMode = rocketmq # flush meta cursor/parse position to file canal.file.data.dir = ${canal.conf.dir} canal.file.flush.period = 1000 diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index f72b65fe..3cdbac4a 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -6,7 +6,7 @@ canal.instance.gtidon=false # position info -canal.instance.master.address=127.0.0.1:3306 +canal.instance.master.address=120.78.225.173:3307 canal.instance.master.journal.name= canal.instance.master.position= canal.instance.master.timestamp= @@ -18,8 +18,9 @@ canal.instance.rds.secretkey= canal.instance.rds.instanceId= # table meta tsdb info -canal.instance.tsdb.enable=true -#canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb +#canal.instance.tsdb.enable=true +#canal.instance.tsdb.url=jdbc:mysql://120.78.225.173:3306/test +#canal.instance.mysql.slaveId = 1234 #canal.instance.tsdb.dbUsername=canal #canal.instance.tsdb.dbPassword=canal @@ -31,7 +32,7 @@ canal.instance.tsdb.enable=true # username/password canal.instance.dbUsername=canal -canal.instance.dbPassword=canal +canal.instance.dbPassword=P@ssw0rd canal.instance.connectionCharset=UTF-8 # table regex diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/mq.yml similarity index 85% rename from deployer/src/main/resources/kafka.yml rename to deployer/src/main/resources/mq.yml index 93d4d557..d8b18f4a 100644 --- a/deployer/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/mq.yml @@ -1,4 +1,4 @@ -servers: slave1:6667,slave2:6667,slave3:6667 +servers: localhost:9876 #for rocketmq: means the nameserver retries: 0 batchSize: 16384 lingerMs: 1 diff --git a/server/pom.xml b/server/pom.xml index a2eb437d..20108523 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -42,6 +42,11 @@ + + org.apache.rocketmq + rocketmq-client + 4.3.0 + org.jboss.netty diff --git a/server/src/main/java/com/alibaba/otter/canal/common/CanalMessageSerializer.java b/server/src/main/java/com/alibaba/otter/canal/common/CanalMessageSerializer.java new file mode 100644 index 00000000..55d350c6 --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/common/CanalMessageSerializer.java @@ -0,0 +1,70 @@ +package com.alibaba.otter.canal.common; + +import com.alibaba.otter.canal.protocol.CanalEntry; +import com.alibaba.otter.canal.protocol.CanalPacket; +import com.alibaba.otter.canal.protocol.Message; +import com.google.protobuf.ByteString; +import com.google.protobuf.CodedOutputStream; +import com.google.protobuf.WireFormat; +import java.util.List; +import org.apache.kafka.common.errors.SerializationException; +import org.springframework.util.CollectionUtils; + +public class CanalMessageSerializer { + public static byte[] serializer(Message data){ + try { + if (data != null) { + if (data.getId() != -1) { + if (data.isRaw() && !CollectionUtils.isEmpty(data.getRawEntries())) { + // for performance + List rowEntries = data.getRawEntries(); + // message size + int messageSize = 0; + messageSize += CodedOutputStream.computeInt64Size(1, data.getId()); + + int dataSize = 0; + for (int i = 0; i < rowEntries.size(); i++) { + dataSize += CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); + } + messageSize += dataSize; + messageSize += 1 * rowEntries.size(); + // packet size + int size = 0; + size += CodedOutputStream.computeEnumSize(3, + CanalPacket.PacketType.MESSAGES.getNumber()); + size += CodedOutputStream.computeTagSize(5) + + CodedOutputStream.computeRawVarint32Size(messageSize) + + messageSize; + // build data + byte[] body = new byte[size]; + CodedOutputStream output = CodedOutputStream.newInstance(body); + output.writeEnum(3, CanalPacket.PacketType.MESSAGES.getNumber()); + + output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); + output.writeRawVarint32(messageSize); + // message + output.writeInt64(1, data.getId()); + for (int i = 0; i < rowEntries.size(); i++) { + output.writeBytes(2, rowEntries.get(i)); + } + output.checkNoSpaceLeft(); + return body; + } else if (!CollectionUtils.isEmpty(data.getEntries())) { + CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); + for (CanalEntry.Entry entry : data.getEntries()) { + messageBuilder.addMessages(entry.toByteString()); + } + + CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); + packetBuilder.setType(CanalPacket.PacketType.MESSAGES); + packetBuilder.setBody(messageBuilder.build().toByteString()); + return packetBuilder.build().toByteArray(); + } + } + } + } catch (Exception e) { + throw new SerializationException("Error when serializing message to byte[] "); + } + return null; + } +} diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java similarity index 92% rename from server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java rename to server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index 140ce965..823a506a 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -1,4 +1,4 @@ -package com.alibaba.otter.canal.kafka; +package com.alibaba.otter.canal.common; import java.util.ArrayList; import java.util.HashSet; @@ -11,7 +11,7 @@ import java.util.Set; * @author machengyuan 2018-6-11 下午05:30:49 * @version 1.0.0 */ -public class KafkaProperties { +public class MQProperties { private String servers = "localhost:6667"; private int retries = 0; @@ -20,6 +20,7 @@ public class KafkaProperties { private long bufferMemory = 33554432L; private boolean filterTransactionEntry = true; private int canalBatchSize = 5; + private String producerGroup = "Canal-Producer"; private List canalDestinations = new ArrayList(); @@ -167,4 +168,11 @@ public class KafkaProperties { this.filterTransactionEntry = filterTransactionEntry; } + public String getProducerGroup() { + return producerGroup; + } + + public void setProducerGroup(String producerGroup) { + this.producerGroup = producerGroup; + } } diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index b6dba051..91531f7d 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.kafka; +import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.spi.CanalMQProducer; import java.io.IOException; import java.util.Properties; @@ -18,13 +20,14 @@ import com.alibaba.otter.canal.protocol.Message; * @author machengyuan 2018-6-11 下午05:30:49 * @version 1.0.0 */ -public class CanalKafkaProducer { +public class CanalKafkaProducer implements CanalMQProducer { - private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); + private static final Logger logger = LoggerFactory.getLogger(CanalKafkaProducer.class); private Producer producer; - public void init(KafkaProperties kafkaProperties) { + @Override + public void init(MQProperties kafkaProperties) { Properties properties = new Properties(); properties.put("bootstrap.servers", kafkaProperties.getServers()); properties.put("acks", "all"); @@ -37,6 +40,7 @@ public class CanalKafkaProducer { producer = new KafkaProducer(properties); } + @Override public void stop() { try { logger.info("## stop the kafka producer"); @@ -48,33 +52,8 @@ public class CanalKafkaProducer { } } - public void send(KafkaProperties.Topic topic, Message message) throws IOException { - // set canal.instance.filter.transaction.entry = true - - // boolean valid = false; - // if (message != null) { - // if (message.isRaw() && !message.getRawEntries().isEmpty()) { - // for (ByteString byteString : message.getRawEntries()) { - // CanalEntry.Entry entry = CanalEntry.Entry.parseFrom(byteString); - // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - // valid = true; - // break; - // } - // } - // } else if (!message.getEntries().isEmpty()){ - // for (CanalEntry.Entry entry : message.getEntries()) { - // if (entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONBEGIN - // && entry.getEntryType() != CanalEntry.EntryType.TRANSACTIONEND) { - // valid = true; - // break; - // } - // } - // } - // } - // if (!valid) { - // return; - // } + @Override + public void send(MQProperties.Topic topic, Message message) throws IOException { ProducerRecord record; if (topic.getPartition() != null) { record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java index 49db5aa1..67dc04bc 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/MessageSerializer.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.kafka; +import com.alibaba.otter.canal.common.CanalMessageSerializer; import java.util.List; import java.util.Map; @@ -29,60 +30,7 @@ public class MessageSerializer implements Serializer { @Override public byte[] serialize(String topic, Message data) { - try { - if (data != null) { - if (data.getId() != -1) { - if (data.isRaw() && !CollectionUtils.isEmpty(data.getRawEntries())) { - // for performance - List rowEntries = data.getRawEntries(); - // message size - int messageSize = 0; - messageSize += CodedOutputStream.computeInt64Size(1, data.getId()); - - int dataSize = 0; - for (int i = 0; i < rowEntries.size(); i++) { - dataSize += CodedOutputStream.computeBytesSizeNoTag(rowEntries.get(i)); - } - messageSize += dataSize; - messageSize += 1 * rowEntries.size(); - // packet size - int size = 0; - size += CodedOutputStream.computeEnumSize(3, - PacketType.MESSAGES.getNumber()); - size += CodedOutputStream.computeTagSize(5) - + CodedOutputStream.computeRawVarint32Size(messageSize) - + messageSize; - // build data - byte[] body = new byte[size]; - CodedOutputStream output = CodedOutputStream.newInstance(body); - output.writeEnum(3, PacketType.MESSAGES.getNumber()); - - output.writeTag(5, WireFormat.WIRETYPE_LENGTH_DELIMITED); - output.writeRawVarint32(messageSize); - // message - output.writeInt64(1, data.getId()); - for (int i = 0; i < rowEntries.size(); i++) { - output.writeBytes(2, rowEntries.get(i)); - } - output.checkNoSpaceLeft(); - return body; - } else if (!CollectionUtils.isEmpty(data.getEntries())) { - CanalPacket.Messages.Builder messageBuilder = CanalPacket.Messages.newBuilder(); - for (CanalEntry.Entry entry : data.getEntries()) { - messageBuilder.addMessages(entry.toByteString()); - } - - CanalPacket.Packet.Builder packetBuilder = CanalPacket.Packet.newBuilder(); - packetBuilder.setType(PacketType.MESSAGES); - packetBuilder.setBody(messageBuilder.build().toByteString()); - return packetBuilder.build().toByteArray(); - } - } - } - } catch (Exception e) { - throw new SerializationException("Error when serializing message to byte[] "); - } - return null; + return CanalMessageSerializer.serializer(data); } @Override diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java new file mode 100644 index 00000000..5a2900de --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -0,0 +1,58 @@ +package com.alibaba.otter.canal.rocketmq; + +import com.alibaba.otter.canal.common.CanalMessageSerializer; +import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.server.exception.CanalServerException; +import com.alibaba.otter.canal.spi.CanalMQProducer; +import java.util.List; +import org.apache.rocketmq.client.exception.MQBrokerException; +import org.apache.rocketmq.client.exception.MQClientException; +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.MessageQueueSelector; +import org.apache.rocketmq.common.message.Message; +import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.remoting.exception.RemotingException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CanalRocketMQProducer implements CanalMQProducer { + private static final Logger logger = LoggerFactory.getLogger(CanalRocketMQProducer.class); + + private DefaultMQProducer defaultMQProducer; + + @Override + public void init(MQProperties rocketMQProperties) { + defaultMQProducer = new DefaultMQProducer(); + defaultMQProducer.setNamesrvAddr(rocketMQProperties.getServers()); + defaultMQProducer.setProducerGroup(rocketMQProperties.getProducerGroup()); + defaultMQProducer.setRetryTimesWhenSendFailed(rocketMQProperties.getRetries()); + logger.info("##Start RocketMQ producer##"); + try { + defaultMQProducer.start(); + } catch (MQClientException ex) { + throw new CanalServerException("Start RocketMQ producer error", ex); + } + } + + @Override + public void send(MQProperties.Topic topic, com.alibaba.otter.canal.protocol.Message data) { + try { + Message message = new Message(topic.getTopic(), CanalMessageSerializer.serializer(data)); + this.defaultMQProducer.send(message, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + // int index = (arg.hashCode() % mqs.size()); + return mqs.get(1); + } + }, null); + } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { + logger.error("Send message error!", e); + } + } + + @Override + public void stop() { + logger.info("## Stop RocketMQ producer##"); + this.defaultMQProducer.shutdown(); + } +} diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java similarity index 66% rename from server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java rename to server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index d244ce31..bd2fdac2 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -1,69 +1,63 @@ -package com.alibaba.otter.canal.kafka; +package com.alibaba.otter.canal.server; +import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.protocol.ClientIdentity; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; +import com.alibaba.otter.canal.spi.CanalMQProducer; import java.io.FileInputStream; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; - import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; -import com.alibaba.otter.canal.kafka.KafkaProperties.CanalDestination; -import com.alibaba.otter.canal.kafka.KafkaProperties.Topic; -import com.alibaba.otter.canal.protocol.ClientIdentity; -import com.alibaba.otter.canal.protocol.Message; -import com.alibaba.otter.canal.server.CanalServerStarter; -import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; - -/** - * kafka 启动类 - * - * @author machengyuan 2018-6-11 下午05:30:49 - * @version 1.0.0 - */ -public class CanalKafkaStarter implements CanalServerStarter { - - private static final Logger logger = LoggerFactory.getLogger(CanalKafkaStarter.class); +public class CanalMQStarter { + private static final Logger logger = LoggerFactory.getLogger(CanalMQStarter.class); private static final String CLASSPATH_URL_PREFIX = "classpath:"; private volatile boolean running = false; - private ExecutorService executorService; + private ExecutorService executorService; - private CanalKafkaProducer canalKafkaProducer; + private CanalMQProducer canalMQProducer; - private KafkaProperties kafkaProperties; + private MQProperties properties; + + public CanalMQStarter(CanalMQProducer canalMQProducer){ + this.canalMQProducer = canalMQProducer; + } public void init() { try { - logger.info("## load kafka configurations"); - String conf = System.getProperty("kafka.conf", "classpath:kafka.yml"); + logger.info("## load MQ configurations"); + String conf = System.getProperty("mq.conf", "classpath:mq.yml"); if (conf.startsWith(CLASSPATH_URL_PREFIX)) { conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX); - kafkaProperties = new Yaml().loadAs(CanalKafkaStarter.class.getClassLoader().getResourceAsStream(conf), - KafkaProperties.class); + properties = new Yaml().loadAs(CanalMQStarter.class.getClassLoader().getResourceAsStream(conf), + MQProperties.class); } else { - kafkaProperties = new Yaml().loadAs(new FileInputStream(conf), KafkaProperties.class); + properties = new Yaml().loadAs(new FileInputStream(conf), MQProperties.class); } // 初始化 kafka producer - canalKafkaProducer = new CanalKafkaProducer(); - canalKafkaProducer.init(kafkaProperties); +// canalMQProducer = new CanalKafkaProducer(); + canalMQProducer.init(properties); // set filterTransactionEntry - if (kafkaProperties.isFilterTransactionEntry()) { + if (properties.isFilterTransactionEntry()) { System.setProperty("canal.instance.filter.transaction.entry", "true"); } // 对应每个instance启动一个worker线程 - List destinations = kafkaProperties.getCanalDestinations(); + List destinations = properties.getCanalDestinations(); executorService = Executors.newFixedThreadPool(destinations.size()); - logger.info("## start the kafka workers."); - for (final CanalDestination destination : destinations) { + logger.info("## start the MQ workers."); + for (final MQProperties.CanalDestination destination : destinations) { executorService.execute(new Runnable() { @Override @@ -73,31 +67,31 @@ public class CanalKafkaStarter implements CanalServerStarter { }); } running = true; - logger.info("## the kafka workers is running now ......"); + logger.info("## the MQ workers is running now ......"); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { try { - logger.info("## stop the kafka workers"); + logger.info("## stop the MQ workers"); running = false; executorService.shutdown(); - canalKafkaProducer.stop(); + canalMQProducer.stop(); } catch (Throwable e) { - logger.warn("##something goes wrong when stopping kafka workers:", e); + logger.warn("##something goes wrong when stopping MQ workers:", e); } finally { - logger.info("## canal kafka is down."); + logger.info("## canal MQ is down."); } } }); } catch (Throwable e) { - logger.error("## Something goes wrong when starting up the canal kafka workers:", e); + logger.error("## Something goes wrong when starting up the canal MQ workers:", e); System.exit(0); } } - private void worker(CanalDestination destination) { + private void worker(MQProperties.CanalDestination destination) { while (!running) ; logger.info("## start the canal consumer: {}.", destination.getCanalDestination()); @@ -117,19 +111,19 @@ public class CanalKafkaStarter implements CanalServerStarter { logger.info("## the canal consumer {} is running now ......", destination.getCanalDestination()); while (running) { - Message message = server.getWithoutAck(clientIdentity, kafkaProperties.getCanalBatchSize()); // 获取指定数量的数据 + Message message = server.getWithoutAck(clientIdentity, properties.getCanalBatchSize()); // 获取指定数量的数据 long batchId = message.getId(); try { int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); if (batchId != -1 && size != 0) { if (!StringUtils.isEmpty(destination.getTopic())) { - Topic topic = new Topic(); + MQProperties.Topic topic = new MQProperties.Topic(); topic.setTopic(destination.getTopic()); topic.setPartition(destination.getPartition()); destination.getTopics().add(topic); } - for (Topic topic : destination.getTopics()) { - canalKafkaProducer.send(topic, message); // 发送message到所有topic + for (MQProperties.Topic topic : destination.getTopics()) { + canalMQProducer.send(topic, message); // 发送message到所有topic } } diff --git a/server/src/main/java/com/alibaba/otter/canal/spi/CanalMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMQProducer.java new file mode 100644 index 00000000..b0edaf5b --- /dev/null +++ b/server/src/main/java/com/alibaba/otter/canal/spi/CanalMQProducer.java @@ -0,0 +1,28 @@ +package com.alibaba.otter.canal.spi; + +import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.protocol.Message; +import java.io.IOException; + +public interface CanalMQProducer { + /** + * Init producer. + * + * @param mqProperties MQ config + */ + void init(MQProperties mqProperties); + + /** + * Send canal message to related topic + * + * @param topic MQ topic + * @param message canal message + * @throws IOException + */ + void send(MQProperties.Topic topic, Message message) throws IOException; + + /** + * Stop MQ producer service + */ + void stop(); +} \ No newline at end of file From 0cda90cdeae57c4d30468ffa581e105c784695da Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 20 Sep 2018 16:17:38 +0800 Subject: [PATCH 146/360] =?UTF-8?q?=E9=BB=98=E8=AE=A4=E5=B0=86=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=8F=91=E7=8E=B0kafka=E7=9A=84partition=200=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E5=A4=9A=E5=88=86=E5=8C=BA=E7=9A=84=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=B8=80=E8=87=B4=E6=80=A7=E9=97=AE=E9=A2=98=20?= =?UTF-8?q?=E5=90=8E=E7=BB=AD=E5=B0=86=E6=94=AF=E6=8C=81=E9=80=9A=E8=BF=87?= =?UTF-8?q?id=20hash=E5=88=86=E5=8F=91=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/kafka/CanalKafkaProducer.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index 4942a37b..e1ea5e47 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -75,7 +75,7 @@ public class CanalKafkaProducer { if (topic.getPartition() != null) { record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); } else { - record = new ProducerRecord(topic.getTopic(), message); + record = new ProducerRecord(topic.getTopic(), 0, null, message); } producer.send(record); @@ -84,8 +84,18 @@ public class CanalKafkaProducer { List flatMessages = FlatMessage.messageConverter(message); if (flatMessages != null) { for (FlatMessage flatMessage : flatMessages) { - ProducerRecord record = new ProducerRecord(topic.getTopic(), - JSON.toJSONString(flatMessage)); + ProducerRecord record; + if (topic.getPartition() != null) { + record = new ProducerRecord(topic.getTopic(), + topic.getPartition(), + null, + JSON.toJSONString(flatMessage)); + } else { + record = new ProducerRecord(topic.getTopic(), + 0, + null, + JSON.toJSONString(flatMessage)); + } producer2.send(record); } } From 74890050d0c323877eb9c1e3dec128e989eb6ee8 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 20 Sep 2018 16:48:14 +0800 Subject: [PATCH 147/360] =?UTF-8?q?kafka=E7=94=9F=E4=BA=A7=E7=AB=AF?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8C=89pk=20hash=E5=88=B0=E5=AF=B9=E5=BA=94?= =?UTF-8?q?partition=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/kafka.yml | 6 +- .../otter/canal/protocol/FlatMessage.java | 31 ++++++---- .../otter/canal/kafka/CanalKafkaProducer.java | 55 ++++++++++++----- .../otter/canal/kafka/CanalKafkaStarter.java | 5 +- .../otter/canal/kafka/KafkaProperties.java | 60 +++++-------------- 5 files changed, 79 insertions(+), 78 deletions(-) diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml index 735f723d..b86b3034 100644 --- a/deployer/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/kafka.yml @@ -12,7 +12,9 @@ flatMessage: true canalDestinations: - canalDestination: example - topic: example - partition: + topic: expample + #partitionsNum: 3 + #partitionHash: + # mytest.person: id diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index d7150b79..51bd320a 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -1,14 +1,8 @@ package com.alibaba.otter.canal.protocol; import java.io.Serializable; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; -import com.google.common.collect.Table; import com.google.protobuf.ByteString; /** @@ -31,7 +25,7 @@ public class FlatMessage implements Serializable { private List> data; private List> old; - public FlatMessage() { + public FlatMessage(){ } public FlatMessage(long id){ @@ -126,6 +120,12 @@ public class FlatMessage implements Serializable { this.old = old; } + /** + * 将Message转换为FlatMessage + * + * @param message 原生message + * @return FlatMessage列表 + */ public static List messageConverter(Message message) { try { if (message == null) { @@ -231,11 +231,22 @@ public class FlatMessage implements Serializable { } } + /** + * 将FlatMessage按指定的字段值hash拆分 + * + * @param flatMessage flatMessage + * @param partitionsNum 分区数量 + * @param pkHashConfig hash映射 + * @return 拆分后的flatMessage数组 + */ public static FlatMessage[] messagePartition(FlatMessage flatMessage, Integer partitionsNum, - Table pkHashConfig) { + Map pkHashConfig) { + if (partitionsNum == null) { + partitionsNum = 1; + } FlatMessage[] partitionMessages = new FlatMessage[partitionsNum]; - String pk = pkHashConfig.get(flatMessage.getDatabase(), flatMessage.getTable()); + String pk = pkHashConfig.get(flatMessage.getDatabase() + "." + flatMessage.getTable()); if (pk == null || flatMessage.getIsDdl()) { partitionMessages[0] = flatMessage; } else { diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index e1ea5e47..fbeeeb87 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -67,15 +67,18 @@ public class CanalKafkaProducer { } } - public void send(KafkaProperties.Topic topic, Message message, Callback callback) { + public void send(KafkaProperties.CanalDestination canalDestination, Message message, Callback callback) { try { // producer.beginTransaction(); if (!kafkaProperties.getFlatMessage()) { ProducerRecord record; - if (topic.getPartition() != null) { - record = new ProducerRecord(topic.getTopic(), topic.getPartition(), null, message); + if (canalDestination.getPartition() != null) { + record = new ProducerRecord(canalDestination.getTopic(), + canalDestination.getPartition(), + null, + message); } else { - record = new ProducerRecord(topic.getTopic(), 0, null, message); + record = new ProducerRecord(canalDestination.getTopic(), 0, null, message); } producer.send(record); @@ -84,19 +87,39 @@ public class CanalKafkaProducer { List flatMessages = FlatMessage.messageConverter(message); if (flatMessages != null) { for (FlatMessage flatMessage : flatMessages) { - ProducerRecord record; - if (topic.getPartition() != null) { - record = new ProducerRecord(topic.getTopic(), - topic.getPartition(), - null, - JSON.toJSONString(flatMessage)); + if (canalDestination.getPartition() != null) { + ProducerRecord record = new ProducerRecord(canalDestination + .getTopic(), canalDestination.getPartition(), null, JSON.toJSONString(flatMessage)); + producer2.send(record); } else { - record = new ProducerRecord(topic.getTopic(), - 0, - null, - JSON.toJSONString(flatMessage)); + if (canalDestination.getPartitionHash() != null + && !canalDestination.getPartitionHash().isEmpty()) { + FlatMessage[] partitionFlatMessage = FlatMessage.messagePartition(flatMessage, + canalDestination.getPartitionsNum(), + canalDestination.getPartitionHash()); + int length = partitionFlatMessage.length; + for (int i = 0; i < length; i++) { + FlatMessage flatMessage1 = partitionFlatMessage[i]; + if (flatMessage1 == null) { + continue; + } + ProducerRecord record = new ProducerRecord( + canalDestination.getTopic(), + i, + null, + JSON.toJSONString(partitionFlatMessage[i])); + producer2.send(record); + } + } else { + ProducerRecord record = new ProducerRecord( + canalDestination.getTopic(), + 0, + null, + JSON.toJSONString(flatMessage)); + producer2.send(record); + } } - producer2.send(record); + } } } @@ -104,7 +127,7 @@ public class CanalKafkaProducer { // producer.commitTransaction(); callback.commit(); if (logger.isDebugEnabled()) { - logger.debug("send message to kafka topic: {}", topic.getTopic()); + logger.debug("send message to kafka topic: {}", canalDestination.getTopic()); } } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index 5b73bf3b..abe7bf09 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -132,10 +132,7 @@ public class CanalKafkaStarter implements CanalServerStarter { try { int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); if (batchId != -1 && size != 0) { - Topic topic = new Topic(); - topic.setTopic(destination.getTopic()); - topic.setPartition(destination.getPartition()); - canalKafkaProducer.send(topic, message, new CanalKafkaProducer.Callback() { + canalKafkaProducer.send(destination, message, new CanalKafkaProducer.Callback() { @Override public void commit() { diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java index 82707ba4..5e4145fe 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/KafkaProperties.java @@ -1,9 +1,8 @@ package com.alibaba.otter.canal.kafka; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import java.util.Map; /** * kafka 配置项 @@ -27,10 +26,11 @@ public class KafkaProperties { public static class CanalDestination { - private String canalDestination; - private String topic; - private Integer partition; - private Set topics = new HashSet(); + private String canalDestination; + private String topic; + private Integer partition; + private Integer partitionsNum; + private Map partitionHash; public String getCanalDestination() { return canalDestination; @@ -56,52 +56,20 @@ public class KafkaProperties { this.partition = partition; } - public Set getTopics() { - return topics; + public Integer getPartitionsNum() { + return partitionsNum; } - public void setTopics(Set topics) { - this.topics = topics; - } - } - - public static class Topic { - - private String topic; - private Integer partition; - - public String getTopic() { - return topic; + public void setPartitionsNum(Integer partitionsNum) { + this.partitionsNum = partitionsNum; } - public void setTopic(String topic) { - this.topic = topic; + public Map getPartitionHash() { + return partitionHash; } - public Integer getPartition() { - return partition; - } - - public void setPartition(Integer partition) { - this.partition = partition; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Topic topic1 = (Topic) o; - - if (topic != null ? !topic.equals(topic1.topic) : topic1.topic != null) return false; - return partition != null ? partition.equals(topic1.partition) : topic1.partition == null; - } - - @Override - public int hashCode() { - int result = topic != null ? topic.hashCode() : 0; - result = 31 * result + (partition != null ? partition.hashCode() : 0); - return result; + public void setPartitionHash(Map partitionHash) { + this.partitionHash = partitionHash; } } From e566160f1ea24c3db15b45f51b1b6ad2ce226b42 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 20 Sep 2018 17:24:32 +0800 Subject: [PATCH 148/360] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/kafka.yml | 10 ++++++---- .../otter/canal/kafka/CanalKafkaProducer.java | 17 ++++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml index b86b3034..76e2bade 100644 --- a/deployer/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/kafka.yml @@ -12,9 +12,11 @@ flatMessage: true canalDestinations: - canalDestination: example - topic: expample - #partitionsNum: 3 - #partitionHash: - # mytest.person: id + topic: example +# #对应topic分区数量 +# partitionsNum: 3 +# partitionHash: +# #库名.表名: 唯一主键 +# mytest.person: id diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index fbeeeb87..5708105c 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -99,16 +99,15 @@ public class CanalKafkaProducer { canalDestination.getPartitionHash()); int length = partitionFlatMessage.length; for (int i = 0; i < length; i++) { - FlatMessage flatMessage1 = partitionFlatMessage[i]; - if (flatMessage1 == null) { - continue; + FlatMessage flatMessagePart = partitionFlatMessage[i]; + if (flatMessagePart != null) { + ProducerRecord record = new ProducerRecord( + canalDestination.getTopic(), + i, + null, + JSON.toJSONString(flatMessagePart)); + producer2.send(record); } - ProducerRecord record = new ProducerRecord( - canalDestination.getTopic(), - i, - null, - JSON.toJSONString(partitionFlatMessage[i])); - producer2.send(record); } } else { ProducerRecord record = new ProducerRecord( From 4f2b4cea53e9dfc526f6600fbf1c0a3bf955dabd Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 20 Sep 2018 17:25:32 +0800 Subject: [PATCH 149/360] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/kafka.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/deployer/src/main/resources/kafka.yml b/deployer/src/main/resources/kafka.yml index 76e2bade..aa23c1ee 100644 --- a/deployer/src/main/resources/kafka.yml +++ b/deployer/src/main/resources/kafka.yml @@ -11,12 +11,12 @@ canalGetTimeout: 100 flatMessage: true canalDestinations: - - canalDestination: example - topic: example -# #对应topic分区数量 -# partitionsNum: 3 -# partitionHash: -# #库名.表名: 唯一主键 -# mytest.person: id +- canalDestination: example + topic: exp3 +# #对应topic分区数量 +# partitionsNum: 3 +# partitionHash: +# #库名.表名: 唯一主键 +# mytest.person: id From 19b7f730b640471150796f487f002378dca0cc0b Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 20 Sep 2018 18:06:47 +0800 Subject: [PATCH 150/360] =?UTF-8?q?=E6=95=B4=E7=90=86=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java index abe7bf09..f2df3547 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaStarter.java @@ -12,7 +12,6 @@ import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; import com.alibaba.otter.canal.kafka.KafkaProperties.CanalDestination; -import com.alibaba.otter.canal.kafka.KafkaProperties.Topic; import com.alibaba.otter.canal.protocol.ClientIdentity; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.server.CanalServerStarter; From fd80f953e97eaf741ace487f950c8d2492ea60a8 Mon Sep 17 00:00:00 2001 From: agapple Date: Fri, 21 Sep 2018 15:30:35 +0800 Subject: [PATCH 151/360] Update README.md --- README.md | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 86ef8755..2d7bc589 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@
      1. canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
      2. canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
      3. +
      4. canal c#客户端开源项目地址: https://github.com/CanalSharp/CanalSharp ,推荐!
      5. Canal已在阿里云推出商业化版本 数据传输服务DTS, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。DTS产品使用文档
      6. DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,立即体验>>>
      @@ -52,18 +53,38 @@ See the wiki page for : wiki文

      wiki文档列表

      +

      多语言业务使用

      + +1. canal整体交互协议设计上使用了protobuf3.0,理论上可以支持绝大部分的多语言场景,欢迎大家提交多客户端的PR + * canal java客户端: https://github.com/alibaba/canal/wiki/ClientExample + * canal c#客户端开源项目地址: https://github.com/CanalSharp/CanalSharp + * canal go客户端,开发进行中 +2. canal作为MySQL binlog的增量获取工具,可以将数据投递到MQ系统中,比如Kafka/RocketMQ,可以借助于MQ的多语言能力 +

      相关资料

      * ADC阿里技术嘉年华分享ppt (放在google docs上,可能需要翻墙): ppt下载 @@ -90,10 +111,3 @@ See the wiki page for : wiki文
    5. 新浪微博: agapple0002
    6. 报告issue:issues
    7. - -
      -【招聘】阿里巴巴中间件团队招聘JAVA高级工程师
      -岗位主要为技术型内容(非业务部门),阿里中间件整个体系对于未来想在技术上有所沉淀的同学还是非常有帮助的
      -工作地点:杭州、北京均可. ps. 阿里待遇向来都是不错的,有意者可以QQ、微博私聊. 
      -具体招聘内容:https://job.alibaba.com/zhaopin/position_detail.htm?positionId=32666
      -
      From 9535e82da6a3ed760919747122f25b8ee1ae334e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 21 Sep 2018 15:40:18 +0800 Subject: [PATCH 152/360] fixed readme --- README.md | 17 +++++++++++++++-- .../resources/spring/tsdb/sql/create_table.sql | 8 ++++---- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2d7bc589..95331c55 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,11 @@

      最新更新

        +
      1. canal发布重大版本更新1.1.0,具体releaseNode参考:https://github.com/alibaba/canal/releases/tag/canal-1.1.0
      2. +
      3. canal c#客户端开源项目地址: https://github.com/CanalSharp/CanalSharp ,推荐!
      4. canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
      5. canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
      6. -
      7. canal c#客户端开源项目地址: https://github.com/CanalSharp/CanalSharp ,推荐!
      8. +
      9. Canal已在阿里云推出商业化版本 数据传输服务DTS, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。DTS产品使用文档
      10. DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,立即体验>>>
      @@ -47,6 +49,16 @@ DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体
    8. canal解析binary log对象(原始为byte流)
    9. +

      重要版本更新说明

      + +canal 1.1.x系列,参考release文档:版本发布信息: + +1. 整体性能测试&优化,提升了150%. #726 参考: 【[Performance](https://github.com/alibaba/canal/wiki/Performance)】 +2. 原生支持prometheus监控 #765 【[Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)】 +3. 原生支持kafka消息投递 #695 【[Canal Kafka QuickStart](https://github.com/alibaba/canal/wiki/Canal-Kafka-QuickStart)】 +4. 原生支持aliyun rds的binlog订阅 (解决自动主备切换/oss binlog离线解析) 参考: 【[Aliyun RDS QuickStart](https://github.com/alibaba/canal/wiki/aliyun-RDS-QuickStart)】 +5. 原生支持docker镜像 #801 参考: 【[Docker QuickStart](https://github.com/alibaba/canal/wiki/Docker-QuickStart)】 +

      相关文档

      See the wiki page for : wiki文档 @@ -77,7 +89,7 @@ See the wiki page for : wiki文
    10. FAQ
    11. -

      多语言业务使用

      +

      多语言业务

      1. canal整体交互协议设计上使用了protobuf3.0,理论上可以支持绝大部分的多语言场景,欢迎大家提交多客户端的PR * canal java客户端: https://github.com/alibaba/canal/wiki/ClientExample @@ -111,3 +123,4 @@ See the wiki page for : wiki文
    12. 新浪微博: agapple0002
    13. 报告issue:issues
    14. + diff --git a/deployer/src/main/resources/spring/tsdb/sql/create_table.sql b/deployer/src/main/resources/spring/tsdb/sql/create_table.sql index e5d6dbf4..322bc2b6 100644 --- a/deployer/src/main/resources/spring/tsdb/sql/create_table.sql +++ b/deployer/src/main/resources/spring/tsdb/sql/create_table.sql @@ -26,10 +26,10 @@ CREATE TABLE IF NOT EXISTS `meta_history` ( `binlog_master_id` varchar(64) DEFAULT NULL COMMENT 'binlog节点id', `binlog_timestamp` bigint(20) DEFAULT NULL COMMENT 'binlog应用的时间戳', `use_schema` varchar(1024) DEFAULT NULL COMMENT '执行sql时对应的schema', - `schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema', - `table` varchar(1024) DEFAULT NULL COMMENT '对应的table', - `sql` longtext DEFAULT NULL COMMENT '执行的sql', - `type` varchar(256) DEFAULT NULL COMMENT 'sql类型', + `sql_schema` varchar(1024) DEFAULT NULL COMMENT '对应的schema', + `sql_table` varchar(1024) DEFAULT NULL COMMENT '对应的table', + `sql_text` longtext DEFAULT NULL COMMENT '执行的sql', + `sql_type` varchar(256) DEFAULT NULL COMMENT 'sql类型', `extra` text DEFAULT NULL COMMENT '额外的扩展信息', PRIMARY KEY (`id`), UNIQUE KEY binlog_file_offest(`destination`,`binlog_master_id`,`binlog_file`,`binlog_offest`), From 9d48d23787c8e24689c7a3c8b431c6109e0a5d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 21 Sep 2018 15:44:03 +0800 Subject: [PATCH 153/360] fixed readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 95331c55..9a87de8f 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体

      重要版本更新说明

      -canal 1.1.x系列,参考release文档:版本发布信息: +canal 1.1.x系列,参考release文档:版本发布信息 1. 整体性能测试&优化,提升了150%. #726 参考: 【[Performance](https://github.com/alibaba/canal/wiki/Performance)】 2. 原生支持prometheus监控 #765 【[Prometheus QuickStart](https://github.com/alibaba/canal/wiki/Prometheus-QuickStart)】 From 00ec70d425e5ebbd0dbcb7211c7354325af99be6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 21 Sep 2018 15:45:52 +0800 Subject: [PATCH 154/360] fixed readme --- README.md | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9a87de8f..c28fc0ea 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,6 @@
      - -

      最新更新

      -
        -
      1. canal发布重大版本更新1.1.0,具体releaseNode参考:https://github.com/alibaba/canal/releases/tag/canal-1.1.0
      2. -
      3. canal c#客户端开源项目地址: https://github.com/CanalSharp/CanalSharp ,推荐!
      4. -
      5. canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
      6. -
      7. canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
      8. - -
      9. Canal已在阿里云推出商业化版本 数据传输服务DTS, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。DTS产品使用文档
      10. -DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,立即体验>>> -
      - +

      背景

      早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&消费的业务,从此开启了一段新纪元。

      ps. 目前内部版本已经支持mysql和oracle部分版本的日志解析,当前的canal开源版本支持5.7及以下的版本(阿里内部mysql 5.7.13, 5.6.10, mysql 5.5.18和5.1.40/48)

      @@ -124,3 +113,13 @@ See the wiki page for : wiki文
    15. 报告issue:issues
    16. +

      最新更新

      +
        +
      1. canal发布重大版本更新1.1.0,具体releaseNode参考:https://github.com/alibaba/canal/releases/tag/canal-1.1.0
      2. +
      3. canal c#客户端开源项目地址: https://github.com/CanalSharp/CanalSharp ,推荐!
      4. +
      5. canal QQ讨论群已经建立,群号:161559791 ,欢迎加入进行技术讨论。
      6. +
      7. canal消费端项目开源: Otter(分布式数据库同步系统),地址:https://github.com/alibaba/otter
      8. + +
      9. Canal已在阿里云推出商业化版本 数据传输服务DTS, 开通即用,免去部署维护的昂贵使用成本。DTS针对阿里云RDS、DRDS等产品进行了适配,解决了Binlog日志回收,主备切换、VPC网络切换等场景下的订阅高可用问题。同时,针对RDS进行了针对性的性能优化。出于稳定性、性能及成本的考虑,强烈推荐阿里云用户使用DTS产品。DTS产品使用文档
      10. +DTS支持阿里云RDS&DRDS的Binlog日志实时订阅,现推出首月免费体验,限时限量,立即体验>>> +
      \ No newline at end of file From 821f3eced54b8a102fbc25f6ff92946f34fe426e Mon Sep 17 00:00:00 2001 From: duhengforever Date: Fri, 21 Sep 2018 18:41:37 +0800 Subject: [PATCH 155/360] Add mq adapter Signed-off-by: duhengforever --- .../adapter/support/CanalClientConfig.java | 9 ++++ .../adapter/loader/CanalAdapterLoader.java | 50 ++++++++++--------- .../src/main/resources/canal-client.yml | 2 + deployer/src/main/resources/mq.yml | 2 +- .../canal/rocketmq/CanalRocketMQProducer.java | 5 +- 5 files changed, 40 insertions(+), 28 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index e0658138..6619555c 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -111,11 +111,20 @@ public class CanalClientConfig { } public static class MQTopic { + private String mqMode; private String topic; private List groups = new ArrayList<>(); + public String getMqMode() { + return mqMode; + } + + public void setMqMode(String mqMode) { + this.mqMode = mqMode; + } + public String getTopic() { return topic; } diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java index a705fad7..84517525 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java @@ -4,6 +4,7 @@ import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -17,7 +18,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - * RocktMQ外部适配器的加载器 + * MQ外部适配器的加载器 * * @version 1.0.0 */ @@ -29,8 +30,7 @@ public class CanalAdapterLoader { private Map canalWorkers = new HashMap<>(); - private Map canalKafkaWorkers = new HashMap<>(); - private Map canalMQWorker = new HashMap<>(); + private Map canalMQWorker = new HashMap<>(); private ExtensionLoader loader; @@ -42,7 +42,7 @@ public class CanalAdapterLoader { * 初始化canal-client、 canal-client-rocketmq的适配器 */ public void init() { - // canal instances 和 rocketmq topics 配置不能同时为空 + // canal instances 和 mq topics 配置不能同时为空 if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getMqTopics() == null) { throw new RuntimeException("Blank config property: canalInstances or canalMQTopics"); } @@ -58,11 +58,6 @@ public class CanalAdapterLoader { } String zkHosts = this.canalClientConfig.getZookeeperHosts(); - // if (zkHosts == null && sa == null) { - // throw new RuntimeException("Blank config property: canalServerHost or - // zookeeperHosts"); - // } - // 初始化canal-client的适配器 if (canalClientConfig.getCanalInstances() != null) { for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { @@ -96,19 +91,27 @@ public class CanalAdapterLoader { List canalOuterAdapters = new ArrayList<>(); for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { - // for (CanalOuterAdapterConfiguration config : adaptor.getOutAdapters()) { loadConnector(config, canalOuterAdapters); - // } } canalOuterAdapterGroups.add(canalOuterAdapters); - CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker( - canalClientConfig.getBootstrapServers(), - topic.getTopic(), - group.getGroupId(), - canalOuterAdapterGroups); - canalMQWorker.put(topic.getTopic() + "-" + group.getGroupId(), rocketMQWorker); - rocketMQWorker.start(); + if (StringUtils.isBlank(topic.getMqMode()) || "rocketmq".equalsIgnoreCase(topic.getMqMode())) { + CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker( + canalClientConfig.getBootstrapServers(), + topic.getTopic(), + group.getGroupId(), + canalOuterAdapterGroups); + canalMQWorker.put(topic.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); + rocketMQWorker.start(); + } else if ("kafka".equalsIgnoreCase(topic.getMqMode())) { + CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(zkHosts, + canalClientConfig.getBootstrapServers(), + topic.getTopic(), + group.getGroupId(), + canalOuterAdapterGroups); + canalMQWorker.put(topic.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker); + canalKafkaWorker.start(); + } logger.info("Start adapter for canal-client rocketmq topic: {} succeed", topic.getTopic() + "-" + group.getGroupId()); } @@ -149,15 +152,14 @@ public class CanalAdapterLoader { } stopExecutorService.shutdown(); } - if (canalKafkaWorkers.size() > 0) { - ExecutorService stopKafkaExecutorService = Executors.newFixedThreadPool(canalKafkaWorkers.size()); - for (CanalAdapterKafkaWorker v : canalKafkaWorkers.values()) { - final CanalAdapterKafkaWorker cakw = v; + if (canalMQWorker.size() > 0) { + ExecutorService stopKafkaExecutorService = Executors.newFixedThreadPool(canalMQWorker.size()); + for (AbstractCanalAdapterWorker tmp : canalMQWorker.values()) { + final AbstractCanalAdapterWorker worker = tmp; stopKafkaExecutorService.submit(new Runnable() { - @Override public void run() { - cakw.stop(); + worker.stop(); } }); } diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml index a5fc4813..6117fb95 100644 --- a/client-launcher/src/main/resources/canal-client.yml +++ b/client-launcher/src/main/resources/canal-client.yml @@ -10,7 +10,9 @@ canalServerHost: 127.0.0.1:11111 # - name: hbase # hosts: slave1:2181 # properties: {znodeParent: "/hbase-unsecure"} + mqTopics: +- mqMode: rocketmq - topic: example groups: - groupId: example_g1 diff --git a/deployer/src/main/resources/mq.yml b/deployer/src/main/resources/mq.yml index d8b18f4a..214bcb6f 100644 --- a/deployer/src/main/resources/mq.yml +++ b/deployer/src/main/resources/mq.yml @@ -10,7 +10,7 @@ filterTransactionEntry: true canalDestinations: - canalDestination: example topic: example - partition: + partition: 0 # 一个destination可以对应多个topic #topics: # - topic: example diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index 5a2900de..b7c57f2a 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -35,14 +35,13 @@ public class CanalRocketMQProducer implements CanalMQProducer { } @Override - public void send(MQProperties.Topic topic, com.alibaba.otter.canal.protocol.Message data) { + public void send(final MQProperties.Topic topic, com.alibaba.otter.canal.protocol.Message data) { try { Message message = new Message(topic.getTopic(), CanalMessageSerializer.serializer(data)); this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override public MessageQueue select(List mqs, Message msg, Object arg) { - // int index = (arg.hashCode() % mqs.size()); - return mqs.get(1); + return mqs.get(topic.getPartition()); } }, null); } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { From a22517adc21101d85c56acb7a839c9bb5e92d1d5 Mon Sep 17 00:00:00 2001 From: XiaoFu Date: Tue, 25 Sep 2018 17:53:54 +0800 Subject: [PATCH 156/360] Fix link error --- README.md | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index c28fc0ea..9fde64b5 100644 --- a/README.md +++ b/README.md @@ -54,25 +54,25 @@ See the wiki page for : wiki文

      wiki文档列表

      */ @@ -239,12 +245,23 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C } else { // 记录到流式信息 Long batchId = canalInstance.getMetaManager().addBatch(clientIdentity, events.getPositionRange()); - List entrys = Lists.transform(events.getEvents(), new Function() { + boolean raw = isRaw(canalInstance.getEventStore()); + List entrys = null; + if (raw) { + entrys = Lists.transform(events.getEvents(), new Function() { - public ByteString apply(Event input) { - return input.getRawEntry(); - } - }); + public ByteString apply(Event input) { + return input.getRawEntry(); + } + }); + } else { + entrys = Lists.transform(events.getEvents(), new Function() { + + public CanalEntry.Entry apply(Event input) { + return input.getEntry(); + } + }); + } if (logger.isInfoEnabled()) { logger.info("get successfully, clientId:{} batchSize:{} real size is {} and result is [batchId:{} , position:{}]", clientIdentity.getClientId(), @@ -255,7 +272,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C } // 直接提交ack ack(clientIdentity, batchId); - return new Message(batchId, true, entrys); + return new Message(batchId, raw, entrys); } } } @@ -283,7 +300,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C * b. 如果timeout不为null * 1. timeout为0,则采用get阻塞方式,获取数据,不设置超时,直到有足够的batchSize数据才返回 * 2. timeout不为0,则采用get+timeout方式,获取数据,超时还没有batchSize足够的数据,有多少返回多少 - * + * * 注意: meta获取和数据的获取需要保证顺序性,优先拿到meta的,一定也会是优先拿到数据,所以需要加同步. (不能出现先拿到meta,拿到第二批数据,这样就会导致数据顺序性出现问题) * */ @@ -311,7 +328,8 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C } if (CollectionUtils.isEmpty(events.getEvents())) { - // logger.debug("getWithoutAck successfully, clientId:{} batchSize:{} but result + // logger.debug("getWithoutAck successfully, clientId:{} + // batchSize:{} but result // is null", // clientIdentity.getClientId(), // batchSize); @@ -319,12 +337,23 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C } else { // 记录到流式信息 Long batchId = canalInstance.getMetaManager().addBatch(clientIdentity, events.getPositionRange()); - List entrys = Lists.transform(events.getEvents(), new Function() { + boolean raw = isRaw(canalInstance.getEventStore()); + List entrys = null; + if (raw) { + entrys = Lists.transform(events.getEvents(), new Function() { - public ByteString apply(Event input) { - return input.getRawEntry(); - } - }); + public ByteString apply(Event input) { + return input.getRawEntry(); + } + }); + } else { + entrys = Lists.transform(events.getEvents(), new Function() { + + public CanalEntry.Entry apply(Event input) { + return input.getEntry(); + } + }); + } if (logger.isInfoEnabled()) { logger.info("getWithoutAck successfully, clientId:{} batchSize:{} real size is {} and result is [batchId:{} , position:{}]", clientIdentity.getClientId(), @@ -333,7 +362,7 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C batchId, events.getPositionRange()); } - return new Message(batchId, true, entrys); + return new Message(batchId, raw, entrys); } } @@ -515,17 +544,25 @@ public class CanalServerWithEmbedded extends AbstractCanalLifeCycle implements C // 发现provider, 进行初始化 if (list.size() > 1) { logger.warn("Found more than one CanalMetricsProvider, use the first one."); - //报告冲突 + // 报告冲突 for (CanalMetricsProvider p : list) { logger.warn("Found CanalMetricsProvider: {}.", p.getClass().getName()); } } - //默认使用第一个 + // 默认使用第一个 CanalMetricsProvider provider = list.get(0); this.metrics = provider.getService(); } } + private boolean isRaw(CanalEventStore eventStore) { + if (eventStore instanceof MemoryEventStoreWithBuffer) { + return ((MemoryEventStoreWithBuffer) eventStore).isRaw(); + } + + return true; + } + // ========= setter ========== public void setCanalInstanceGenerator(CanalInstanceGenerator canalInstanceGenerator) { diff --git a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java index 76124ec4..d34450ed 100644 --- a/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java +++ b/sink/src/main/java/com/alibaba/otter/canal/sink/entry/EntryEventSink.java @@ -20,6 +20,7 @@ import com.alibaba.otter.canal.sink.CanalEventDownStreamHandler; import com.alibaba.otter.canal.sink.CanalEventSink; import com.alibaba.otter.canal.sink.exception.CanalSinkException; import com.alibaba.otter.canal.store.CanalEventStore; +import com.alibaba.otter.canal.store.memory.MemoryEventStoreWithBuffer; import com.alibaba.otter.canal.store.model.Event; /** @@ -42,7 +43,8 @@ public class EntryEventSink extends AbstractCanalEventSink props = entry.getHeader().getPropsList(); if (props != null) { @@ -60,6 +64,16 @@ public class Event implements Serializable { } } } + + if (raw) { + // build raw + this.rawEntry = entry.toByteString(); + this.rawLength = rawEntry.size(); + } else { + this.entry = entry; + // 按照3倍的event length预估 + this.rawLength = entry.getHeader().getEventLength() * 3; + } } public LogIdentity getLogIdentity() { @@ -150,6 +164,14 @@ public class Event implements Serializable { this.rowsCount = rowsCount; } + public CanalEntry.Entry getEntry() { + return entry; + } + + public void setEntry(CanalEntry.Entry entry) { + this.entry = entry; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } From 15e79c140075a145ba8a422cce2ba626fcb222c5 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 24 Oct 2018 15:40:48 +0800 Subject: [PATCH 186/360] =?UTF-8?q?=E8=B0=83=E6=95=B4Hbase=E9=80=82?= =?UTF-8?q?=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/common/pom.xml | 10 +++ ...nalOuterAdapter.java => OuterAdapter.java} | 18 ++++- .../adapter/support/AdapterConfigs.java | 28 ++++++- .../adapter/support/DatasourceConfig.java | 75 ++++++++++++++++++ .../loader/AbstractCanalAdapterWorker.java | 30 ++++---- .../loader/CanalAdapterKafkaWorker.java | 9 +-- .../adapter/loader/CanalAdapterLoader.java | 18 ++--- .../loader/CanalAdapterRocketMQWorker.java | 4 +- .../adapter/loader/CanalAdapterWorker.java | 10 +-- .../client/adapter/hbase/HbaseAdapter.java | 6 +- .../adapter/hbase/config/MappingConfig.java | 9 +++ .../hbase/config/MappingConfigLoader.java | 2 +- .../hbase/service/HbaseEtlService.java | 4 + ...a.otter.canal.client.adapter.OuterAdapter} | 0 .../main/resources/hbase/mytest_person2.yml | 3 +- client-adapter/launcher/pom.xml | 1 + .../launcher/CanalAdapterApplication.java | 44 ----------- .../launcher/config/AdapterCanalConfig.java | 2 +- .../launcher/config/AdapterConfig.java | 76 +++++++++++++++++++ .../launcher/config/AdapterConfigPath.java | 35 --------- .../loader/AbstractCanalAdapterWorker.java | 53 +++++-------- .../launcher/loader/CanalAdapterLoader.java | 27 +++---- .../launcher/loader/CanalAdapterService.java | 65 ++++++++++++++++ .../launcher/loader/CanalAdapterWorker.java | 10 +-- .../src/main/resources/application.yml | 7 +- .../adapter/logger/LoggerAdapterExample.java | 6 +- ...a.otter.canal.client.adapter.OuterAdapter} | 0 27 files changed, 366 insertions(+), 186 deletions(-) rename client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/{CanalOuterAdapter.java => OuterAdapter.java} (70%) create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java rename client-adapter/hbase/src/main/resources/META-INF/canal/{com.alibaba.otter.canal.client.adapter.CanalOuterAdapter => com.alibaba.otter.canal.client.adapter.OuterAdapter} (100%) create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java delete mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfigPath.java create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java rename client-adapter/logger/src/main/resources/META-INF/canal/{com.alibaba.otter.canal.client.adapter.CanalOuterAdapter => com.alibaba.otter.canal.client.adapter.OuterAdapter} (100%) diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml index a581ec4c..d61d8d11 100644 --- a/client-adapter/common/pom.xml +++ b/client-adapter/common/pom.xml @@ -28,6 +28,16 @@ joda-time 2.9.4 + + com.alibaba + druid + 1.1.9 + + + mysql + mysql-connector-java + 5.1.40 + diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java similarity index 70% rename from client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index 5fe0a377..c4ee60fc 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/CanalOuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.client.adapter; +import java.util.Map; + import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.SPI; @@ -11,7 +13,7 @@ import com.alibaba.otter.canal.client.adapter.support.SPI; * @version 1.0.0 */ @SPI("logger") -public interface CanalOuterAdapter { +public interface OuterAdapter { /** * 外部适配器初始化接口 @@ -21,11 +23,11 @@ public interface CanalOuterAdapter { void init(CanalOuterAdapterConfiguration configuration); /** - * 往适配器中写入数据 + * 往适配器中同步数据 * * @param dml 数据包 */ - void writeOut(Dml dml); + void sync(Dml dml); // void writeOut(FlatMessage flatMessage); @@ -33,4 +35,14 @@ public interface CanalOuterAdapter { * 外部适配器销毁接口 */ void destroy(); + + /** + * Etl操作 + * + * @param criteriaSql 条件拼接sql + */ + default void etl(String criteriaSql) { + throw new UnsupportedOperationException(); + } + } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java index 13e51af1..44d29ee9 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java @@ -1,8 +1,28 @@ package com.alibaba.otter.canal.client.adapter.support; -import com.google.common.collect.ArrayListMultimap; -import com.google.common.collect.Multimap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; -public interface AdapterConfigs { - Multimap configs = ArrayListMultimap.create(); +public class AdapterConfigs { + + private static Map> configs = new ConcurrentHashMap<>(); + + public static void put(String key, String value) { + Set values = configs.get(key); + if (values == null) { + values = new LinkedHashSet<>(); + } + values.add(value); + configs.put(key, values); + } + + public static Set get(String key) { + return configs.get(key); + } + + public static void clear() { + configs.clear(); + } } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java new file mode 100644 index 00000000..cf2ec471 --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java @@ -0,0 +1,75 @@ +package com.alibaba.otter.canal.client.adapter.support; + +import com.alibaba.druid.pool.DruidDataSource; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DatasourceConfig { + + public final static Map DATA_SOURCES = new ConcurrentHashMap<>(); + + private String driver = "com.mysql.jdbc.Driver"; + private String url; + private String database; + private String type = "mysql"; + private String username; + private String password; + private Integer maxActive = 3; + + public String getDriver() { + return driver; + } + + public void setDriver(String driver) { + this.driver = driver; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Integer getMaxActive() { + return maxActive; + } + + public void setMaxActive(Integer maxActive) { + this.maxActive = maxActive; + } +} diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java index c1f55cd9..cd3f718d 100644 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java @@ -10,7 +10,7 @@ import java.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.MessageUtil; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -26,10 +26,10 @@ public abstract class AbstractCanalAdapterWorker { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - protected String canalDestination; // canal实例 - protected List> canalOuterAdapters; // 外部适配器 - protected ExecutorService groupInnerExecutorService; // 组内工作线程池 - protected volatile boolean running = false; // 是否运行中 + protected String canalDestination; // canal实例 + protected List> canalOuterAdapters; // 外部适配器 + protected ExecutorService groupInnerExecutorService; // 组内工作线程池 + protected volatile boolean running = false; // 是否运行中 protected Thread thread = null; protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { @@ -42,21 +42,21 @@ public abstract class AbstractCanalAdapterWorker { protected void writeOut(final Message message) { List> futures = new ArrayList<>(); // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { - final List adapters = outerAdapters; + for (List outerAdapters : canalOuterAdapters) { + final List adapters = outerAdapters; futures.add(groupInnerExecutorService.submit(new Callable() { @Override public Boolean call() { try { // 组内适配器穿行运行,尽量不要配置组内适配器 - for (final CanalOuterAdapter c : adapters) { + for (final OuterAdapter c : adapters) { long begin = System.currentTimeMillis(); MessageUtil.parse4Dml(message, new MessageUtil.Consumer() { @Override public void accept(Dml dml) { - c.writeOut(dml); + c.sync(dml); } }); @@ -90,18 +90,18 @@ public abstract class AbstractCanalAdapterWorker { protected void writeOut(final FlatMessage flatMessage) { List> futures = new ArrayList<>(); // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { - final List adapters = outerAdapters; + for (List outerAdapters : canalOuterAdapters) { + final List adapters = outerAdapters; futures.add(groupInnerExecutorService.submit(new Callable() { @Override public Boolean call() { try { // 组内适配器穿行运行,尽量不要配置组内适配器 - for (CanalOuterAdapter c : adapters) { + for (OuterAdapter c : adapters) { long begin = System.currentTimeMillis(); Dml dml = MessageUtil.flatMessage2Dml(flatMessage); - c.writeOut(dml); + c.sync(dml); if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", c.getClass().getName(), @@ -154,8 +154,8 @@ public abstract class AbstractCanalAdapterWorker { } groupInnerExecutorService.shutdown(); logger.info("topic connectors' worker thread dead!"); - for (List outerAdapters : canalOuterAdapters) { - for (CanalOuterAdapter adapter : outerAdapters) { + for (List outerAdapters : canalOuterAdapters) { + for (OuterAdapter adapter : outerAdapters) { adapter.destroy(); } } diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java index 4429ca80..b2d3e583 100644 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java @@ -4,12 +4,11 @@ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import org.apache.kafka.clients.consumer.CommitFailedException; import org.apache.kafka.common.errors.WakeupException; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -30,7 +29,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { private boolean flatMessage; public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId, - List> canalOuterAdapters, boolean flatMessage){ + List> canalOuterAdapters, boolean flatMessage){ this.canalOuterAdapters = canalOuterAdapters; this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); this.topic = topic; @@ -81,8 +80,8 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { } groupInnerExecutorService.shutdown(); logger.info("topic {} connectors' worker thread dead!", this.topic); - for (List outerAdapters : canalOuterAdapters) { - for (CanalOuterAdapter adapter : outerAdapters) { + for (List outerAdapters : canalOuterAdapters) { + for (OuterAdapter adapter : outerAdapters) { adapter.destroy(); } } diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java index 471b6a3d..05ae6d2c 100644 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java +++ b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java @@ -13,7 +13,7 @@ import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; @@ -33,7 +33,7 @@ public class CanalAdapterLoader { private Map canalMQWorker = new HashMap<>(); - private ExtensionLoader loader; + private ExtensionLoader loader; public CanalAdapterLoader(CanalClientConfig canalClientConfig){ this.canalClientConfig = canalClientConfig; @@ -48,7 +48,7 @@ public class CanalAdapterLoader { throw new RuntimeException("Blank config property: canalInstances or canalMQTopics"); } - loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class, "" /* + loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class, "" /* * TODO * canalClientConfig * . @@ -68,10 +68,10 @@ public class CanalAdapterLoader { // 初始化canal-client的适配器 if (canalClientConfig.getCanalInstances() != null) { for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { - List> canalOuterAdapterGroups = new ArrayList<>(); + List> canalOuterAdapterGroups = new ArrayList<>(); for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { - List canalOutConnectors = new ArrayList<>(); + List canalOutConnectors = new ArrayList<>(); for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) { loadConnector(c, canalOutConnectors); } @@ -93,9 +93,9 @@ public class CanalAdapterLoader { if (canalClientConfig.getMqTopics() != null) { for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) { for (CanalClientConfig.Group group : topic.getGroups()) { - List> canalOuterAdapterGroups = new ArrayList<>(); + List> canalOuterAdapterGroups = new ArrayList<>(); - List canalOuterAdapters = new ArrayList<>(); + List canalOuterAdapters = new ArrayList<>(); for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { loadConnector(config, canalOuterAdapters); @@ -125,9 +125,9 @@ public class CanalAdapterLoader { } } - private void loadConnector(CanalOuterAdapterConfiguration config, List canalOutConnectors) { + private void loadConnector(CanalOuterAdapterConfiguration config, List canalOutConnectors) { try { - CanalOuterAdapter adapter = loader.getExtension(config.getName()); + OuterAdapter adapter = loader.getExtension(config.getName()); ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 替换ClassLoader Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader()); diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java index f0f674db..9c126297 100644 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java @@ -8,7 +8,7 @@ import java.util.concurrent.TimeUnit; import org.apache.kafka.clients.consumer.CommitFailedException; import org.apache.kafka.common.errors.WakeupException; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector; import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider; import com.alibaba.otter.canal.protocol.Message; @@ -26,7 +26,7 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { private String topic; public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId, - List> canalOuterAdapters){ + List> canalOuterAdapters){ logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); this.canalOuterAdapters = canalOuterAdapters; this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java index e341c6a3..e72ed2d5 100644 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java +++ b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java @@ -6,7 +6,7 @@ import java.util.concurrent.Executors; import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; import com.alibaba.otter.canal.protocol.Message; @@ -31,7 +31,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { * @param canalOuterAdapters 外部适配器组 */ public CanalAdapterWorker(String canalDestination, SocketAddress address, - List> canalOuterAdapters){ + List> canalOuterAdapters){ this.canalOuterAdapters = canalOuterAdapters; this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); @@ -46,7 +46,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { * @param canalOuterAdapters 外部适配器组 */ public CanalAdapterWorker(String canalDestination, String zookeeperHosts, - List> canalOuterAdapters){ + List> canalOuterAdapters){ this.canalOuterAdapters = canalOuterAdapters; this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); @@ -96,8 +96,8 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { } groupInnerExecutorService.shutdown(); logger.info("destination {} adapters' worker thread dead!", canalDestination); - for (List outerAdapters : canalOuterAdapters) { - for (CanalOuterAdapter adapter : outerAdapters) { + for (List outerAdapters : canalOuterAdapters) { + for (OuterAdapter adapter : outerAdapters) { adapter.destroy(); } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index d75cea67..c9b67998 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -10,7 +10,7 @@ import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.client.ConnectionFactory; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService; @@ -25,7 +25,7 @@ import com.alibaba.otter.canal.client.adapter.support.SPI; * @version 1.0.0 */ @SPI("hbase") -public class HbaseAdapter implements CanalOuterAdapter { +public class HbaseAdapter implements OuterAdapter { private static volatile Map mappingConfigCache = null; @@ -83,7 +83,7 @@ public class HbaseAdapter implements CanalOuterAdapter { } @Override - public void writeOut(Dml dml) { + public void sync(Dml dml) { if (dml == null) { return; } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java index 6d70bdde..c3c30c1b 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -13,9 +13,18 @@ import java.util.Set; * @version 1.0.0 */ public class MappingConfig { + private String dataSourceKey; private HbaseOrm hbaseOrm; + public String getDataSourceKey() { + return dataSourceKey; + } + + public void setDataSourceKey(String dataSourceKey) { + this.dataSourceKey = dataSourceKey; + } + public HbaseOrm getHbaseOrm() { return hbaseOrm; } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 5922d63e..173fc023 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -37,7 +37,7 @@ public class MappingConfigLoader { Map result = new LinkedHashMap<>(); - Collection configs = AdapterConfigs.configs.get("hbase"); + Collection configs = AdapterConfigs.get("hbase"); for (String c : configs) { if (c == null) { continue; diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java new file mode 100644 index 00000000..cccd2db6 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -0,0 +1,4 @@ +package com.alibaba.otter.canal.client.adapter.hbase.service; + +public class HbaseEtlService { +} diff --git a/client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter b/client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter similarity index 100% rename from client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter rename to client-adapter/hbase/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter diff --git a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml index 575a1bf5..f2b6a594 100644 --- a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml +++ b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml @@ -1,8 +1,9 @@ +dataSourceKey: defaultDS hbaseOrm: mode: PHOENIX #NATIVE #STRING database: mytest # 数据库名 table: person2 # 数据库表名 - hbaseTable: MYTEST.PERSON2 # HBase表名 + hbaseTable: MYTEST_PERSON2 # HBase表名 family: CF # 默认统一Family名称 uppercaseQualifier: true # 字段名转大写, 默认为true commitBatch: 3000 # 批量提交的大小 diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index 7b94bfc9..8b91f27f 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -81,6 +81,7 @@ + canal-client-launcher org.springframework.boot diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java index 4b0035d8..56bbde8e 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java @@ -1,56 +1,12 @@ package com.alibaba.otter.canal.adapter.launcher; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.annotation.Resource; - -import com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterLoader; -import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; - @SpringBootApplication public class CanalAdapterApplication { - private static final Logger logger = LoggerFactory.getLogger(CanalAdapterApplication.class); - - private static CanalAdapterLoader adapterLoader; public static void main(String[] args) { new SpringApplicationBuilder(CanalAdapterApplication.class).run(args); } - - @Resource - private CanalClientConfig canalClientConf; - - @PostConstruct - public void init() { - if (adapterLoader == null) { - try { - logger.info("## start the canal client adapters."); - adapterLoader = new CanalAdapterLoader(canalClientConf); - adapterLoader.init(); - logger.info("## the canal client adapters are running now ......"); - } catch (Throwable e) { - logger.error("## something goes wrong when starting up the canal client adapters:", e); - System.exit(0); - } - } - } - - @PreDestroy - public void destroy() { - try { - logger.info("## stop the canal client adapters"); - if (adapterLoader != null) { - adapterLoader.destroy(); - } - } catch (Throwable e) { - logger.warn("## something goes wrong when stopping canal client adapters:", e); - } finally { - logger.info("## canal client adapters are down."); - } - } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index 5b232c36..447f896d 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -4,7 +4,7 @@ import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; -@ConfigurationProperties(prefix = "canal.conf") @Component +@ConfigurationProperties(prefix = "canal.conf") public class AdapterCanalConfig extends CanalClientConfig { } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java new file mode 100644 index 00000000..06cb1730 --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java @@ -0,0 +1,76 @@ +package com.alibaba.otter.canal.adapter.launcher.config; + +import java.sql.SQLException; +import java.util.List; +import java.util.Map; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; + +@Component +@ConfigurationProperties(prefix = "adapter.conf") +public class AdapterConfig { + + private Map datasourceConfigs; + + private List adapterConfigs; + + public List getAdapterConfigs() { + return adapterConfigs; + } + + public Map getDatasourceConfigs() { + return datasourceConfigs; + } + + public void setDatasourceConfigs(Map datasourceConfigs) { + this.datasourceConfigs = datasourceConfigs; + + if (datasourceConfigs != null) { + for (Map.Entry entry : datasourceConfigs.entrySet()) { + DatasourceConfig datasourceConfig = entry.getValue(); + // 加载数据源连接池 + DruidDataSource ds = new DruidDataSource(); + ds.setDriverClassName(datasourceConfig.getDriver()); + ds.setUrl(datasourceConfig.getUrl()); + ds.setUsername(datasourceConfig.getUsername()); + ds.setPassword(datasourceConfig.getPassword()); + ds.setInitialSize(1); + ds.setMinIdle(1); + ds.setMaxActive(datasourceConfig.getMaxActive()); + ds.setMaxWait(60000); + ds.setTimeBetweenEvictionRunsMillis(60000); + ds.setMinEvictableIdleTimeMillis(300000); + ds.setPoolPreparedStatements(false); + ds.setMaxPoolPreparedStatementPerConnectionSize(20); + ds.setValidationQuery("select 1"); + try { + ds.init(); + } catch (SQLException e) { + throw new RuntimeException(e); + } + DatasourceConfig.DATA_SOURCES.put(entry.getKey(), ds); + } + } + } + + public void setAdapterConfigs(List adapterConfigs) { + this.adapterConfigs = adapterConfigs; + + if (adapterConfigs != null) { + AdapterConfigs.clear(); + for (String adapterConfig : adapterConfigs) { + int idx = adapterConfig.indexOf("/"); + if (idx > -1) { + String type = adapterConfig.substring(0, idx); + String ymlFile = adapterConfig.substring(idx + 1); + AdapterConfigs.put(type, ymlFile); + } + } + } + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfigPath.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfigPath.java deleted file mode 100644 index 474d43af..00000000 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfigPath.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.alibaba.otter.canal.adapter.launcher.config; - -import java.util.List; - -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; - -@Component -@ConfigurationProperties(prefix = "adapter.conf.path") -public class AdapterConfigPath { - - private List adapterConfigs; - - public List getAdapterConfigs() { - return adapterConfigs; - } - - public void setAdapterConfigs(List adapterConfigs) { - this.adapterConfigs = adapterConfigs; - - if (adapterConfigs != null) { - AdapterConfigs.configs.clear(); - for (String adapterConfig : adapterConfigs) { - int idx = adapterConfig.indexOf("/"); - if (idx > -1) { - String type = adapterConfig.substring(0, idx); - String ymlFile = adapterConfig.substring(idx + 1); - AdapterConfigs.configs.put(type, ymlFile); - } - } - } - } -} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index e0912bab..694251ac 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -10,7 +10,7 @@ import java.util.concurrent.Future; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.MessageUtil; import com.alibaba.otter.canal.protocol.Message; @@ -25,43 +25,33 @@ public abstract class AbstractCanalAdapterWorker { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected String canalDestination; // canal实例 - protected List> canalOuterAdapters; // 外部适配器 + protected List> canalOuterAdapters; // 外部适配器 protected ExecutorService groupInnerExecutorService; // 组内工作线程池 protected volatile boolean running = false; // 是否运行中 protected Thread thread = null; - protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - logger.error("parse events has an error", e); - } - }; + protected Thread.UncaughtExceptionHandler handler = (t, e) -> logger.error("parse events has an error", e); protected void writeOut(final Message message) { List> futures = new ArrayList<>(); // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { - final List adapters = outerAdapters; - futures.add(groupInnerExecutorService.submit(new Callable() { + for (List outerAdapters : canalOuterAdapters) { + final List adapters = outerAdapters; + futures.add(groupInnerExecutorService.submit(() -> { + try { + // 组内适配器穿行运行,尽量不要配置组内适配器 + for (final OuterAdapter c : adapters) { + long begin = System.currentTimeMillis(); + MessageUtil.parse4Dml(message, c::sync); - @Override - public Boolean call() { - try { - // 组内适配器穿行运行,尽量不要配置组内适配器 - for (final CanalOuterAdapter c : adapters) { - long begin = System.currentTimeMillis(); - MessageUtil.parse4Dml(message, c::writeOut); - - if (logger.isDebugEnabled()) { - logger.debug("{} elapsed time: {}", - c.getClass().getName(), - (System.currentTimeMillis() - begin)); - } + if (logger.isDebugEnabled()) { + logger.debug("{} elapsed time: {}", + c.getClass().getName(), + (System.currentTimeMillis() - begin)); } - return true; - } catch (Exception e) { - return false; } + return true; + } catch (Exception e) { + return false; } })); @@ -131,9 +121,6 @@ public abstract class AbstractCanalAdapterWorker { if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { logger.error("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); } - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); - } } protected void stopOutAdapters() { @@ -146,8 +133,8 @@ public abstract class AbstractCanalAdapterWorker { } groupInnerExecutorService.shutdown(); logger.info("topic connectors' worker thread dead!"); - for (List outerAdapters : canalOuterAdapters) { - for (CanalOuterAdapter adapter : outerAdapters) { + for (List outerAdapters : canalOuterAdapters) { + for (OuterAdapter adapter : outerAdapters) { adapter.destroy(); } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 71c9d130..6e7d2bb8 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -9,7 +9,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; import org.slf4j.Logger; @@ -24,15 +24,15 @@ import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; */ public class CanalAdapterLoader { - private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); + private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); - private CanalClientConfig canalClientConfig; + private CanalClientConfig canalClientConfig; - private Map canalWorkers = new HashMap<>(); + private Map canalWorkers = new HashMap<>(); - private ExtensionLoader loader; + private ExtensionLoader loader; - public CanalAdapterLoader(CanalClientConfig canalClientConfig) { + public CanalAdapterLoader(CanalClientConfig canalClientConfig){ this.canalClientConfig = canalClientConfig; } @@ -40,12 +40,7 @@ public class CanalAdapterLoader { * 初始化canal-client */ public void init() { - // canal instances 和 mq topics 配置不能同时为空 - // if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getMqTopics() == null) { - // throw new RuntimeException("Blank config property: canalInstances or canalMQTopics"); - // } - - loader = ExtensionLoader.getExtensionLoader(CanalOuterAdapter.class); + loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class); String canalServerHost = this.canalClientConfig.getCanalServerHost(); SocketAddress sa = null; @@ -58,10 +53,10 @@ public class CanalAdapterLoader { // 初始化canal-client的适配器 if (canalClientConfig.getCanalInstances() != null) { for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { - List> canalOuterAdapterGroups = new ArrayList<>(); + List> canalOuterAdapterGroups = new ArrayList<>(); for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { - List canalOutConnectors = new ArrayList<>(); + List canalOutConnectors = new ArrayList<>(); for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) { loadConnector(c, canalOutConnectors); } @@ -80,9 +75,9 @@ public class CanalAdapterLoader { } } - private void loadConnector(CanalOuterAdapterConfiguration config, List canalOutConnectors) { + private void loadConnector(CanalOuterAdapterConfiguration config, List canalOutConnectors) { try { - CanalOuterAdapter adapter = loader.getExtension(config.getName()); + OuterAdapter adapter = loader.getExtension(config.getName()); ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 替换ClassLoader Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader()); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java new file mode 100644 index 00000000..f7b9e445 --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java @@ -0,0 +1,65 @@ +package com.alibaba.otter.canal.adapter.launcher.loader; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; +import com.alibaba.otter.canal.adapter.launcher.config.AdapterConfig; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; + +@Component +public class CanalAdapterService { + + private static final Logger logger = LoggerFactory.getLogger(CanalAdapterService.class); + + private static CanalAdapterLoader adapterLoader; + + @Resource + private AdapterCanalConfig adapterCanalConfig; + + // 注入配置保证配置优先先注册 + @Resource + private AdapterConfig adapterConfig; + + @PostConstruct + public void init() { + if (adapterLoader == null) { + try { + logger.info("## start the canal client adapters."); + adapterLoader = new CanalAdapterLoader(adapterCanalConfig); + adapterLoader.init(); + logger.info("## the canal client adapters are running now ......"); + } catch (Throwable e) { + logger.error("## something goes wrong when starting up the canal client adapters:", e); + System.exit(0); + } + } + } + + @PreDestroy + public void destroy() { + try { + logger.info("## stop the canal client adapters"); + if (adapterLoader != null) { + adapterLoader.destroy(); + } + for (DruidDataSource druidDataSource : DatasourceConfig.DATA_SOURCES.values()) { + try { + druidDataSource.close(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } catch (Throwable e) { + logger.warn("## something goes wrong when stopping canal client adapters:", e); + } finally { + logger.info("## canal client adapters are down."); + } + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 7e090c2d..1c38b353 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -6,7 +6,7 @@ import java.util.concurrent.Executors; import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; import com.alibaba.otter.canal.protocol.Message; @@ -31,7 +31,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { * @param canalOuterAdapters 外部适配器组 */ public CanalAdapterWorker(String canalDestination, SocketAddress address, - List> canalOuterAdapters){ + List> canalOuterAdapters){ this.canalOuterAdapters = canalOuterAdapters; this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); @@ -46,7 +46,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { * @param canalOuterAdapters 外部适配器组 */ public CanalAdapterWorker(String canalDestination, String zookeeperHosts, - List> canalOuterAdapters){ + List> canalOuterAdapters){ this.canalOuterAdapters = canalOuterAdapters; this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); @@ -96,8 +96,8 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { } groupInnerExecutorService.shutdown(); logger.info("destination {} adapters' worker thread dead!", canalDestination); - for (List outerAdapters : canalOuterAdapters) { - for (CanalOuterAdapter adapter : outerAdapters) { + for (List outerAdapters : canalOuterAdapters) { + for (OuterAdapter adapter : outerAdapters) { adapter.destroy(); } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index bb06e75b..7adef833 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -16,6 +16,11 @@ canal.conf: # hosts: slave1:2181 # properties: {znodeParent: "/hbase-unsecure"} -adapter.conf.path: +adapter.conf: + datasourceConfigs: + defaultDS: + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 adapterConfigs: - hbase/mytest_person2.yml \ No newline at end of file diff --git a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java index 1885d6c2..27d99aae 100644 --- a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java +++ b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java @@ -3,7 +3,7 @@ package com.alibaba.otter.canal.client.adapter.logger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.client.adapter.CanalOuterAdapter; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.SPI; @@ -16,7 +16,7 @@ import com.alibaba.otter.canal.client.adapter.support.SPI; */ @SPI("logger") // logger参数对应CanalOuterAdapterConfiguration配置中的name -public class LoggerAdapterExample implements CanalOuterAdapter { +public class LoggerAdapterExample implements OuterAdapter { private Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -26,7 +26,7 @@ public class LoggerAdapterExample implements CanalOuterAdapter { } @Override - public void writeOut(Dml dml) { + public void sync(Dml dml) { logger.info(dml.toString()); } diff --git a/client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter b/client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter similarity index 100% rename from client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.CanalOuterAdapter rename to client-adapter/logger/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter From 575dd59c7893e6a41f8b0da6b9f5a107580d40d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 24 Oct 2018 16:25:28 +0800 Subject: [PATCH 187/360] fixed mysql set(8 bit) type parse --- .../java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java index c1b96c3c..9fe22820 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java @@ -859,7 +859,7 @@ public final class RowsLogBuffer { // 转化为unsign long switch (len) { case 1: - value = buffer.getInt8(); + value = buffer.getUint8(); break; case 2: value = buffer.getUint16(); From 92708346310f679db608a2f64bdde8b22b7d2468 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 24 Oct 2018 16:46:26 +0800 Subject: [PATCH 188/360] fixed rds ak/sk config for manager --- .../manager/CanalInstanceWithManager.java | 56 +++++++++++++++++-- .../manager/model/CanalParameter.java | 36 ++++++++++++ .../alibaba/otter/canal/protocol/Message.java | 1 + 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index 778a40aa..36774d94 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -6,7 +6,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import com.alibaba.otter.canal.meta.FileMixedMetaManager; +import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +39,16 @@ import com.alibaba.otter.canal.parse.inbound.AbstractEventParser; import com.alibaba.otter.canal.parse.inbound.group.GroupEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.LocalBinlogEventParser; import com.alibaba.otter.canal.parse.inbound.mysql.MysqlEventParser; -import com.alibaba.otter.canal.parse.index.*; +import com.alibaba.otter.canal.parse.inbound.mysql.rds.RdsBinlogEventParserProxy; +import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.DefaultTableMetaTSDBFactory; +import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDB; +import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.TableMetaTSDBBuilder; +import com.alibaba.otter.canal.parse.index.CanalLogPositionManager; +import com.alibaba.otter.canal.parse.index.FailbackLogPositionManager; +import com.alibaba.otter.canal.parse.index.MemoryLogPositionManager; +import com.alibaba.otter.canal.parse.index.MetaLogPositionManager; +import com.alibaba.otter.canal.parse.index.PeriodMixedLogPositionManager; +import com.alibaba.otter.canal.parse.index.ZooKeeperLogPositionManager; import com.alibaba.otter.canal.parse.support.AuthenticationInfo; import com.alibaba.otter.canal.protocol.position.EntryPosition; import com.alibaba.otter.canal.sink.entry.EntryEventSink; @@ -229,7 +238,18 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { private CanalEventParser doInitEventParser(SourcingType type, List dbAddresses) { CanalEventParser eventParser; if (type.isMysql()) { - MysqlEventParser mysqlEventParser = new MysqlEventParser(); + MysqlEventParser mysqlEventParser = null; + if (StringUtils.isNotEmpty(parameters.getRdsAccesskey()) + && StringUtils.isNotEmpty(parameters.getRdsSecretkey()) + && StringUtils.isNotEmpty(parameters.getRdsInstanceId())) { + + mysqlEventParser = new RdsBinlogEventParserProxy(); + ((RdsBinlogEventParserProxy) mysqlEventParser).setAccesskey(parameters.getRdsAccesskey()); + ((RdsBinlogEventParserProxy) mysqlEventParser).setSecretkey(parameters.getRdsSecretkey()); + ((RdsBinlogEventParserProxy) mysqlEventParser).setInstanceId(parameters.getRdsInstanceId()); + } else { + mysqlEventParser = new MysqlEventParser(); + } mysqlEventParser.setDestination(destination); // 编码参数 mysqlEventParser.setConnectionCharset(Charset.forName(parameters.getConnectionCharset())); @@ -273,6 +293,34 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { mysqlEventParser.setFallbackIntervalInSeconds(parameters.getFallbackIntervalInSeconds()); mysqlEventParser.setProfilingEnabled(false); mysqlEventParser.setFilterTableError(parameters.getFilterTableError()); + mysqlEventParser.setIsGTIDMode(BooleanUtils.toBoolean(parameters.getGtidEnable())); + // tsdb + boolean tsdbEnable = BooleanUtils.toBoolean(parameters.getTsdbEnable()); + if (tsdbEnable) { + mysqlEventParser.setEnableTsdb(tsdbEnable); + mysqlEventParser.setTableMetaTSDBFactory(new DefaultTableMetaTSDBFactory() { + + @Override + public void destory(String destination) { + TableMetaTSDBBuilder.destory(destination); + } + + @Override + public TableMetaTSDB build(String destination, String springXml) { + try { + System.setProperty("canal.instance.tsdb.url", parameters.getTsdbJdbcUrl()); + System.setProperty("canal.instance.tsdb.dbUsername", parameters.getTsdbJdbcUserName()); + System.setProperty("canal.instance.tsdb.dbPassword", parameters.getTsdbJdbcPassword()); + + return TableMetaTSDBBuilder.build(destination, "classpath:spring/tsdb/mysql-tsdb.xml"); + } finally { + System.setProperty("canal.instance.tsdb.url", ""); + System.setProperty("canal.instance.tsdb.dbUsername", ""); + System.setProperty("canal.instance.tsdb.dbPassword", ""); + } + } + }); + } eventParser = mysqlEventParser; } else if (type.isLocalBinlog()) { LocalBinlogEventParser localBinlogEventParser = new LocalBinlogEventParser(); @@ -291,8 +339,8 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { parameters.getDbUsername(), parameters.getDbPassword(), parameters.getDefaultDatabaseName())); - } + eventParser = localBinlogEventParser; } else if (type.isOracle()) { throw new CanalException("unsupport SourcingType for " + type); diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java index 40df1485..ece1196e 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java @@ -97,6 +97,10 @@ public class CanalParameter implements Serializable { private String tsdbJdbcUrl; private String tsdbJdbcUserName; private String tsdbJdbcPassword; + private String rdsAccesskey; + private String rdsSecretkey; + private String rdsInstanceId; + private Boolean gtidEnable = Boolean.FALSE; // 是否开启gtid // ================================== 兼容字段处理 private InetSocketAddress masterAddress; // 主库信息 private String masterUsername; // 帐号 @@ -919,6 +923,38 @@ public class CanalParameter implements Serializable { this.tsdbJdbcPassword = tsdbJdbcPassword; } + public String getRdsAccesskey() { + return rdsAccesskey; + } + + public void setRdsAccesskey(String rdsAccesskey) { + this.rdsAccesskey = rdsAccesskey; + } + + public String getRdsSecretkey() { + return rdsSecretkey; + } + + public void setRdsSecretkey(String rdsSecretkey) { + this.rdsSecretkey = rdsSecretkey; + } + + public String getRdsInstanceId() { + return rdsInstanceId; + } + + public void setRdsInstanceId(String rdsInstanceId) { + this.rdsInstanceId = rdsInstanceId; + } + + public Boolean getGtidEnable() { + return gtidEnable; + } + + public void setGtidEnable(Boolean gtidEnable) { + this.gtidEnable = gtidEnable; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java index 751e09d0..bf79ee45 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/Message.java @@ -38,6 +38,7 @@ public class Message implements Serializable { } else { this.entries = entries == null ? new ArrayList() : entries; } + this.raw = raw; } public Message(long id){ From b5dac60cbb192ff2f9722b4ff3a2c1e4ac103326 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Wed, 24 Oct 2018 17:00:56 +0800 Subject: [PATCH 189/360] fixed cliet-adapter artifactId --- client-adapter/example/pom.xml | 4 ++-- client-adapter/launcher/pom.xml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client-adapter/example/pom.xml b/client-adapter/example/pom.xml index 3c68087a..606534e0 100644 --- a/client-adapter/example/pom.xml +++ b/client-adapter/example/pom.xml @@ -9,7 +9,7 @@ 4.0.0 com.alibaba.otter - example + client-adapter.example jar canal client adapter example module for otter ${project.version} @@ -145,4 +145,4 @@ - \ No newline at end of file + diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index 7b94bfc9..76480a70 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -9,7 +9,7 @@ 4.0.0 com.alibaba.otter - launcher + client-adapter.launcher jar canal client adapter launcher module for otter ${project.version} @@ -134,4 +134,4 @@ - \ No newline at end of file + From 0a7251add08f927f76bc5170258c0662a9c38c82 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 24 Oct 2018 18:30:42 +0800 Subject: [PATCH 190/360] =?UTF-8?q?HBase=E7=9A=84etl=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/OuterAdapter.java | 13 +- .../adapter/support/CanalClientConfig.java | 12 +- .../client/adapter/support/JdbcTypeUtil.java | 54 ++- ...iguration.java => OuterAdapterConfig.java} | 16 +- .../adapter/loader/CanalAdapterLoader.java | 8 +- .../client/adapter/hbase/HbaseAdapter.java | 59 ++- .../hbase/service/HbaseEtlService.java | 347 ++++++++++++++++++ .../hbase/service/HbaseSyncService.java | 21 +- client-adapter/launcher/pom.xml | 12 + .../launcher/config/AdapterConfig.java | 6 +- .../loader/AbstractCanalAdapterWorker.java | 92 ++--- .../loader/CanalAdapterKafkaWorker.java | 148 ++++++++ .../launcher/loader/CanalAdapterLoader.java | 66 +++- .../loader/CanalAdapterRocketMQWorker.java | 124 +++++++ .../launcher/loader/CanalAdapterWorker.java | 8 +- .../src/main/resources/application.yml | 28 +- .../adapter/logger/LoggerAdapterExample.java | 4 +- 17 files changed, 869 insertions(+), 149 deletions(-) rename client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/{CanalOuterAdapterConfiguration.java => OuterAdapterConfig.java} (51%) create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index c4ee60fc..a508d1ef 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -1,8 +1,8 @@ package com.alibaba.otter.canal.client.adapter; -import java.util.Map; +import java.util.List; -import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.SPI; @@ -20,7 +20,7 @@ public interface OuterAdapter { * * @param configuration 外部适配器配置信息 */ - void init(CanalOuterAdapterConfiguration configuration); + void init(OuterAdapterConfig configuration); /** * 往适配器中同步数据 @@ -29,8 +29,6 @@ public interface OuterAdapter { */ void sync(Dml dml); - // void writeOut(FlatMessage flatMessage); - /** * 外部适配器销毁接口 */ @@ -39,9 +37,10 @@ public interface OuterAdapter { /** * Etl操作 * - * @param criteriaSql 条件拼接sql + * @param task 任务名, 对应配置名 + * @param params etl筛选条件 */ - default void etl(String criteriaSql) { + default void etl(String task, List params) { throw new UnsupportedOperationException(); } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index d885e1cb..d7f7582a 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -109,13 +109,13 @@ public class CanalClientConfig { public static class AdapterGroup { - private List outAdapters; + private List outAdapters; - public List getOutAdapters() { + public List getOutAdapters() { return outAdapters; } - public void setOutAdapters(List outAdapters) { + public void setOutAdapters(List outAdapters) { this.outAdapters = outAdapters; } } @@ -159,7 +159,7 @@ public class CanalClientConfig { // private List adapters = new ArrayList<>(); - private List outAdapters; + private List outAdapters; public String getGroupId() { return groupId; @@ -169,11 +169,11 @@ public class CanalClientConfig { this.groupId = groupId; } - public List getOutAdapters() { + public List getOutAdapters() { return outAdapters; } - public void setOutAdapters(List outAdapters) { + public void setOutAdapters(List outAdapters) { this.outAdapters = outAdapters; } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java index 777770ef..d4cb8a0e 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java @@ -2,10 +2,7 @@ package com.alibaba.otter.canal.client.adapter.support; import java.math.BigDecimal; import java.math.BigInteger; -import java.sql.Date; -import java.sql.Time; -import java.sql.Timestamp; -import java.sql.Types; +import java.sql.*; import org.joda.time.DateTime; import org.slf4j.Logger; @@ -21,6 +18,55 @@ public class JdbcTypeUtil { private static Logger logger = LoggerFactory.getLogger(JdbcTypeUtil.class); + public static Object getRSData(ResultSet rs, String columnName, int jdbcType) throws SQLException { + if (jdbcType == Types.BIT || jdbcType == Types.BOOLEAN) { + return rs.getByte(columnName); + } else { + return rs.getObject(columnName); + } + } + + public static Class jdbcType2javaType(int jdbcType) { + switch (jdbcType) { + case Types.BIT: + case Types.BOOLEAN: +// return Boolean.class; + case Types.TINYINT: + return Byte.TYPE; + case Types.SMALLINT: + return Short.class; + case Types.INTEGER: + return Integer.class; + case Types.BIGINT: + return Long.class; + case Types.DECIMAL: + case Types.NUMERIC: + return BigDecimal.class; + case Types.REAL: + return Float.class; + case Types.FLOAT: + case Types.DOUBLE: + return Double.class; + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + return String.class; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BLOB: + return byte[].class; + case Types.DATE: + return java.sql.Date.class; + case Types.TIME: + return Time.class; + case Types.TIMESTAMP: + return Timestamp.class; + default: + return String.class; + } + } + public static Object typeConvert(String columnName, String value, int sqlType, String mysqlType) { if (value == null || value.equals("")) { return null; diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalOuterAdapterConfiguration.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java similarity index 51% rename from client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalOuterAdapterConfiguration.java rename to client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java index 790044c8..3aed2f16 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalOuterAdapterConfiguration.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java @@ -1,6 +1,6 @@ package com.alibaba.otter.canal.client.adapter.support; -import java.util.Properties; +import java.util.Map; /** * 外部适配器配置信息类 @@ -8,15 +8,15 @@ import java.util.Properties; * @author machengyuan 2018-8-18 下午10:15:12 * @version 1.0.0 */ -public class CanalOuterAdapterConfiguration { +public class OuterAdapterConfig { - private String name; // 适配器名称, 如: logger, hbase, es + private String name; // 适配器名称, 如: logger, hbase, es - private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址 + private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址 - private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址 + private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址 - private Properties properties; // 其余参数, 可填写适配器中的所需的配置信息 + private Map properties; // 其余参数, 可填写适配器中的所需的配置信息 public String getName() { return name; @@ -34,11 +34,11 @@ public class CanalOuterAdapterConfiguration { this.hosts = hosts; } - public Properties getProperties() { + public Map getProperties() { return properties; } - public void setProperties(Properties properties) { + public void setProperties(Map properties) { this.properties = properties; } diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java index 05ae6d2c..51b12a0a 100644 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java +++ b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java @@ -15,7 +15,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; -import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; /** @@ -72,7 +72,7 @@ public class CanalAdapterLoader { for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { List canalOutConnectors = new ArrayList<>(); - for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) { + for (OuterAdapterConfig c : connectorGroup.getOutAdapters()) { loadConnector(c, canalOutConnectors); } canalOuterAdapterGroups.add(canalOutConnectors); @@ -97,7 +97,7 @@ public class CanalAdapterLoader { List canalOuterAdapters = new ArrayList<>(); - for (CanalOuterAdapterConfiguration config : group.getOutAdapters()) { + for (OuterAdapterConfig config : group.getOutAdapters()) { loadConnector(config, canalOuterAdapters); } canalOuterAdapterGroups.add(canalOuterAdapters); @@ -125,7 +125,7 @@ public class CanalAdapterLoader { } } - private void loadConnector(CanalOuterAdapterConfiguration config, List canalOutConnectors) { + private void loadConnector(OuterAdapterConfig config, List canalOutConnectors) { try { OuterAdapter adapter = loader.getExtension(config.getName()); ClassLoader cl = Thread.currentThread().getContextClassLoader(); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index c9b67998..da51ac88 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -2,9 +2,11 @@ package com.alibaba.otter.canal.client.adapter.hbase; import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; -import org.apache.commons.lang.StringUtils; +import javax.sql.DataSource; + import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.client.Connection; @@ -13,9 +15,12 @@ import org.apache.hadoop.hbase.client.ConnectionFactory; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader; +import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService; -import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.SPI; /** @@ -27,56 +32,37 @@ import com.alibaba.otter.canal.client.adapter.support.SPI; @SPI("hbase") public class HbaseAdapter implements OuterAdapter { - private static volatile Map mappingConfigCache = null; + private static volatile Map hbaseMapping = null; // 文件名对应配置 + private static volatile Map mappingConfigCache = null; // 库名-表名对应配置 private Connection conn; private HbaseSyncService hbaseSyncService; + private HbaseTemplate hbaseTemplate; @Override - public void init(CanalOuterAdapterConfiguration configuration) { + public void init(OuterAdapterConfig configuration) { try { if (mappingConfigCache == null) { synchronized (MappingConfig.class) { if (mappingConfigCache == null) { - Map hbaseMapping = MappingConfigLoader.load(); + hbaseMapping = MappingConfigLoader.load(); mappingConfigCache = new HashMap<>(); for (MappingConfig mappingConfig : hbaseMapping.values()) { mappingConfigCache.put(mappingConfig.getHbaseOrm().getDatabase() + "-" - + mappingConfig.getHbaseOrm().getTable(), mappingConfig); + + mappingConfig.getHbaseOrm().getTable(), + mappingConfig); } } } } - String hosts = configuration.getZkHosts(); - if (StringUtils.isEmpty(hosts)) { - hosts = configuration.getHosts(); - } - if (StringUtils.isEmpty(hosts)) { - throw new RuntimeException("Empty zookeeper hosts"); - } - String[] zkHosts = StringUtils.split(hosts, ","); - int zkPort = 0; - StringBuilder hostsWithoutPort = new StringBuilder(); - for (String host : zkHosts) { - int i = host.indexOf(":"); - hostsWithoutPort.append(host, 0, i); - hostsWithoutPort.append(","); - if (zkPort == 0) zkPort = Integer.parseInt(host.substring(i + 1)); - } - hostsWithoutPort.deleteCharAt(hostsWithoutPort.length() - 1); - - String znode = configuration.getProperties().getProperty("znodeParent"); - if (StringUtils.isEmpty(znode)) { - znode = "/hbase"; - } + Map propertites = configuration.getProperties(); Configuration hbaseConfig = HBaseConfiguration.create(); - hbaseConfig.set("hbase.zookeeper.quorum", hostsWithoutPort.toString()); - hbaseConfig.set("hbase.zookeeper.property.clientPort", Integer.toString(zkPort)); - hbaseConfig.set("zookeeper.znode.parent", znode); + propertites.forEach(hbaseConfig::set); conn = ConnectionFactory.createConnection(hbaseConfig); - hbaseSyncService = new HbaseSyncService(conn); + hbaseTemplate = new HbaseTemplate(conn); + hbaseSyncService = new HbaseSyncService(hbaseTemplate); } catch (Exception e) { throw new RuntimeException(e); } @@ -93,6 +79,15 @@ public class HbaseAdapter implements OuterAdapter { hbaseSyncService.sync(config, dml); } + @Override + public void etl(String task, List params) { + MappingConfig config = hbaseMapping.get(task); + DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); + if (dataSource != null) { + HbaseEtlService.importData(dataSource, hbaseTemplate, config, params); + } + } + @Override public void destroy() { if (conn != null) { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index cccd2db6..2c919955 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -1,4 +1,351 @@ package com.alibaba.otter.canal.client.adapter.hbase.service; +import java.sql.*; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +import javax.sql.DataSource; + +import org.apache.hadoop.hbase.util.Bytes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.hbase.support.*; +import com.alibaba.otter.canal.client.adapter.support.JdbcTypeUtil; +import com.google.common.base.Joiner; + public class HbaseEtlService { + + private static Logger logger = LoggerFactory.getLogger(HbaseEtlService.class); + + public static Object sqlRS(DataSource ds, String sql, Function fun) throws SQLException { + Connection conn = null; + Statement smt = null; + ResultSet rs = null; + try { + conn = ds.getConnection(); + smt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + smt.setFetchSize(Integer.MIN_VALUE); + rs = smt.executeQuery(sql); + return fun.apply(rs); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + if (smt != null) { + try { + smt.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + } + } + + public static void createTable(HbaseTemplate hbaseTemplate, MappingConfig config) { + try { + // 判断hbase表是否存在,不存在则建表 + MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + if (!hbaseTemplate.tableExists(hbaseOrm.getHbaseTable())) { + hbaseTemplate.createTable(hbaseOrm.getHbaseTable(), hbaseOrm.getFamilies().toArray(new String[0])); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new RuntimeException(e); + } + } + + public static void importData(DataSource ds, HbaseTemplate hbaseTemplate, MappingConfig config, + List params) { + // EtlResult etlResult = new EtlResult(); + AtomicLong successCount = new AtomicLong(); + List errMsg = new ArrayList<>(); + String hbaseTable = ""; + try { + // if (config == null) { + // logger.error("Config is null!"); + // etlResult.setSucceeded(false); + // etlResult.setErrorMessage("Config is null!"); + // return etlResult; + // } + MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + hbaseTable = hbaseOrm.getHbaseTable(); + + long start = System.currentTimeMillis(); + + if (params != null && params.size() == 1 && "rebuild".equalsIgnoreCase(params.get(0))) { + logger.info(hbaseOrm.getHbaseTable() + " rebuild is starting!"); + // 如果表存在则删除 + if (hbaseTemplate.tableExists(hbaseOrm.getHbaseTable())) { + hbaseTemplate.disableTable(hbaseOrm.getHbaseTable()); + hbaseTemplate.deleteTable(hbaseOrm.getHbaseTable()); + } + params = null; + } else { + logger.info(hbaseOrm.getHbaseTable() + " etl is starting!"); + } + createTable(hbaseTemplate, config); + + // 拼接sql + String sql = "SELECT * FROM " + config.getHbaseOrm().getDatabase() + "." + hbaseOrm.getTable(); + + // 拼接条件 + if (params != null && params.size() == 1 && hbaseOrm.getEtlCondition() == null) { + AtomicBoolean stExists = new AtomicBoolean(false); + // 验证是否有SYS_TIME字段 + sqlRS(ds, sql, rs -> { + try { + ResultSetMetaData rsmd = rs.getMetaData(); + int cnt = rsmd.getColumnCount(); + for (int i = 1; i <= cnt; i++) { + String columnName = rsmd.getColumnName(i); + if ("SYS_TIME".equalsIgnoreCase(columnName)) { + stExists.set(true); + break; + } + } + } catch (Exception e) { + // ignore + } + return null; + }); + if (stExists.get()) { + sql += " WHERE SYS_TIME >= '" + params.get(0) + "' "; + } + } else if (hbaseOrm.getEtlCondition() != null && params != null) { + String etlCondition = hbaseOrm.getEtlCondition(); + int size = params.size(); + for (int i = 0; i < size; i++) { + etlCondition = etlCondition.replace("{" + i + "}", params.get(i)); + } + + sql += " " + etlCondition; + } + + // 获取总数 + String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT "; + long cnt = (Long) sqlRS(ds, countSql, rs -> { + Long count = null; + try { + if (rs.next()) { + count = ((Number) rs.getObject(1)).longValue(); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return count == null ? 0 : count; + }); + + // 当大于1万条记录时开启多线程 + if (cnt >= 10000) { + int threadCount = 3; // TODO 从配置读取默认为3 + long perThreadCnt = cnt / threadCount; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(threadCount); + for (int i = 0; i < threadCount; i++) { + long offset = i * perThreadCnt; + Long size = null; + if (i != threadCount - 1) { + size = perThreadCnt; + } + String sqlFinal; + if (size != null) { + sqlFinal = sql + " LIMIT " + offset + "," + size; + } else { + sqlFinal = sql + " LIMIT " + offset + "," + cnt; + } + Future future = executor + .submit(() -> executeSqlImport(ds, sqlFinal, hbaseOrm, hbaseTemplate, successCount, errMsg)); + futures.add(future); + } + + for (Future future : futures) { + future.get(); + } + + executor.shutdown(); + } else { + executeSqlImport(ds, sql, hbaseOrm, hbaseTemplate, successCount, errMsg); + } + + logger.info( + hbaseOrm.getHbaseTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); + + // etlResult.setResultMessage("导入HBase表 " + hbaseOrm.getHbaseTable() + " 数据:" + + // successCount.get() + " 条"); + } catch (Exception e) { + logger.error(e.getMessage(), e); + errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage()); + } + + // if (errMsg.isEmpty()) { + // etlResult.setSucceeded(true); + // } else { + // etlResult.setErrorMessage(Joiner.on("\n").join(errMsg)); + // } + // return etlResult; + } + + private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseOrm hbaseOrm, + HbaseTemplate hbaseTemplate, AtomicLong successCount, List errMsg) { + try { + sqlRS(ds, sql, rs -> { + int i = 1; + + try { + boolean complete = false; + List rows = new ArrayList<>(); + String[] rowKeyColumns = null; + if (hbaseOrm.getRowKey() != null) { + rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + } + while (rs.next()) { + int cc = rs.getMetaData().getColumnCount(); + int[] jdbcTypes = new int[cc]; + Class[] classes = new Class[cc]; + for (int j = 1; j <= cc; j++) { + int jdbcType = rs.getMetaData().getColumnType(j); + jdbcTypes[j - 1] = jdbcType; + classes[j - 1] = JdbcTypeUtil.jdbcType2javaType(jdbcType); + } + HRow row = new HRow(); + + if (rowKeyColumns != null) { + // 取rowKey字段拼接 + StringBuilder rowKeyVale = new StringBuilder(); + for (String rowKeyColumnName : rowKeyColumns) { + Object obj = rs.getObject(rowKeyColumnName); + if (obj != null) { + rowKeyVale.append(obj.toString()); + } + rowKeyVale.append("|"); + } + int len = rowKeyVale.length(); + if (len > 0) { + rowKeyVale.delete(len - 1, len); + } + row.setRowKey(Bytes.toBytes(rowKeyVale.toString())); + } + + for (int j = 1; j <= cc; j++) { + String columnName = rs.getMetaData().getColumnName(j); + + Object val = JdbcTypeUtil.getRSData(rs, columnName, jdbcTypes[j - 1]); + if (val == null) { + continue; + } + + MappingConfig.ColumnItem columnItem = hbaseOrm.getColumnItems().get(columnName); + // 没有配置映射 + if (columnItem == null) { + String family = hbaseOrm.getFamily(); + String qualifile = columnName; + if (hbaseOrm.isUppercaseQualifier()) { + qualifile = qualifile.toUpperCase(); + } + if (MappingConfig.Mode.STRING == hbaseOrm.getMode()) { + if (hbaseOrm.getRowKey() == null && j == 1) { + row.setRowKey(Bytes.toBytes(val.toString())); + } else { + row.addCell(family, qualifile, Bytes.toBytes(val.toString())); + } + } else if (MappingConfig.Mode.NATIVE == hbaseOrm.getMode()) { + Type type = Type.getType(classes[j - 1]); + if (hbaseOrm.getRowKey() == null && j == 1) { + row.setRowKey(TypeUtil.toBytes(val, type)); + } else { + row.addCell(family, qualifile, TypeUtil.toBytes(val, type)); + } + } else if (MappingConfig.Mode.PHOENIX == hbaseOrm.getMode()) { + PhType phType = PhType.getType(classes[j - 1]); + if (hbaseOrm.getRowKey() == null && j == 1) { + row.setRowKey(PhTypeUtil.toBytes(val, phType)); + } else { + row.addCell(family, qualifile, PhTypeUtil.toBytes(val, phType)); + } + } + } else { + // 如果不需要类型转换 + if (columnItem.getType() == null || "".equals(columnItem.getType())) { + if (val instanceof java.sql.Date) { + SimpleDateFormat dateFmt = new SimpleDateFormat("yyyy-MM-dd"); + val = dateFmt.format((Date) val); + } else if (val instanceof Timestamp) { + SimpleDateFormat datetimeFmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + val = datetimeFmt.format((Date) val); + } + + byte[] valBytes = Bytes.toBytes(val.toString()); + if (columnItem.isRowKey()) { + row.setRowKey(valBytes); + } else { + row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes); + } + } else { + PhType phType = PhType.getType(columnItem.getType()); + if (columnItem.isRowKey()) { + row.setRowKey(PhTypeUtil.toBytes(val, phType)); + } else { + row.addCell(columnItem.getFamily(), + columnItem.getQualifier(), + PhTypeUtil.toBytes(val, phType)); + } + } + } + } + + if (row.getRowKey() == null) throw new RuntimeException("RowKey 值为空"); + + rows.add(row); + complete = false; + if (i % hbaseOrm.getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + rows.clear(); + complete = true; + } + if (logger.isDebugEnabled()) { + logger.debug("import count:" + i); + } + // System.out.println(i); + i++; + successCount.incrementAndGet(); + } + + if (!complete && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + } + + } catch (Exception e) { + logger.error(hbaseOrm.getHbaseTable() + " etl failed! ==>" + e.getMessage(), e); + errMsg.add(hbaseOrm.getHbaseTable() + " etl failed! ==>" + e.getMessage()); + throw new RuntimeException(e); + } + return i; + }); + return true; + } catch (Exception e) { + logger.error(e.getMessage(), e); + return false; + } + } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java index 7e69f9d0..1ff406fd 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -1,23 +1,13 @@ package com.alibaba.otter.canal.client.adapter.hbase.service; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; -import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.util.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; -import com.alibaba.otter.canal.client.adapter.hbase.support.HRow; -import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate; -import com.alibaba.otter.canal.client.adapter.hbase.support.PhType; -import com.alibaba.otter.canal.client.adapter.hbase.support.PhTypeUtil; -import com.alibaba.otter.canal.client.adapter.hbase.support.Type; -import com.alibaba.otter.canal.client.adapter.hbase.support.TypeUtil; +import com.alibaba.otter.canal.client.adapter.hbase.support.*; import com.alibaba.otter.canal.client.adapter.support.Dml; /** @@ -32,8 +22,8 @@ public class HbaseSyncService { private HbaseTemplate hbaseTemplate; // HBase操作模板 - public HbaseSyncService(Connection conn){ - hbaseTemplate = new HbaseTemplate(conn); + public HbaseSyncService(HbaseTemplate hbaseTemplate){ + this.hbaseTemplate = hbaseTemplate; } public void sync(MappingConfig config, Dml dml) { @@ -375,7 +365,8 @@ public class HbaseSyncService { * @param value 值 * @return 复合字段rowKey */ - private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseOrm hbaseOrm, Object value) { + private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseOrm hbaseOrm, + Object value) { if (value == null) { return null; } diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index 8b91f27f..2f268088 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -50,6 +50,18 @@ spring-boot-configuration-processor true + + + org.apache.rocketmq + rocketmq-client + 4.3.0 + + + + org.apache.kafka + kafka-clients + 1.1.1 + diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java index 06cb1730..da37804f 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java @@ -4,6 +4,8 @@ import java.sql.SQLException; import java.util.List; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -15,6 +17,8 @@ import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; @ConfigurationProperties(prefix = "adapter.conf") public class AdapterConfig { + private static Logger logger = LoggerFactory.getLogger(AdapterConfig.class); + private Map datasourceConfigs; private List adapterConfigs; @@ -51,7 +55,7 @@ public class AdapterConfig { try { ds.init(); } catch (SQLException e) { - throw new RuntimeException(e); + logger.error("#Failed to initial datasource: " + datasourceConfig.getUrl(), e); } DatasourceConfig.DATA_SOURCES.put(entry.getKey(), ds); } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index 694251ac..e007faa0 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -7,6 +7,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.protocol.FlatMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,10 +26,10 @@ public abstract class AbstractCanalAdapterWorker { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - protected String canalDestination; // canal实例 - protected List> canalOuterAdapters; // 外部适配器 - protected ExecutorService groupInnerExecutorService; // 组内工作线程池 - protected volatile boolean running = false; // 是否运行中 + protected String canalDestination; // canal实例 + protected List> canalOuterAdapters; // 外部适配器 + protected ExecutorService groupInnerExecutorService; // 组内工作线程池 + protected volatile boolean running = false; // 是否运行中 protected Thread thread = null; protected Thread.UncaughtExceptionHandler handler = (t, e) -> logger.error("parse events has an error", e); @@ -69,47 +71,47 @@ public abstract class AbstractCanalAdapterWorker { } } - // protected void writeOut(final FlatMessage flatMessage) { - // List> futures = new ArrayList<>(); - // // 组间适配器并行运行 - // for (List outerAdapters : canalOuterAdapters) { - // final List adapters = outerAdapters; - // futures.add(groupInnerExecutorService.submit(new Callable() { - // - // @Override - // public Boolean call() { - // try { - // // 组内适配器穿行运行,尽量不要配置组内适配器 - // for (CanalOuterAdapter c : adapters) { - // long begin = System.currentTimeMillis(); - // Dml dml = MessageUtil.flatMessage2Dml(flatMessage); - // c.writeOut(dml); - // if (logger.isDebugEnabled()) { - // logger.debug("{} elapsed time: {}", - // c.getClass().getName(), - // (System.currentTimeMillis() - begin)); - // } - // } - // return true; - // } catch (Exception e) { - // return false; - // } - // } - // })); - // - // // 等待所有适配器写入完成 - // // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 - // for (Future f : futures) { - // try { - // if (!f.get()) { - // logger.error("Outer adapter write failed"); - // } - // } catch (InterruptedException | ExecutionException e) { - // // ignore - // } - // } - // } - // } + protected void writeOut(final FlatMessage flatMessage) { + List> futures = new ArrayList<>(); + // 组间适配器并行运行 + for (List outerAdapters : canalOuterAdapters) { + final List adapters = outerAdapters; + futures.add(groupInnerExecutorService.submit(new Callable() { + + @Override + public Boolean call() { + try { + // 组内适配器穿行运行,尽量不要配置组内适配器 + for (OuterAdapter c : adapters) { + long begin = System.currentTimeMillis(); + Dml dml = MessageUtil.flatMessage2Dml(flatMessage); + c.sync(dml); + if (logger.isDebugEnabled()) { + logger.debug("{} elapsed time: {}", + c.getClass().getName(), + (System.currentTimeMillis() - begin)); + } + } + return true; + } catch (Exception e) { + return false; + } + } + })); + + // 等待所有适配器写入完成 + // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 + for (Future f : futures) { + try { + if (!f.get()) { + logger.error("Outer adapter write failed"); + } + } catch (InterruptedException | ExecutionException e) { + // ignore + } + } + } + } protected void writeOut(Message message, String topic) { if (logger.isDebugEnabled()) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java new file mode 100644 index 00000000..01f52e8e --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -0,0 +1,148 @@ +package com.alibaba.otter.canal.adapter.launcher.loader; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.kafka.clients.consumer.CommitFailedException; +import org.apache.kafka.common.errors.WakeupException; + +import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; +import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors; +import com.alibaba.otter.canal.protocol.FlatMessage; +import com.alibaba.otter.canal.protocol.Message; + +/** + * kafka对应的client适配器工作线程 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ +public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { + + private KafkaCanalConnector connector; + + private String topic; + + private boolean flatMessage; + + public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId, + List> canalOuterAdapters, boolean flatMessage){ + this.canalOuterAdapters = canalOuterAdapters; + this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); + this.topic = topic; + this.canalDestination = topic; + this.flatMessage = flatMessage; + connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage); + // connector.setSessionTimeout(1L, TimeUnit.MINUTES); + + // super.initSwitcher(topic); + } + + @Override + public void start() { + if (!running) { + thread = new Thread(new Runnable() { + + @Override + public void run() { + process(); + } + }); + thread.setUncaughtExceptionHandler(handler); + running = true; + thread.start(); + } + } + + @Override + public void stop() { + try { + if (!running) { + return; + } + + connector.stopRunning(); + running = false; + + // if (switcher != null && !switcher.state()) { + // switcher.set(true); + // } + + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } + } + groupInnerExecutorService.shutdown(); + logger.info("topic {} connectors' worker thread dead!", this.topic); + for (List outerAdapters : canalOuterAdapters) { + for (OuterAdapter adapter : outerAdapters) { + adapter.destroy(); + } + } + logger.info("topic {} all connectors destroyed!", this.topic); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void process() { + while (!running) + ; + ExecutorService executor = Executors.newSingleThreadExecutor(); + // final AtomicBoolean executing = new AtomicBoolean(true); + while (running) { + try { + logger.info("=============> Start to connect topic: {} <=============", this.topic); + connector.connect(); + logger.info("=============> Start to subscribe topic: {} <=============", this.topic); + connector.subscribe(); + logger.info("=============> Subscribe topic: {} succeed <=============", this.topic); + while (running) { + try { + // switcher.get(); //等待开关开启 + + List messages; + if (!flatMessage) { + messages = connector.getWithoutAck(); + } else { + messages = connector.getFlatMessageWithoutAck(100L, TimeUnit.MILLISECONDS); + } + if (messages != null) { + for (final Object message : messages) { + if (message instanceof FlatMessage) { + writeOut((FlatMessage) message); + } else { + writeOut((Message) message); + } + } + } + connector.ack(); + } catch (CommitFailedException e) { + logger.warn(e.getMessage()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + TimeUnit.SECONDS.sleep(1L); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + executor.shutdown(); + + try { + connector.unsubscribe(); + } catch (WakeupException e) { + // No-op. Continue process + } + connector.disconnect(); + logger.info("=============> Disconnect topic: {} <=============", this.topic); + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 6e7d2bb8..d9cdaa7c 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -9,13 +9,14 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; -import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; +import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; /** * 外部适配器的加载器 @@ -24,13 +25,15 @@ import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; */ public class CanalAdapterLoader { - private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); + private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); - private CanalClientConfig canalClientConfig; + private CanalClientConfig canalClientConfig; - private Map canalWorkers = new HashMap<>(); + private Map canalWorkers = new HashMap<>(); - private ExtensionLoader loader; + private Map canalMQWorker = new HashMap<>(); + + private ExtensionLoader loader; public CanalAdapterLoader(CanalClientConfig canalClientConfig){ this.canalClientConfig = canalClientConfig; @@ -57,7 +60,7 @@ public class CanalAdapterLoader { for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { List canalOutConnectors = new ArrayList<>(); - for (CanalOuterAdapterConfiguration c : connectorGroup.getOutAdapters()) { + for (OuterAdapterConfig c : connectorGroup.getOutAdapters()) { loadConnector(c, canalOutConnectors); } canalOuterAdapterGroups.add(canalOutConnectors); @@ -73,9 +76,43 @@ public class CanalAdapterLoader { logger.info("Start adapter for canal instance: {} succeed", instance.getInstance()); } } + + // 初始化canal-client-mq的适配器 + if (canalClientConfig.getMqTopics() != null) { + for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) { + for (CanalClientConfig.Group group : topic.getGroups()) { + List> canalOuterAdapterGroups = new ArrayList<>(); + + List canalOuterAdapters = new ArrayList<>(); + + for (OuterAdapterConfig config : group.getOutAdapters()) { + loadConnector(config, canalOuterAdapters); + } + canalOuterAdapterGroups.add(canalOuterAdapters); + if (StringUtils.isBlank(topic.getMqMode()) || "rocketmq".equalsIgnoreCase(topic.getMqMode())) { + CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig + .getBootstrapServers(), topic.getTopic(), group.getGroupId(), canalOuterAdapterGroups); + canalMQWorker.put(topic.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); + rocketMQWorker.start(); + } else if ("kafka".equalsIgnoreCase(topic.getMqMode())) { + CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker( + canalClientConfig.getBootstrapServers(), + topic.getTopic(), + group.getGroupId(), + canalOuterAdapterGroups, + canalClientConfig.getFlatMessage()); + canalMQWorker.put(topic.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker); + canalKafkaWorker.start(); + } + logger.info("Start adapter for canal-client rocketmq topic: {} succeed", + topic.getTopic() + "-" + group.getGroupId()); + + } + } + } } - private void loadConnector(CanalOuterAdapterConfiguration config, List canalOutConnectors) { + private void loadConnector(OuterAdapterConfig config, List canalOutConnectors) { try { OuterAdapter adapter = loader.getExtension(config.getName()); ClassLoader cl = Thread.currentThread().getContextClassLoader(); @@ -98,10 +135,19 @@ public class CanalAdapterLoader { ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size()); for (CanalAdapterWorker v : canalWorkers.values()) { final CanalAdapterWorker caw = v; - stopExecutorService.submit(() -> caw.stop()); + stopExecutorService.submit(caw::stop); } stopExecutorService.shutdown(); } + + if (canalMQWorker.size() > 0) { + ExecutorService stopMQWokerService = Executors.newFixedThreadPool(canalMQWorker.size()); + for (AbstractCanalAdapterWorker tmp : canalMQWorker.values()) { + final AbstractCanalAdapterWorker worker = tmp; + stopMQWokerService.submit(worker::stop); + } + stopMQWokerService.shutdown(); + } logger.info("All canal adapters destroyed"); } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java new file mode 100644 index 00000000..aa963bbe --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -0,0 +1,124 @@ +package com.alibaba.otter.canal.adapter.launcher.loader; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.kafka.clients.consumer.CommitFailedException; +import org.apache.kafka.common.errors.WakeupException; + +import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector; +import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider; +import com.alibaba.otter.canal.protocol.Message; + +/** + * kafka对应的client适配器工作线程 + * + * @author machengyuan 2018-8-19 下午11:30:49 + * @version 1.0.0 + */ +public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { + + private RocketMQCanalConnector connector; + + private String topic; + + public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId, + List> canalOuterAdapters){ + logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); + this.canalOuterAdapters = canalOuterAdapters; + this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); + this.topic = topic; + this.canalDestination = topic; + connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId); + } + + @Override + public void start() { + if (!running) { + thread = new Thread(new Runnable() { + + @Override + public void run() { + process(); + } + }); + thread.setUncaughtExceptionHandler(handler); + running = true; + thread.start(); + } + } + + @Override + public void stop() { + try { + if (!running) { + return; + } + connector.stopRunning(); + running = false; + logger.info("Stop topic {} out adapters begin", this.topic); + stopOutAdapters(); + logger.info("Stop topic {} out adapters end", this.topic); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void process() { + while (!running) + ; + ExecutorService executor = Executors.newSingleThreadExecutor(); + while (running) { + try { + logger.info("=============> Start to connect topic: {} <=============", this.topic); + connector.connect(); + logger.info("=============> Start to subscribe topic: {}<=============", this.topic); + connector.subscribe(); + logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); + while (running) { + try { + // switcher.get(); //等待开关开启 + + final Message message = connector.getWithoutAck(1); + if (message != null) { + executor.submit(new Runnable() { + + @Override + public void run() { + try { + writeOut(message, topic); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + connector.ack(message.getId()); + } + }); + } else { + logger.debug("Message is null"); + } + } catch (CommitFailedException e) { + logger.warn(e.getMessage()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + TimeUnit.SECONDS.sleep(1L); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + executor.shutdown(); + + try { + connector.unsubscribe(); + } catch (WakeupException e) { + // No-op. Continue process + } + connector.stopRunning(); + logger.info("=============> Disconnect topic: {} <=============", this.topic); + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 1c38b353..224e70d4 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -59,13 +59,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { @Override public void start() { if (!running) { - thread = new Thread(new Runnable() { - - @Override - public void run() { - process(); - } - }); + thread = new Thread(() -> process()); thread.setUncaughtExceptionHandler(handler); thread.start(); running = true; diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 7adef833..af787a9f 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -1,20 +1,32 @@ server: port: 8081 +logging: + level: + com.alibaba.otter.canal.client.adapter.hbase: DEBUG + canal.conf: canalServerHost: 127.0.0.1:11111 - #zookeeperHosts: slave1:2181 - #bootstrapServers: localhost:9092 #or rocketmq nameservers:host1:9876;host2:9876 - flatMessage: false - +# zookeeperHosts: slave1:2181 +# bootstrapServers: slave1:6667 #or rocketmq nameservers:host1:9876;host2:9876 + flatMessage: true canalInstances: - instance: example adapterGroups: - outAdapters: - - name: logger -# - name: hbase -# hosts: slave1:2181 -# properties: {znodeParent: "/hbase-unsecure"} +# - name: logger + - name: hbase + properties: + hbase.zookeeper.quorum: slave1 + hbase.zookeeper.property.clientPort: 2181 + zookeeper.znode.parent: /hbase-unsecure +# mqTopics: +# - mqMode: kafka +# topic: example +# groups: +# - groupId: g2 +# outAdapters: +# - name: logger adapter.conf: datasourceConfigs: diff --git a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java index 27d99aae..985c7804 100644 --- a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java +++ b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java @@ -4,7 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.adapter.support.CanalOuterAdapterConfiguration; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.SPI; @@ -21,7 +21,7 @@ public class LoggerAdapterExample implements OuterAdapter { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Override - public void init(CanalOuterAdapterConfiguration configuration) { + public void init(OuterAdapterConfig configuration) { } From 49caff57599fe550cc55c30a65ed8dfe6f7b8dfb Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 24 Oct 2018 19:12:16 +0800 Subject: [PATCH 191/360] =?UTF-8?q?HBase=E7=9A=84etl=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/OuterAdapter.java | 5 ++- .../client/adapter/support/EtlResult.java | 37 ++++++++++++++++ .../client/adapter/hbase/HbaseAdapter.java | 14 ++++--- .../hbase/service/HbaseEtlService.java | 36 ++++++++-------- .../canal/adapter/launcher/rest/EtlRest.java | 42 +++++++++++++++++++ .../src/main/resources/application.yml | 6 ++- 6 files changed, 113 insertions(+), 27 deletions(-) create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/EtlRest.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index a508d1ef..3fef6c0e 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -2,8 +2,9 @@ package com.alibaba.otter.canal.client.adapter; import java.util.List; -import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.EtlResult; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.SPI; /** @@ -40,7 +41,7 @@ public interface OuterAdapter { * @param task 任务名, 对应配置名 * @param params etl筛选条件 */ - default void etl(String task, List params) { + default EtlResult etl(String task, List params) { throw new UnsupportedOperationException(); } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java new file mode 100644 index 00000000..0004e51a --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java @@ -0,0 +1,37 @@ +package com.alibaba.otter.canal.client.adapter.support; + +import java.io.Serializable; + +public class EtlResult implements Serializable { + private static final long serialVersionUID = 4250522736289866505L; + + private boolean succeeded = false; + + private String resultMessage; + + private String errorMessage; + + public boolean getSucceeded() { + return succeeded; + } + + public void setSucceeded(boolean succeeded) { + this.succeeded = succeeded; + } + + public String getResultMessage() { + return resultMessage; + } + + public void setResultMessage(String resultMessage) { + this.resultMessage = resultMessage; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} \ No newline at end of file diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index da51ac88..88c7fe22 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -18,10 +18,7 @@ import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService; import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate; -import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; -import com.alibaba.otter.canal.client.adapter.support.Dml; -import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; -import com.alibaba.otter.canal.client.adapter.support.SPI; +import com.alibaba.otter.canal.client.adapter.support.*; /** * HBase外部适配器 @@ -80,11 +77,16 @@ public class HbaseAdapter implements OuterAdapter { } @Override - public void etl(String task, List params) { + public EtlResult etl(String task, List params) { MappingConfig config = hbaseMapping.get(task); DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); if (dataSource != null) { - HbaseEtlService.importData(dataSource, hbaseTemplate, config, params); + return HbaseEtlService.importData(dataSource, hbaseTemplate, config, params); + } else { + EtlResult etlResult = new EtlResult(); + etlResult.setSucceeded(false); + etlResult.setErrorMessage("DataSource not found"); + return etlResult; } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index 2c919955..e2b86df1 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -20,6 +20,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.support.*; +import com.alibaba.otter.canal.client.adapter.support.EtlResult; import com.alibaba.otter.canal.client.adapter.support.JdbcTypeUtil; import com.google.common.base.Joiner; @@ -75,19 +76,19 @@ public class HbaseEtlService { } } - public static void importData(DataSource ds, HbaseTemplate hbaseTemplate, MappingConfig config, - List params) { - // EtlResult etlResult = new EtlResult(); + public static EtlResult importData(DataSource ds, HbaseTemplate hbaseTemplate, MappingConfig config, + List params) { + EtlResult etlResult = new EtlResult(); AtomicLong successCount = new AtomicLong(); List errMsg = new ArrayList<>(); String hbaseTable = ""; try { - // if (config == null) { - // logger.error("Config is null!"); - // etlResult.setSucceeded(false); - // etlResult.setErrorMessage("Config is null!"); - // return etlResult; - // } + if (config == null) { + logger.error("Config is null!"); + etlResult.setSucceeded(false); + etlResult.setErrorMessage("Config is null!"); + return etlResult; + } MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); hbaseTable = hbaseOrm.getHbaseTable(); @@ -158,7 +159,7 @@ public class HbaseEtlService { // 当大于1万条记录时开启多线程 if (cnt >= 10000) { - int threadCount = 3; // TODO 从配置读取默认为3 + int threadCount = 3; long perThreadCnt = cnt / threadCount; ExecutorService executor = Executors.newFixedThreadPool(threadCount); List> futures = new ArrayList<>(threadCount); @@ -191,19 +192,18 @@ public class HbaseEtlService { logger.info( hbaseOrm.getHbaseTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); - // etlResult.setResultMessage("导入HBase表 " + hbaseOrm.getHbaseTable() + " 数据:" + - // successCount.get() + " 条"); + etlResult.setResultMessage("导入HBase表 " + hbaseOrm.getHbaseTable() + " 数据:" + successCount.get() + " 条"); } catch (Exception e) { logger.error(e.getMessage(), e); errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage()); } - // if (errMsg.isEmpty()) { - // etlResult.setSucceeded(true); - // } else { - // etlResult.setErrorMessage(Joiner.on("\n").join(errMsg)); - // } - // return etlResult; + if (errMsg.isEmpty()) { + etlResult.setSucceeded(true); + } else { + etlResult.setErrorMessage(Joiner.on("\n").join(errMsg)); + } + return etlResult; } private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseOrm hbaseOrm, diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/EtlRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/EtlRest.java new file mode 100644 index 00000000..33b1d8f9 --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/EtlRest.java @@ -0,0 +1,42 @@ +package com.alibaba.otter.canal.adapter.launcher.rest; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.PostConstruct; + +import com.alibaba.otter.canal.client.adapter.support.EtlResult; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; + +@RestController +public class EtlRest { + + private ExtensionLoader loader; + + @PostConstruct + public void init() { + loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class); + } + + /** + * Demo: http://127.0.0.1:8081/etl/hbase/mytest_person2.yml + */ + @GetMapping("/etl/{type}/{task}") + public EtlResult etl(@PathVariable String type, @PathVariable String task, + @RequestParam(required = false) String params) { + OuterAdapter adapter = loader.getExtension(type); + List paramArr = null; + if (params != null) { + String[] parmaArray = params.trim().split(";"); + paramArr = Arrays.asList(parmaArray); + } + + return adapter.etl(task, paramArr); + } +} diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index af787a9f..7a319012 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -1,9 +1,13 @@ server: port: 8081 - logging: level: com.alibaba.otter.canal.client.adapter.hbase: DEBUG +spring: + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null canal.conf: canalServerHost: 127.0.0.1:11111 From 30a11c75cbaecfb290b5ae43df2191b82818d7aa Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 24 Oct 2018 20:47:25 +0800 Subject: [PATCH 192/360] =?UTF-8?q?HBase=E7=9A=84etl=E5=88=9D=E6=AD=A5?= =?UTF-8?q?=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/OuterAdapter.java | 12 ++++- .../canal/client/adapter/support/Result.java | 50 +++++++++++++++++++ client-adapter/example/pom.xml | 2 +- .../client/adapter/hbase/HbaseAdapter.java | 39 +++++++++++++-- .../hbase/config/MappingConfigLoader.java | 4 +- .../hbase/service/HbaseEtlService.java | 2 +- .../main/resources/hbase/mytest_person2.yml | 2 +- client-adapter/launcher/pom.xml | 2 +- .../rest/{EtlRest.java => CommonRest.java} | 25 ++++++---- .../src/main/resources/application.yml | 5 +- 10 files changed, 121 insertions(+), 22 deletions(-) create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java rename client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/{EtlRest.java => CommonRest.java} (62%) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index 3fef6c0e..bf6d5fce 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -1,6 +1,7 @@ package com.alibaba.otter.canal.client.adapter; import java.util.List; +import java.util.Map; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.EtlResult; @@ -42,7 +43,16 @@ public interface OuterAdapter { * @param params etl筛选条件 */ default EtlResult etl(String task, List params) { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("unsupported operation"); } + /** + * 计算总数 + * + * @param task 任务名, 对应配置名 + * @return 总数 + */ + default Map count(String task) { + throw new UnsupportedOperationException("unsupported operation"); + } } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java new file mode 100644 index 00000000..0ea913e1 --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java @@ -0,0 +1,50 @@ +package com.alibaba.otter.canal.client.adapter.support; + +import java.io.Serializable; +import java.util.Date; + +public class Result implements Serializable { + + public Integer code = 20000; + public Object data; + public String message; + public Date sysTime = new Date(); + + public static Result createSuccess(Object data) { + Result result = new Result(); + result.setData(data); + return result; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getSysTime() { + return sysTime; + } + + public void setSysTime(Date sysTime) { + this.sysTime = sysTime; + } +} diff --git a/client-adapter/example/pom.xml b/client-adapter/example/pom.xml index 3c68087a..7433e948 100644 --- a/client-adapter/example/pom.xml +++ b/client-adapter/example/pom.xml @@ -9,7 +9,7 @@ 4.0.0 com.alibaba.otter - example + client-adapter.example jar canal client adapter example module for otter ${project.version} diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 88c7fe22..97a7bd70 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.client.adapter.hbase; import java.io.IOException; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -9,8 +10,11 @@ import javax.sql.DataSource; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; -import org.apache.hadoop.hbase.client.Connection; -import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.*; +import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; @@ -29,12 +33,15 @@ import com.alibaba.otter.canal.client.adapter.support.*; @SPI("hbase") public class HbaseAdapter implements OuterAdapter { - private static volatile Map hbaseMapping = null; // 文件名对应配置 - private static volatile Map mappingConfigCache = null; // 库名-表名对应配置 + private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class); + + private static volatile Map hbaseMapping = null; // 文件名对应配置 + private static volatile Map mappingConfigCache = null; // 库名-表名对应配置 private Connection conn; private HbaseSyncService hbaseSyncService; private HbaseTemplate hbaseTemplate; + private Configuration hbaseConfig; @Override public void init(OuterAdapterConfig configuration) { @@ -55,7 +62,7 @@ public class HbaseAdapter implements OuterAdapter { Map propertites = configuration.getProperties(); - Configuration hbaseConfig = HBaseConfiguration.create(); + hbaseConfig = HBaseConfiguration.create(); propertites.forEach(hbaseConfig::set); conn = ConnectionFactory.createConnection(hbaseConfig); hbaseTemplate = new HbaseTemplate(conn); @@ -90,6 +97,28 @@ public class HbaseAdapter implements OuterAdapter { } } + @Override + public Map count(String task) { + MappingConfig config = hbaseMapping.get(task); + String hbaseTable = config.getHbaseOrm().getHbaseTable(); + long rowCount = 0L; + try { + HTable table = new HTable(hbaseConfig, hbaseTable); + Scan scan = new Scan(); + scan.setFilter(new FirstKeyOnlyFilter()); + ResultScanner resultScanner = table.getScanner(scan); + for (Result result : resultScanner) { + rowCount += result.size(); + } + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + Map res = new LinkedHashMap<>(); + res.put("hbaseTable", hbaseTable); + res.put("count", rowCount); + return res; + } + @Override public void destroy() { if (conn != null) { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 173fc023..0fb727d8 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -72,6 +72,7 @@ public class MappingConfigLoader { String[] dbTable; if (dsKey == null) { dbTable = srcMeta.split("\\."); + } else { dbTable = srcMeta.split("@")[0].split("\\."); } @@ -84,7 +85,7 @@ public class MappingConfigLoader { hbaseOrm.setAutoCreateTable(true); hbaseOrm.setDatabase(dbTable[0]); hbaseOrm.setTable(dbTable[1]); - hbaseOrm.setMode(MappingConfig.Mode.STRING); + hbaseOrm.setMode(MappingConfig.Mode.PHOENIX); hbaseOrm.setRowKey(rowKey); // 有定义rowKey if (rowKey != null) { @@ -94,6 +95,7 @@ public class MappingConfigLoader { hbaseOrm.setRowKeyColumn(columnItem); } config.setHbaseOrm(hbaseOrm); + config.setDataSourceKey(dsKey); } else { throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c)); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index e2b86df1..6b955096 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -68,7 +68,7 @@ public class HbaseEtlService { // 判断hbase表是否存在,不存在则建表 MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); if (!hbaseTemplate.tableExists(hbaseOrm.getHbaseTable())) { - hbaseTemplate.createTable(hbaseOrm.getHbaseTable(), hbaseOrm.getFamilies().toArray(new String[0])); + hbaseTemplate.createTable(hbaseOrm.getHbaseTable(), hbaseOrm.getFamily()); } } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml index f2b6a594..cbf14d48 100644 --- a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml +++ b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml @@ -3,7 +3,7 @@ hbaseOrm: mode: PHOENIX #NATIVE #STRING database: mytest # 数据库名 table: person2 # 数据库表名 - hbaseTable: MYTEST_PERSON2 # HBase表名 + hbaseTable: MYTEST.PERSON2 # HBase表名 family: CF # 默认统一Family名称 uppercaseQualifier: true # 字段名转大写, 默认为true commitBatch: 3000 # 批量提交的大小 diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index 2f268088..d5e43708 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -9,7 +9,7 @@ 4.0.0 com.alibaba.otter - launcher + client-adapter.launcher jar canal client adapter launcher module for otter ${project.version} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/EtlRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java similarity index 62% rename from client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/EtlRest.java rename to client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index 33b1d8f9..388f42cd 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/EtlRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -2,20 +2,18 @@ package com.alibaba.otter.canal.adapter.launcher.rest; import java.util.Arrays; import java.util.List; +import java.util.Map; import javax.annotation.PostConstruct; -import com.alibaba.otter.canal.client.adapter.support.EtlResult; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.adapter.support.EtlResult; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; @RestController -public class EtlRest { +public class CommonRest { private ExtensionLoader loader; @@ -25,9 +23,9 @@ public class EtlRest { } /** - * Demo: http://127.0.0.1:8081/etl/hbase/mytest_person2.yml + * Demo: POST: http://127.0.0.1:8081/etl/hbase/mytest_person2.yml */ - @GetMapping("/etl/{type}/{task}") + @PostMapping("/etl/{type}/{task}") public EtlResult etl(@PathVariable String type, @PathVariable String task, @RequestParam(required = false) String params) { OuterAdapter adapter = loader.getExtension(type); @@ -37,6 +35,15 @@ public class EtlRest { paramArr = Arrays.asList(parmaArray); } - return adapter.etl(task, paramArr); + return adapter.etl(task, paramArr); + } + + /** + * Demo: GET: http://127.0.0.1:8081/count/hbase/mytest_person2.yml + */ + @GetMapping("/count/{type}/{task}") + public Map count(@PathVariable String type, @PathVariable String task) { + OuterAdapter adapter = loader.getExtension(type); + return adapter.count(task); } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 7a319012..b61a4749 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -18,7 +18,7 @@ canal.conf: - instance: example adapterGroups: - outAdapters: -# - name: logger + - name: logger - name: hbase properties: hbase.zookeeper.quorum: slave1 @@ -39,4 +39,5 @@ adapter.conf: username: root password: 121212 adapterConfigs: - - hbase/mytest_person2.yml \ No newline at end of file + - hbase/mytest_person2.yml +# - hbase/mytest.person2@defaultDS From 8dbf63e4364bcb64b63627ef0632549d10965b6e Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 10:05:05 +0800 Subject: [PATCH 193/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0adapter=20assembly?= =?UTF-8?q?=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/example/pom.xml | 1 + client-adapter/hbase/pom.xml | 19 ----------- .../client/adapter/hbase/HbaseAdapter.java | 6 ++-- .../adapter/hbase/support/TypeUtil.java | 4 +-- client-adapter/launcher/assembly.xml | 34 +++++++++++++++++++ client-adapter/launcher/pom.xml | 20 ++++++++++- 6 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 client-adapter/launcher/assembly.xml diff --git a/client-adapter/example/pom.xml b/client-adapter/example/pom.xml index 7433e948..f21649db 100644 --- a/client-adapter/example/pom.xml +++ b/client-adapter/example/pom.xml @@ -55,6 +55,7 @@ maven-jar-plugin + 3.0.2 true diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml index 866bd31f..a48d7401 100644 --- a/client-adapter/hbase/pom.xml +++ b/client-adapter/hbase/pom.xml @@ -84,25 +84,6 @@ - - org.apache.maven.plugins - maven-assembly-plugin - 2.4 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 97a7bd70..88fa9a4a 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -10,6 +10,7 @@ import javax.sql.DataSource; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; +import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.*; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter; @@ -41,7 +42,6 @@ public class HbaseAdapter implements OuterAdapter { private Connection conn; private HbaseSyncService hbaseSyncService; private HbaseTemplate hbaseTemplate; - private Configuration hbaseConfig; @Override public void init(OuterAdapterConfig configuration) { @@ -62,7 +62,7 @@ public class HbaseAdapter implements OuterAdapter { Map propertites = configuration.getProperties(); - hbaseConfig = HBaseConfiguration.create(); + Configuration hbaseConfig = HBaseConfiguration.create(); propertites.forEach(hbaseConfig::set); conn = ConnectionFactory.createConnection(hbaseConfig); hbaseTemplate = new HbaseTemplate(conn); @@ -103,7 +103,7 @@ public class HbaseAdapter implements OuterAdapter { String hbaseTable = config.getHbaseOrm().getHbaseTable(); long rowCount = 0L; try { - HTable table = new HTable(hbaseConfig, hbaseTable); + HTable table = (HTable) conn.getTable(TableName.valueOf(hbaseTable)); Scan scan = new Scan(); scan.setFilter(new FirstKeyOnlyFilter()); ResultScanner resultScanner = table.getScanner(scan); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java index 07aad00f..f90e5bd8 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java @@ -92,6 +92,7 @@ public class TypeUtil { return b; } + @SuppressWarnings("unchecked") public static T toObject(byte[] bytes, Class clazz) { if (bytes == null) { return null; @@ -132,10 +133,10 @@ public class TypeUtil { } else { throw new IllegalArgumentException("mismatch class type"); } - // noinspection unchecked return (T) res; } + @SuppressWarnings("unchecked") public static T toObject(byte[] bytes, Type type) { if (bytes == null) { return null; @@ -182,7 +183,6 @@ public class TypeUtil { } else { throw new IllegalArgumentException("mismatch class type"); } - // noinspection unchecked return (T) res; } } diff --git a/client-adapter/launcher/assembly.xml b/client-adapter/launcher/assembly.xml new file mode 100644 index 00000000..7bb12314 --- /dev/null +++ b/client-adapter/launcher/assembly.xml @@ -0,0 +1,34 @@ + + canal-adapter + + tar.gz + + + + ${project.build.directory} + / + + *.jar + + + + + ${project.build.directory}/lib + /lib/ + + *.jar + + + + + ${project.build.directory}/config + /config/ + + ** + + + + / + \ No newline at end of file diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index d5e43708..7d54cd58 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -93,7 +93,7 @@ - canal-client-launcher + canal-adapter-launcher org.springframework.boot @@ -145,6 +145,24 @@ + + maven-assembly-plugin + + false + + assembly.xml + + + + + make-assembly + package + + single + + + + \ No newline at end of file From 27dba9ec1bf921eddc6f2dff9f9a8b5cd797032b Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 10:52:55 +0800 Subject: [PATCH 194/360] =?UTF-8?q?HBase=E6=A0=B9=E6=8D=AEdatasourceKey?= =?UTF-8?q?=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/adapter/hbase/HbaseAdapter.java | 47 ++++++++++++++++--- .../hbase/service/HbaseEtlService.java | 9 ++-- .../adapter/launcher/rest/CommonRest.java | 6 +-- 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 88fa9a4a..22731960 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -85,16 +85,49 @@ public class HbaseAdapter implements OuterAdapter { @Override public EtlResult etl(String task, List params) { + EtlResult etlResult = new EtlResult(); MappingConfig config = hbaseMapping.get(task); - DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); - if (dataSource != null) { - return HbaseEtlService.importData(dataSource, hbaseTemplate, config, params); + if (config != null) { + DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); + if (dataSource != null) { + return HbaseEtlService.importData(dataSource, hbaseTemplate, config, params); + } else { + etlResult.setSucceeded(false); + etlResult.setErrorMessage("DataSource not found"); + return etlResult; + } } else { - EtlResult etlResult = new EtlResult(); - etlResult.setSucceeded(false); - etlResult.setErrorMessage("DataSource not found"); - return etlResult; + DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(task); + if (dataSource != null) { + StringBuilder resultMsg = new StringBuilder(); + boolean resSucc = true; + // ds不为空说明传入的是datasourceKey + for (MappingConfig configTmp : hbaseMapping.values()) { + // 取所有的datasourceKey为task的配置 + if (configTmp.getDataSourceKey().equals(task)) { + EtlResult etlRes = HbaseEtlService.importData(dataSource, hbaseTemplate, configTmp, params); + if (!etlRes.getSucceeded()) { + resSucc = false; + resultMsg.append(etlRes.getErrorMessage()).append("\n"); + } else { + resultMsg.append(etlRes.getResultMessage()).append("\n"); + } + } + } + if (resultMsg.length() > 0) { + etlResult.setSucceeded(resSucc); + if (resSucc) { + etlResult.setResultMessage(resultMsg.toString()); + } else { + etlResult.setErrorMessage(resultMsg.toString()); + } + return etlResult; + } + } } + etlResult.setSucceeded(false); + etlResult.setErrorMessage("Task not found"); + return etlResult; } @Override diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index 6b955096..d40278f0 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -323,12 +323,11 @@ public class HbaseEtlService { rows.clear(); complete = true; } - if (logger.isDebugEnabled()) { - logger.debug("import count:" + i); - } - // System.out.println(i); i++; successCount.incrementAndGet(); + if (logger.isDebugEnabled()) { + logger.debug("successful import count:" + successCount.get()); + } } if (!complete && !rows.isEmpty()) { @@ -338,7 +337,7 @@ public class HbaseEtlService { } catch (Exception e) { logger.error(hbaseOrm.getHbaseTable() + " etl failed! ==>" + e.getMessage(), e); errMsg.add(hbaseOrm.getHbaseTable() + " etl failed! ==>" + e.getMessage()); - throw new RuntimeException(e); + // throw new RuntimeException(e); } return i; }); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index 388f42cd..c326c574 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -23,11 +23,11 @@ public class CommonRest { } /** - * Demo: POST: http://127.0.0.1:8081/etl/hbase/mytest_person2.yml + * Demo: curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST -d "params=0,1,2" */ @PostMapping("/etl/{type}/{task}") public EtlResult etl(@PathVariable String type, @PathVariable String task, - @RequestParam(required = false) String params) { + @RequestParam(name = "params", required = false) String params) { OuterAdapter adapter = loader.getExtension(type); List paramArr = null; if (params != null) { @@ -39,7 +39,7 @@ public class CommonRest { } /** - * Demo: GET: http://127.0.0.1:8081/count/hbase/mytest_person2.yml + * Demo: curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml */ @GetMapping("/count/{type}/{task}") public Map count(@PathVariable String type, @PathVariable String task) { From 346d091d003a8b604ae2a462bc88bcabc4a57317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Thu, 25 Oct 2018 13:11:34 +0800 Subject: [PATCH 195/360] fixed issue #1032 , support raw config --- deployer/src/main/resources/canal.properties | 1 + .../resources/spring/default-instance.xml | 1 + .../main/resources/spring/file-instance.xml | 1 + .../main/resources/spring/group-instance.xml | 1 + .../main/resources/spring/memory-instance.xml | 1 + .../manager/CanalInstanceWithManager.java | 1 + .../manager/model/CanalParameter.java | 9 +++++++++ .../otter/canal/kafka/CanalKafkaProducer.java | 19 ++++++++----------- .../otter/canal/server/CanalMQStarter.java | 5 +++++ 9 files changed, 28 insertions(+), 11 deletions(-) diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index ea096de8..2a24b66f 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -20,6 +20,7 @@ canal.instance.memory.buffer.size = 16384 canal.instance.memory.buffer.memunit = 1024 ## meory store gets mode used MEMSIZE or ITEMSIZE canal.instance.memory.batch.mode = MEMSIZE +canal.instance.memory.rawEntry = true ## detecing config canal.instance.detecting.enable = false diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index 95ec6a29..fe9cc7a5 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -58,6 +58,7 @@ + diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index df30174a..23d8e4bd 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -44,6 +44,7 @@ + diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index 71788f66..e614b86c 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -41,6 +41,7 @@ + diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index 751e9f4b..f785a46e 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -41,6 +41,7 @@ + diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index 36774d94..dff18278 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -147,6 +147,7 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { memoryEventStore.setBufferMemUnit(parameters.getMemoryStorageBufferMemUnit()); memoryEventStore.setBatchMode(BatchMode.valueOf(parameters.getStorageBatchMode().name())); memoryEventStore.setDdlIsolation(parameters.getDdlIsolation()); + memoryEventStore.setRaw(parameters.getMemoryStorageRawEntry()); eventStore = memoryEventStore; } else if (mode.isFile()) { // 后续版本支持 diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java index ece1196e..ca5e1af7 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java @@ -39,6 +39,7 @@ public class CanalParameter implements Serializable { private BatchMode storageBatchMode = BatchMode.MEMSIZE; // 基于大小返回结果 private Integer memoryStorageBufferSize = 16 * 1024; // 内存存储的buffer大小 private Integer memoryStorageBufferMemUnit = 1024; // 内存存储的buffer内存占用单位,默认为1kb + private Boolean memoryStorageRawEntry = Boolean.TRUE; // 内存存储的对象是否启用raw的ByteString模式 private String fileStorageDirectory; // 文件存储的目录位置 private Integer fileStorageStoreCount; // 每个文件store存储的记录数 private Integer fileStorageRollverCount; // store文件的个数 @@ -955,6 +956,14 @@ public class CanalParameter implements Serializable { this.gtidEnable = gtidEnable; } + public Boolean getMemoryStorageRawEntry() { + return memoryStorageRawEntry; + } + + public void setMemoryStorageRawEntry(Boolean memoryStorageRawEntry) { + this.memoryStorageRawEntry = memoryStorageRawEntry; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index 93f5f4ab..654a4b6b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -1,24 +1,21 @@ package com.alibaba.otter.canal.kafka; -import java.util.ArrayList; import java.util.List; import java.util.Properties; -import java.util.concurrent.Future; -import com.alibaba.fastjson.serializer.SerializerFeature; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; -import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.common.serialization.StringSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.Message; -import com.alibaba.otter.canal.spi.CanalMQProducer;; +import com.alibaba.otter.canal.spi.CanalMQProducer; /** * kafka producer 主操作类 @@ -104,8 +101,10 @@ public class CanalKafkaProducer implements CanalMQProducer { for (FlatMessage flatMessage : flatMessages) { if (canalDestination.getPartition() != null) { try { - ProducerRecord record = new ProducerRecord(canalDestination - .getTopic(), canalDestination.getPartition(), null, JSON.toJSONString(flatMessage)); + ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), + canalDestination.getPartition(), + null, + JSON.toJSONString(flatMessage)); producer2.send(record); } catch (Exception e) { logger.error(e.getMessage(), e); @@ -123,8 +122,7 @@ public class CanalKafkaProducer implements CanalMQProducer { FlatMessage flatMessagePart = partitionFlatMessage[i]; if (flatMessagePart != null) { try { - ProducerRecord record = new ProducerRecord( - canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), i, null, JSON.toJSONString(flatMessagePart)); @@ -138,8 +136,7 @@ public class CanalKafkaProducer implements CanalMQProducer { } } else { try { - ProducerRecord record = new ProducerRecord( - canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), 0, null, JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index b3ed906f..413c42b2 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -57,6 +57,11 @@ public class CanalMQStarter { System.setProperty("canal.instance.filter.transaction.entry", "true"); } + if (properties.getFlatMessage()) { + // 针对flat message模式,设置为raw避免ByteString->Entry的二次解析 + System.setProperty("canal.instance.memory.rawEntry", "false"); + } + // 对应每个instance启动一个worker线程 List destinations = properties.getCanalDestinations(); From 0f1334c92c8ac1f3753a8a6f9934d4570000a3a3 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 13:52:52 +0800 Subject: [PATCH 196/360] =?UTF-8?q?=E5=8A=A0=E5=85=A5=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/CanalClientConfig.java | 10 +- .../canal/client/adapter/support/Dml.java | 14 ++- .../client/adapter/support/MessageUtil.java | 22 ++-- .../canal/client/adapter/support/Result.java | 6 +- .../loader/AbstractCanalAdapterWorker.java | 4 +- .../hbase/service/HbaseEtlService.java | 15 +-- .../adapter/launcher/common/SyncSwitch.java | 102 ++++++++++++++++++ .../launcher/config/AdapterCanalConfig.java | 37 ++++++- .../launcher/config/SpringContext.java | 28 +++++ .../loader/AbstractCanalAdapterWorker.java | 24 ++--- .../loader/CanalAdapterKafkaWorker.java | 8 +- .../loader/CanalAdapterRocketMQWorker.java | 10 +- .../launcher/loader/CanalAdapterService.java | 8 +- .../launcher/loader/CanalAdapterWorker.java | 30 +++--- .../adapter/launcher/rest/CommonRest.java | 70 +++++++++++- .../src/main/resources/application.yml | 11 +- 16 files changed, 323 insertions(+), 76 deletions(-) create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index d7f7582a..08246a42 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -62,6 +62,10 @@ public class CanalClientConfig { return mqTopics; } + public void setMqTopics(List mqTopics) { + this.mqTopics = mqTopics; + } + public Boolean getFlatMessage() { return flatMessage; } @@ -70,10 +74,6 @@ public class CanalClientConfig { this.flatMessage = flatMessage; } - public void setMqTopics(List mqTopics) { - this.mqTopics = mqTopics; - } - public List getCanalInstances() { return canalInstances; } @@ -155,7 +155,7 @@ public class CanalClientConfig { public static class Group { - private String groupId; + private String groupId; // private List adapters = new ArrayList<>(); diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java index d4cbbf7d..8e5af059 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java @@ -14,6 +14,7 @@ public class Dml implements Serializable { private static final long serialVersionUID = 2611556444074013268L; + private String destination; private String database; private String table; private String type; @@ -25,6 +26,14 @@ public class Dml implements Serializable { private List> data; private List> old; + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + public String getDatabase() { return database; } @@ -102,7 +111,8 @@ public class Dml implements Serializable { @Override public String toString() { - return "Dml [database=" + database + ", table=" + table + ", type=" + type + ", es=" + es + ", ts=" + ts - + ", sql=" + sql + ", data=" + data + ", old=" + old + "]"; + return "Dml{" + "destination='" + destination + '\'' + ", database='" + database + '\'' + ", table='" + table + + '\'' + ", type='" + type + '\'' + ", es=" + es + ", ts=" + ts + ", sql='" + sql + '\'' + ", data=" + + data + ", old=" + old + '}'; } } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index 02723b41..826ed917 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -1,11 +1,6 @@ package com.alibaba.otter.canal.client.adapter.support; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -19,7 +14,7 @@ import com.alibaba.otter.canal.protocol.Message; */ public class MessageUtil { - public static void parse4Dml(Message message, Consumer consumer) { + public static void parse4Dml(String destination, Message message, Consumer consumer) { if (message == null) { return; } @@ -42,6 +37,7 @@ public class MessageUtil { CanalEntry.EventType eventType = rowChange.getEventType(); final Dml dml = new Dml(); + dml.setDestination(destination); dml.setDatabase(entry.getHeader().getSchemaName()); dml.setTable(entry.getHeader().getTableName()); dml.setType(eventType.toString()); @@ -87,10 +83,11 @@ public class MessageUtil { Map rowOld = new LinkedHashMap<>(); for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { if (updateSet.contains(column.getName())) { - rowOld.put(column.getName(), JdbcTypeUtil.typeConvert(column.getName(), - column.getValue(), - column.getSqlType(), - column.getMysqlType())); + rowOld.put(column.getName(), + JdbcTypeUtil.typeConvert(column.getName(), + column.getValue(), + column.getSqlType(), + column.getMysqlType())); } } // update操作将记录修改前的值 @@ -110,11 +107,12 @@ public class MessageUtil { } } - public static Dml flatMessage2Dml(FlatMessage flatMessage) { + public static Dml flatMessage2Dml(String destination, FlatMessage flatMessage) { if (flatMessage == null) { return null; } Dml dml = new Dml(); + dml.setDestination(destination); dml.setDatabase(flatMessage.getDatabase()); dml.setTable(flatMessage.getTable()); dml.setType(flatMessage.getType()); diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java index 0ea913e1..03831f1f 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java @@ -8,11 +8,11 @@ public class Result implements Serializable { public Integer code = 20000; public Object data; public String message; - public Date sysTime = new Date(); + public Date sysTime; - public static Result createSuccess(Object data) { + public static Result createSuccess(String message) { Result result = new Result(); - result.setData(data); + result.setMessage(message); return result; } diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java index cd3f718d..bbfd462d 100644 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java @@ -52,7 +52,7 @@ public abstract class AbstractCanalAdapterWorker { // 组内适配器穿行运行,尽量不要配置组内适配器 for (final OuterAdapter c : adapters) { long begin = System.currentTimeMillis(); - MessageUtil.parse4Dml(message, new MessageUtil.Consumer() { + MessageUtil.parse4Dml(canalDestination, message, new MessageUtil.Consumer() { @Override public void accept(Dml dml) { @@ -100,7 +100,7 @@ public abstract class AbstractCanalAdapterWorker { // 组内适配器穿行运行,尽量不要配置组内适配器 for (OuterAdapter c : adapters) { long begin = System.currentTimeMillis(); - Dml dml = MessageUtil.flatMessage2Dml(flatMessage); + Dml dml = MessageUtil.flatMessage2Dml(canalDestination, flatMessage); c.sync(dml); if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index d40278f0..ee110755 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -30,13 +30,13 @@ public class HbaseEtlService { public static Object sqlRS(DataSource ds, String sql, Function fun) throws SQLException { Connection conn = null; - Statement smt = null; + Statement stmt = null; ResultSet rs = null; try { conn = ds.getConnection(); - smt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - smt.setFetchSize(Integer.MIN_VALUE); - rs = smt.executeQuery(sql); + stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + stmt.setFetchSize(Integer.MIN_VALUE); + rs = stmt.executeQuery(sql); return fun.apply(rs); } finally { if (rs != null) { @@ -46,9 +46,9 @@ public class HbaseEtlService { logger.error(e.getMessage(), e); } } - if (smt != null) { + if (stmt != null) { try { - smt.close(); + stmt.close(); } catch (SQLException e) { logger.error(e.getMessage(), e); } @@ -60,6 +60,9 @@ public class HbaseEtlService { logger.error(e.getMessage(), e); } } + rs = null; + stmt = null; + conn = null; } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java new file mode 100644 index 00000000..2a2f1220 --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java @@ -0,0 +1,102 @@ +package com.alibaba.otter.canal.adapter.launcher.common; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +import org.apache.commons.lang.StringUtils; +import org.springframework.stereotype.Component; + +import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; +import com.alibaba.otter.canal.common.utils.BooleanMutex; + +@Component +public class SyncSwitch { + + private static final Map LOCAL_LOCK = new ConcurrentHashMap<>(); + + private Mode mode = Mode.LOCAL; + + @Resource + private AdapterCanalConfig adapterCanalConfig; + + @PostConstruct + public void init() { + if (StringUtils.isEmpty(adapterCanalConfig.getZookeeperHosts())) { + mode = Mode.LOCAL; + LOCAL_LOCK.clear(); + for (String destination : AdapterCanalConfig.DESTINATIONS) { + // 对应每个destination注册锁 + LOCAL_LOCK.put(destination, new BooleanMutex(true)); + } + } else { + mode = Mode.DISTRIBUTED; + } + } + + public synchronized void off(String destination) { + if (mode == Mode.LOCAL) { + BooleanMutex mutex = LOCAL_LOCK.get(destination); + if (mutex != null && mutex.state()) { + mutex.set(false); + } + } + } + + public synchronized void on(String destination) { + if (mode == Mode.LOCAL) { + BooleanMutex mutex = LOCAL_LOCK.get(destination); + if (mutex != null && !mutex.state()) { + mutex.set(true); + } + } + } + + public synchronized void release(String destination) { + if (mode == Mode.LOCAL) { + BooleanMutex mutex = LOCAL_LOCK.get(destination); + if (mutex != null && mutex.state()) { + mutex.set(false); + } + } + } + + public Boolean status(String destination) { + if (mode == Mode.LOCAL) { + BooleanMutex mutex = LOCAL_LOCK.get(destination); + if (mutex != null) { + return mutex.state(); + } else { + return null; + } + } + return null; + } + + public void get(String destination) throws InterruptedException { + if (mode == Mode.LOCAL) { + BooleanMutex mutex = LOCAL_LOCK.get(destination); + if (mutex != null) { + mutex.get(); + } + } + } + + public void get(String destination, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException { + if (mode == Mode.LOCAL) { + BooleanMutex mutex = LOCAL_LOCK.get(destination); + if (mutex != null) { + mutex.get(timeout, unit); + } + } + } + + enum Mode { + LOCAL, // 本地模式 + DISTRIBUTED // 分布式 + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index 447f896d..3dcfe4e7 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -1,10 +1,45 @@ package com.alibaba.otter.canal.adapter.launcher.config; -import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; + @Component @ConfigurationProperties(prefix = "canal.conf") public class AdapterCanalConfig extends CanalClientConfig { + + public static final Set DESTINATIONS = new LinkedHashSet<>(); + + @Override + public void setCanalInstances(List canalInstances) { + super.setCanalInstances(canalInstances); + + if (canalInstances != null) { + synchronized (DESTINATIONS) { + DESTINATIONS.clear(); + for (CanalInstance canalInstance : canalInstances) { + DESTINATIONS.add(canalInstance.getInstance()); + } + } + } + } + + @Override + public void setMqTopics(List mqTopics) { + super.setMqTopics(mqTopics); + + if (mqTopics != null) { + synchronized (DESTINATIONS) { + DESTINATIONS.clear(); + for (MQTopic mqTopic : mqTopics) { + DESTINATIONS.add(mqTopic.getTopic()); + } + } + } + } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java new file mode 100644 index 00000000..ac860bea --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java @@ -0,0 +1,28 @@ +package com.alibaba.otter.canal.adapter.launcher.config; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; + +@Component +public class SpringContext implements ApplicationContextAware { + + private static ApplicationContext context; + + /* + * 注入ApplicationContext + */ + public void setApplicationContext(final ApplicationContext context) throws BeansException { + // 在加载Spring时自动获得context + SpringContext.context = context; + } + + public static Object getBean(final String beanName) { + return SpringContext.context.getBean(beanName); + } + + public static Object getBean(final Class clz) { + return context.getBean(clz); + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index e007faa0..bd3a47a4 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -7,6 +7,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; +import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; +import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.protocol.FlatMessage; import org.slf4j.Logger; @@ -33,6 +35,12 @@ public abstract class AbstractCanalAdapterWorker { protected Thread thread = null; protected Thread.UncaughtExceptionHandler handler = (t, e) -> logger.error("parse events has an error", e); + protected SyncSwitch syncSwitch; + + public AbstractCanalAdapterWorker(){ + syncSwitch = (SyncSwitch) SpringContext.getBean(SyncSwitch.class); + } + protected void writeOut(final Message message) { List> futures = new ArrayList<>(); // 组间适配器并行运行 @@ -43,7 +51,7 @@ public abstract class AbstractCanalAdapterWorker { // 组内适配器穿行运行,尽量不要配置组内适配器 for (final OuterAdapter c : adapters) { long begin = System.currentTimeMillis(); - MessageUtil.parse4Dml(message, c::sync); + MessageUtil.parse4Dml(canalDestination, message, c::sync); if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", @@ -84,7 +92,7 @@ public abstract class AbstractCanalAdapterWorker { // 组内适配器穿行运行,尽量不要配置组内适配器 for (OuterAdapter c : adapters) { long begin = System.currentTimeMillis(); - Dml dml = MessageUtil.flatMessage2Dml(flatMessage); + Dml dml = MessageUtil.flatMessage2Dml(canalDestination, flatMessage); c.sync(dml); if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", @@ -113,18 +121,6 @@ public abstract class AbstractCanalAdapterWorker { } } - protected void writeOut(Message message, String topic) { - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId: {} batchSize: {} ", topic, message.getId(), message.getEntries().size()); - } - long begin = System.currentTimeMillis(); - writeOut(message); - long now = System.currentTimeMillis(); - if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { - logger.error("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); - } - } - protected void stopOutAdapters() { if (thread != null) { try { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 01f52e8e..22272e80 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -44,13 +44,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { @Override public void start() { if (!running) { - thread = new Thread(new Runnable() { - - @Override - public void run() { - process(); - } - }); + thread = new Thread(() -> process()); thread.setUncaughtExceptionHandler(handler); running = true; thread.start(); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index aa963bbe..09863a33 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -89,7 +89,15 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { @Override public void run() { try { - writeOut(message, topic); + if (logger.isDebugEnabled()) { + logger.debug("topic: {} batchId: {} batchSize: {} ", topic, message.getId(), message.getEntries().size()); + } + long begin = System.currentTimeMillis(); + writeOut(message); + long now = System.currentTimeMillis(); + if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { + logger.error("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); + } } catch (Exception e) { logger.error(e.getMessage(), e); } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java index f7b9e445..03f515bf 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java @@ -4,6 +4,8 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; +import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; +import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -23,9 +25,13 @@ public class CanalAdapterService { @Resource private AdapterCanalConfig adapterCanalConfig; - // 注入配置保证配置优先先注册 + // 注入bean保证优先注册 @Resource private AdapterConfig adapterConfig; + @Resource + private SpringContext springContext; + @Resource + private SyncSwitch syncSwitch; @PostConstruct public void init() { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 224e70d4..b976bb82 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -3,6 +3,8 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.net.SocketAddress; import java.util.List; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; @@ -59,7 +61,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { @Override public void start() { if (!running) { - thread = new Thread(() -> process()); + thread = new Thread(this::process); thread.setUncaughtExceptionHandler(handler); thread.start(); running = true; @@ -73,13 +75,11 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { return; } - // if (switcher != null && !switcher.state()) { - // switcher.set(true); - // } - connector.stopRunning(); running = false; + syncSwitch.release(canalDestination); + logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination); if (thread != null) { try { @@ -106,22 +106,22 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { ; // waiting until running == true while (running) { try { - // if (switcher != null) { - // switcher.get(); - // } + syncSwitch.get(canalDestination); + logger.info("=============> Start to connect destination: {} <=============", this.canalDestination); connector.connect(); logger.info("=============> Start to subscribe destination: {} <=============", this.canalDestination); connector.subscribe(); logger.info("=============> Subscribe destination: {} succeed <=============", this.canalDestination); while (running) { - // try { - // if (switcher != null) { - // switcher.get(); - // } - // } catch (TimeoutException e) { - // break; - // } + try { + syncSwitch.get(canalDestination, 1L, TimeUnit.MINUTES); + } catch (TimeoutException e) { + break; + } + if (!running) { + break; + } // server配置canal.instance.network.soTimeout(默认: 30s) // 范围内未与server交互,server将关闭本次socket连接 diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index c326c574..4e4aa7cb 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -1,29 +1,39 @@ package com.alibaba.otter.canal.adapter.launcher.rest; -import java.util.Arrays; -import java.util.List; -import java.util.Map; +import java.util.*; import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; +import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; +import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.EtlResult; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; +import com.alibaba.otter.canal.client.adapter.support.Result; @RestController public class CommonRest { + private static Logger logger = LoggerFactory.getLogger(CommonRest.class); + private ExtensionLoader loader; + @Resource + private SyncSwitch syncSwitch; + @PostConstruct public void init() { loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class); } /** - * Demo: curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST -d "params=0,1,2" + * Demo: curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST -d + * "params=0,1,2" */ @PostMapping("/etl/{type}/{task}") public EtlResult etl(@PathVariable String type, @PathVariable String task, @@ -46,4 +56,56 @@ public class CommonRest { OuterAdapter adapter = loader.getExtension(type); return adapter.count(task); } + + @GetMapping("/destinations") + public List> destinations() { + List> result = new ArrayList<>(); + Set destinations = AdapterCanalConfig.DESTINATIONS; + for (String destination : destinations) { + Map resMap = new LinkedHashMap<>(); + Boolean status = syncSwitch.status(destination); + String resStatus = "none"; + if (status != null && status) { + resStatus = "on"; + } else if (status != null && !status) { + resStatus = "off"; + } + resMap.put("destination", destination); + resMap.put("status", resStatus); + result.add(resMap); + } + return result; + } + + @PutMapping("/syncSwitch/{destination}/{status}") + public Result etl(@PathVariable String destination, @PathVariable String status) { + if (status.equals("on")) { + syncSwitch.on(destination); + logger.info("#Destination: {} sync on", destination); + return Result.createSuccess("实例: " + destination + " 开启同步成功"); + } else if (status.equals("off")) { + syncSwitch.off(destination); + logger.info("#Destination: {} sync off", destination); + return Result.createSuccess("实例: " + destination + " 关闭同步成功"); + } else { + Result result = new Result(); + result.setCode(50000); + result.setMessage("实例: " + destination + " 操作失败"); + return result; + } + } + + @GetMapping("/syncSwitch/{destination}") + public Map etl(@PathVariable String destination) { + Boolean status = syncSwitch.status(destination); + String resStatus = "none"; + if (status != null && status) { + resStatus = "on"; + } else if (status != null && !status) { + resStatus = "off"; + } + Map res = new LinkedHashMap<>(); + res.put("stauts", resStatus); + return res; + } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index b61a4749..83ccb66d 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -9,6 +9,11 @@ spring: time-zone: GMT+8 default-property-inclusion: non_null + +hbase.zookeeper.quorum: slave1 +hbase.zookeeper.property.clientPort: 2181 +hbase.zookeeper.znode.parent: /hbase-unsecure + canal.conf: canalServerHost: 127.0.0.1:11111 # zookeeperHosts: slave1:2181 @@ -21,9 +26,9 @@ canal.conf: - name: logger - name: hbase properties: - hbase.zookeeper.quorum: slave1 - hbase.zookeeper.property.clientPort: 2181 - zookeeper.znode.parent: /hbase-unsecure + hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} + hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort} + zookeeper.znode.parent: ${hbase.zookeeper.znode.parent} # mqTopics: # - mqMode: kafka # topic: example From 42a4abe4c7ff6abb0d57adfbac4afee955a88bf7 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 14:24:33 +0800 Subject: [PATCH 197/360] =?UTF-8?q?HBase=20etl=E9=94=81=E5=AE=9A=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E7=AD=89=E5=BE=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/OuterAdapter.java | 10 ++++ .../client/adapter/hbase/HbaseAdapter.java | 9 ++++ .../adapter/hbase/config/MappingConfig.java | 9 ++++ .../main/resources/hbase/mytest_person2.yml | 1 + .../adapter/launcher/rest/CommonRest.java | 53 +++++++++++++++---- 5 files changed, 73 insertions(+), 9 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index bf6d5fce..286ba398 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -55,4 +55,14 @@ public interface OuterAdapter { default Map count(String task) { throw new UnsupportedOperationException("unsupported operation"); } + + /** + * 通过task获取对应的destination + * + * @param task 任务名, 对应配置名 + * @return destination + */ + default String getDestination(String task) { + return null; + } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 22731960..50eb271c 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -162,4 +162,13 @@ public class HbaseAdapter implements OuterAdapter { } } } + + @Override + public String getDestination(String task) { + MappingConfig config = hbaseMapping.get(task); + if (config != null && config.getHbaseOrm() != null) { + return config.getHbaseOrm().getDestination(); + } + return null; + } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java index c3c30c1b..a5ea5707 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -146,6 +146,7 @@ public class MappingConfig { public static class HbaseOrm { private Mode mode = Mode.STRING; + private String destination; private String database; private String table; private String hbaseTable; @@ -170,6 +171,14 @@ public class MappingConfig { this.mode = mode; } + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + public String getDatabase() { return database; } diff --git a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml index cbf14d48..88cd99e2 100644 --- a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml +++ b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml @@ -1,6 +1,7 @@ dataSourceKey: defaultDS hbaseOrm: mode: PHOENIX #NATIVE #STRING + destination: example database: mytest # 数据库名 table: person2 # 数据库表名 hbaseTable: MYTEST.PERSON2 # HBase表名 diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index 4e4aa7cb..61c2b47b 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -32,24 +32,43 @@ public class CommonRest { } /** - * Demo: curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST -d - * "params=0,1,2" + * ETL curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST + * + * @param type 类型 hbase, es + * @param task 任务名对应配置文件名 mytest_person2.yml + * @param params etl where条件参数, 为空全部导入 + * @return */ @PostMapping("/etl/{type}/{task}") public EtlResult etl(@PathVariable String type, @PathVariable String task, @RequestParam(name = "params", required = false) String params) { OuterAdapter adapter = loader.getExtension(type); - List paramArr = null; - if (params != null) { - String[] parmaArray = params.trim().split(";"); - paramArr = Arrays.asList(parmaArray); + String destination = adapter.getDestination(task); + Boolean oriSwithcStatus = null; + if (destination != null) { + oriSwithcStatus = syncSwitch.status(destination); + syncSwitch.off(destination); + } + try { + List paramArr = null; + if (params != null) { + String[] parmaArray = params.trim().split(";"); + paramArr = Arrays.asList(parmaArray); + } + return adapter.etl(task, paramArr); + } finally { + if (destination != null && oriSwithcStatus != null && oriSwithcStatus) { + syncSwitch.on(destination); + } } - - return adapter.etl(task, paramArr); } /** - * Demo: curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml + * 统计总数 curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml + * + * @param type 类型 hbase, es + * @param task 任务名对应配置文件名 mytest_person2.yml + * @return */ @GetMapping("/count/{type}/{task}") public Map count(@PathVariable String type, @PathVariable String task) { @@ -57,6 +76,9 @@ public class CommonRest { return adapter.count(task); } + /** + * 返回所有实例 curl http://127.0.0.1:8081/destinations + */ @GetMapping("/destinations") public List> destinations() { List> result = new ArrayList<>(); @@ -77,6 +99,13 @@ public class CommonRest { return result; } + /** + * 实例同步开关 curl http://127.0.0.1:8081/syncSwitch/example/off -X PUT + * + * @param destination 实例名称 + * @param status 开关状态: off on + * @return + */ @PutMapping("/syncSwitch/{destination}/{status}") public Result etl(@PathVariable String destination, @PathVariable String status) { if (status.equals("on")) { @@ -95,6 +124,12 @@ public class CommonRest { } } + /** + * 获取实例开关状态 curl http://127.0.0.1:8081/syncSwitch/example + * + * @param destination 实例名称 + * @return + */ @GetMapping("/syncSwitch/{destination}") public Map etl(@PathVariable String destination) { Boolean status = syncSwitch.status(destination); From 6ee6b3c3980f422921dce524fdf806286572589f Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 16:43:33 +0800 Subject: [PATCH 198/360] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E5=88=86=E5=B8=83?= =?UTF-8?q?=E5=BC=8F=E5=BC=80=E5=85=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/common/pom.xml | 5 - .../client/adapter/hbase/HbaseAdapter.java | 7 +- client-adapter/launcher/pom.xml | 5 + .../adapter/launcher/common/SyncSwitch.java | 130 ++++++++++++++++-- .../launcher/config/AdapterCanalConfig.java | 2 +- .../launcher/config/CuratorClient.java | 36 +++++ .../launcher/loader/CanalAdapterLoader.java | 8 +- .../launcher/loader/CanalAdapterWorker.java | 2 - .../adapter/launcher/rest/CommonRest.java | 5 +- .../src/main/resources/application.yml | 5 +- 10 files changed, 178 insertions(+), 27 deletions(-) create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml index d61d8d11..eb3a21a3 100644 --- a/client-adapter/common/pom.xml +++ b/client-adapter/common/pom.xml @@ -18,11 +18,6 @@ canal.protocol ${canal_version} - - org.slf4j - slf4j-api - 1.7.12 - joda-time joda-time diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 50eb271c..ababeb6e 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -8,6 +8,7 @@ import java.util.Map; import javax.sql.DataSource; +import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; @@ -52,7 +53,8 @@ public class HbaseAdapter implements OuterAdapter { hbaseMapping = MappingConfigLoader.load(); mappingConfigCache = new HashMap<>(); for (MappingConfig mappingConfig : hbaseMapping.values()) { - mappingConfigCache.put(mappingConfig.getHbaseOrm().getDatabase() + "-" + mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getHbaseOrm().getDestination()) + + "." + mappingConfig.getHbaseOrm().getDatabase() + "." + mappingConfig.getHbaseOrm().getTable(), mappingConfig); } @@ -77,9 +79,10 @@ public class HbaseAdapter implements OuterAdapter { if (dml == null) { return; } + String destination = StringUtils.trimToEmpty(dml.getDestination()); String database = dml.getDatabase(); String table = dml.getTable(); - MappingConfig config = mappingConfigCache.get(database + "-" + table); + MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); hbaseSyncService.sync(config, dml); } diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index 7d54cd58..0f39411e 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -50,6 +50,11 @@ spring-boot-configuration-processor true + + org.apache.curator + curator-recipes + 2.10.0 + org.apache.rocketmq diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java index 2a2f1220..e9e647fd 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.adapter.launcher.common; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -8,42 +9,114 @@ import java.util.concurrent.TimeoutException; import javax.annotation.PostConstruct; import javax.annotation.Resource; -import org.apache.commons.lang.StringUtils; +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.cache.NodeCache; +import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.data.Stat; import org.springframework.stereotype.Component; import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; +import com.alibaba.otter.canal.adapter.launcher.config.CuratorClient; import com.alibaba.otter.canal.common.utils.BooleanMutex; @Component public class SyncSwitch { - private static final Map LOCAL_LOCK = new ConcurrentHashMap<>(); + private static final String SYN_SWITCH_ZKNODE = "/sync-switch/"; - private Mode mode = Mode.LOCAL; + private static final Map LOCAL_LOCK = new ConcurrentHashMap<>(); + + private static final Map DISTRIBUTED_LOCK = new ConcurrentHashMap<>(); + + private static Mode mode = Mode.LOCAL; @Resource private AdapterCanalConfig adapterCanalConfig; + @Resource + private CuratorClient curatorClient; @PostConstruct public void init() { - if (StringUtils.isEmpty(adapterCanalConfig.getZookeeperHosts())) { + CuratorFramework curator = curatorClient.getCurator(); + if (curator != null) { + mode = Mode.DISTRIBUTED; + DISTRIBUTED_LOCK.clear(); + for (String destination : adapterCanalConfig.DESTINATIONS) { + // 对应每个destination注册锁 + BooleanMutex mutex = new BooleanMutex(true); + initMutex(curator, destination, mutex); + DISTRIBUTED_LOCK.put(destination, mutex); + startListen(destination, mutex); + } + } else { mode = Mode.LOCAL; LOCAL_LOCK.clear(); - for (String destination : AdapterCanalConfig.DESTINATIONS) { + for (String destination : adapterCanalConfig.DESTINATIONS) { // 对应每个destination注册锁 LOCAL_LOCK.put(destination, new BooleanMutex(true)); } - } else { - mode = Mode.DISTRIBUTED; + } + } + + private synchronized void startListen(String destination, BooleanMutex mutex) { + try { + String path = SYN_SWITCH_ZKNODE + destination; + CuratorFramework curator = curatorClient.getCurator(); + final NodeCache nodeCache = new NodeCache(curator, path); + nodeCache.start(); + nodeCache.getListenable().addListener(() -> initMutex(curator, destination, mutex)); + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); + } + } + + private synchronized void initMutex(CuratorFramework curator, String destination, BooleanMutex mutex) { + try { + String path = SYN_SWITCH_ZKNODE + destination; + Stat stat = curator.checkExists().forPath(path); + if (stat == null) { + if (!mutex.state()) { + mutex.set(true); + } + } else { + String data = new String(curator.getData().forPath(path), StandardCharsets.UTF_8); + if ("on".equals(data)) { + if (!mutex.state()) { + mutex.set(true); + } + } else { + if (mutex.state()) { + mutex.set(false); + } + } + } + } catch (Exception e) { + throw new RuntimeException(e.getMessage()); } } public synchronized void off(String destination) { + if (mode == Mode.LOCAL) { BooleanMutex mutex = LOCAL_LOCK.get(destination); if (mutex != null && mutex.state()) { mutex.set(false); } + } else { + try { + String path = SYN_SWITCH_ZKNODE + destination; + try { + curatorClient.getCurator() + .create() + .creatingParentContainersIfNeeded() + .withMode(CreateMode.PERSISTENT) + .forPath(path, "off".getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + curatorClient.getCurator().setData().forPath(path, "off".getBytes(StandardCharsets.UTF_8)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } } } @@ -53,14 +126,35 @@ public class SyncSwitch { if (mutex != null && !mutex.state()) { mutex.set(true); } + } else { + try { + String path = SYN_SWITCH_ZKNODE + destination; + try { + curatorClient.getCurator() + .create() + .creatingParentContainersIfNeeded() + .withMode(CreateMode.PERSISTENT) + .forPath(path, "on".getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + curatorClient.getCurator().setData().forPath(path, "on".getBytes(StandardCharsets.UTF_8)); + } + } catch (Exception e) { + throw new RuntimeException(e); + } } } public synchronized void release(String destination) { if (mode == Mode.LOCAL) { BooleanMutex mutex = LOCAL_LOCK.get(destination); - if (mutex != null && mutex.state()) { - mutex.set(false); + if (mutex != null && !mutex.state()) { + mutex.set(true); + } + } + if (mode == Mode.DISTRIBUTED) { + BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination); + if (mutex != null && !mutex.state()) { + mutex.set(true); } } } @@ -73,8 +167,14 @@ public class SyncSwitch { } else { return null; } + } else { + BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination); + if (mutex != null) { + return mutex.state(); + } else { + return null; + } } - return null; } public void get(String destination) throws InterruptedException { @@ -83,6 +183,11 @@ public class SyncSwitch { if (mutex != null) { mutex.get(); } + } else { + BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination); + if (mutex != null) { + mutex.get(); + } } } @@ -92,6 +197,11 @@ public class SyncSwitch { if (mutex != null) { mutex.get(timeout, unit); } + } else { + BooleanMutex mutex = DISTRIBUTED_LOCK.get(destination); + if (mutex != null) { + mutex.get(timeout, unit); + } } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index 3dcfe4e7..18a12d7c 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -13,7 +13,7 @@ import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; @ConfigurationProperties(prefix = "canal.conf") public class AdapterCanalConfig extends CanalClientConfig { - public static final Set DESTINATIONS = new LinkedHashSet<>(); + public final Set DESTINATIONS = new LinkedHashSet<>(); @Override public void setCanalInstances(List canalInstances) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java new file mode 100644 index 00000000..b7944852 --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java @@ -0,0 +1,36 @@ +package com.alibaba.otter.canal.adapter.launcher.config; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.retry.ExponentialBackoffRetry; +import org.springframework.stereotype.Component; + +@Component +public class CuratorClient { + + @Resource + private AdapterCanalConfig adapterCanalConfig; + + private CuratorFramework curator = null; + + @PostConstruct + public void init() { + if (adapterCanalConfig.getZookeeperHosts() != null) { + curator = CuratorFrameworkFactory.builder() + .connectString(adapterCanalConfig.getZookeeperHosts()) + .retryPolicy(new ExponentialBackoffRetry(1000, 3)) + .sessionTimeoutMs(6000) + .connectionTimeoutMs(3000) + .namespace("canal-adapter") + .build(); + curator.start(); + } + } + + public CuratorFramework getCurator() { + return curator; + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index d9cdaa7c..3bb5b4a4 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -15,8 +15,8 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; -import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; /** * 外部适配器的加载器 @@ -66,10 +66,12 @@ public class CanalAdapterLoader { canalOuterAdapterGroups.add(canalOutConnectors); } CanalAdapterWorker worker; - if (zkHosts != null) { + if (sa != null) { + worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups); + } else if (zkHosts != null) { worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups); } else { - worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups); + throw new RuntimeException("No canal server connector found"); } canalWorkers.put(instance.getInstance(), worker); worker.start(); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index b976bb82..1c324996 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -54,8 +54,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", ""); ((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT); - - // super.initSwitcher(canalDestination); } @Override diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index 61c2b47b..d08af780 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -26,6 +26,9 @@ public class CommonRest { @Resource private SyncSwitch syncSwitch; + @Resource + private AdapterCanalConfig adapterCanalConfig; + @PostConstruct public void init() { loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class); @@ -82,7 +85,7 @@ public class CommonRest { @GetMapping("/destinations") public List> destinations() { List> result = new ArrayList<>(); - Set destinations = AdapterCanalConfig.DESTINATIONS; + Set destinations = adapterCanalConfig.DESTINATIONS; for (String destination : destinations) { Map resMap = new LinkedHashMap<>(); Boolean status = syncSwitch.status(destination); diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 83ccb66d..9030cb71 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -16,14 +16,14 @@ hbase.zookeeper.znode.parent: /hbase-unsecure canal.conf: canalServerHost: 127.0.0.1:11111 -# zookeeperHosts: slave1:2181 + zookeeperHosts: slave1:2181 # bootstrapServers: slave1:6667 #or rocketmq nameservers:host1:9876;host2:9876 flatMessage: true canalInstances: - instance: example adapterGroups: - outAdapters: - - name: logger +# - name: logger - name: hbase properties: hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} @@ -45,4 +45,3 @@ adapter.conf: password: 121212 adapterConfigs: - hbase/mytest_person2.yml -# - hbase/mytest.person2@defaultDS From c3402a97f377b3cb8dc1d69a5fab027f293088ee Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 17:34:15 +0800 Subject: [PATCH 199/360] =?UTF-8?q?etl=20=E5=A2=9E=E5=8A=A0=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/launcher/common/EtlLock.java | 110 ++++++++++++++++++ .../canal/adapter/launcher/common/Mode.java | 6 + .../adapter/launcher/common/SyncSwitch.java | 16 +-- .../adapter/launcher/rest/CommonRest.java | 49 +++++--- .../src/main/resources/application.yml | 30 ++--- 5 files changed, 172 insertions(+), 39 deletions(-) create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/Mode.java diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java new file mode 100644 index 00000000..35ba5ddf --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java @@ -0,0 +1,110 @@ +package com.alibaba.otter.canal.adapter.launcher.common; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; + +import org.apache.curator.framework.CuratorFramework; +import org.apache.curator.framework.recipes.locks.InterProcessMutex; +import org.springframework.stereotype.Component; + +import com.alibaba.otter.canal.adapter.launcher.config.CuratorClient; + +@Component +public class EtlLock { + + private static final Map LOCAL_LOCK = new ConcurrentHashMap<>(); + + private static final Map DISTRIBUTED_LOCK = new ConcurrentHashMap<>(); + + private static Mode mode = Mode.LOCAL; + + @Resource + private CuratorClient curatorClient; + + @PostConstruct + public void init() { + CuratorFramework curator = curatorClient.getCurator(); + if (curator != null) { + mode = Mode.DISTRIBUTED; + } else { + mode = Mode.LOCAL; + } + } + + private ReentrantLock getLock(String key) { + ReentrantLock lock = LOCAL_LOCK.get(key); + if (lock == null) { + synchronized (EtlLock.class) { + lock = LOCAL_LOCK.get(key); + if (lock == null) { + lock = new ReentrantLock(); + LOCAL_LOCK.put(key, lock); + } + } + } + return lock; + } + + private InterProcessMutex getRemoteLock(String key) { + InterProcessMutex lock = DISTRIBUTED_LOCK.get(key); + if (lock == null) { + synchronized (EtlLock.class) { + lock = DISTRIBUTED_LOCK.get(key); + if (lock == null) { + lock = new InterProcessMutex(curatorClient.getCurator(), key); + DISTRIBUTED_LOCK.put(key, lock); + } + } + } + return lock; + } + + public void lock(String key) throws Exception { + if (mode == Mode.LOCAL) { + getLock(key).lock(); + } else { + InterProcessMutex lock = getRemoteLock(key); + lock.acquire(); + } + } + + public boolean tryLock(String key, long timeout, TimeUnit unit) { + try { + if (mode == Mode.LOCAL) { + return getLock(key).tryLock(timeout, unit); + } else { + InterProcessMutex lock = getRemoteLock(key); + return lock.acquire(timeout, unit); + } + } catch (Exception e) { + return false; + } + } + + public boolean tryLock(String key) { + try { + if (mode == Mode.LOCAL) { + return getLock(key).tryLock(); + } else { + InterProcessMutex lock = getRemoteLock(key); + return lock.acquire(500, TimeUnit.MILLISECONDS); + } + } catch (Exception e) { + return false; + } + } + + public void unlock(String key) throws Exception { + if (mode == Mode.LOCAL) { + getLock(key).unlock(); + } else { + InterProcessMutex lock = getRemoteLock(key); + lock.release(); + } + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/Mode.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/Mode.java new file mode 100644 index 00000000..ac73b85e --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/Mode.java @@ -0,0 +1,6 @@ +package com.alibaba.otter.canal.adapter.launcher.common; + +public enum Mode { + LOCAL, // 本地模式 + DISTRIBUTED // 分布式 +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java index e9e647fd..d96c753e 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java @@ -22,7 +22,7 @@ import com.alibaba.otter.canal.common.utils.BooleanMutex; @Component public class SyncSwitch { - private static final String SYN_SWITCH_ZKNODE = "/sync-switch/"; + private static final String SYN_SWITCH_ZK_NODE = "/sync-switch/"; private static final Map LOCAL_LOCK = new ConcurrentHashMap<>(); @@ -60,7 +60,7 @@ public class SyncSwitch { private synchronized void startListen(String destination, BooleanMutex mutex) { try { - String path = SYN_SWITCH_ZKNODE + destination; + String path = SYN_SWITCH_ZK_NODE + destination; CuratorFramework curator = curatorClient.getCurator(); final NodeCache nodeCache = new NodeCache(curator, path); nodeCache.start(); @@ -72,7 +72,7 @@ public class SyncSwitch { private synchronized void initMutex(CuratorFramework curator, String destination, BooleanMutex mutex) { try { - String path = SYN_SWITCH_ZKNODE + destination; + String path = SYN_SWITCH_ZK_NODE + destination; Stat stat = curator.checkExists().forPath(path); if (stat == null) { if (!mutex.state()) { @@ -96,7 +96,6 @@ public class SyncSwitch { } public synchronized void off(String destination) { - if (mode == Mode.LOCAL) { BooleanMutex mutex = LOCAL_LOCK.get(destination); if (mutex != null && mutex.state()) { @@ -104,7 +103,7 @@ public class SyncSwitch { } } else { try { - String path = SYN_SWITCH_ZKNODE + destination; + String path = SYN_SWITCH_ZK_NODE + destination; try { curatorClient.getCurator() .create() @@ -128,7 +127,7 @@ public class SyncSwitch { } } else { try { - String path = SYN_SWITCH_ZKNODE + destination; + String path = SYN_SWITCH_ZK_NODE + destination; try { curatorClient.getCurator() .create() @@ -205,8 +204,5 @@ public class SyncSwitch { } } - enum Mode { - LOCAL, // 本地模式 - DISTRIBUTED // 分布式 - } + } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index d08af780..0c4392a3 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -9,6 +9,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.bind.annotation.*; +import com.alibaba.otter.canal.adapter.launcher.common.EtlLock; import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; import com.alibaba.otter.canal.client.adapter.OuterAdapter; @@ -19,12 +20,16 @@ import com.alibaba.otter.canal.client.adapter.support.Result; @RestController public class CommonRest { - private static Logger logger = LoggerFactory.getLogger(CommonRest.class); + private static Logger logger = LoggerFactory.getLogger(CommonRest.class); + + private static final String ETL_LOCK_ZK_NODE = "/sync-etl/"; private ExtensionLoader loader; @Resource private SyncSwitch syncSwitch; + @Resource + private EtlLock etlLock; @Resource private AdapterCanalConfig adapterCanalConfig; @@ -45,23 +50,39 @@ public class CommonRest { @PostMapping("/etl/{type}/{task}") public EtlResult etl(@PathVariable String type, @PathVariable String task, @RequestParam(name = "params", required = false) String params) { - OuterAdapter adapter = loader.getExtension(type); - String destination = adapter.getDestination(task); - Boolean oriSwithcStatus = null; - if (destination != null) { - oriSwithcStatus = syncSwitch.status(destination); - syncSwitch.off(destination); + + boolean locked = etlLock.tryLock(ETL_LOCK_ZK_NODE + type + "-" + task); + if (!locked) { + EtlResult result = new EtlResult(); + result.setSucceeded(false); + result.setErrorMessage(task + " 有其他进程正在导入中, 请稍后再试"); + return result; } try { - List paramArr = null; - if (params != null) { - String[] parmaArray = params.trim().split(";"); - paramArr = Arrays.asList(parmaArray); + OuterAdapter adapter = loader.getExtension(type); + String destination = adapter.getDestination(task); + Boolean oriSwithcStatus = null; + if (destination != null) { + oriSwithcStatus = syncSwitch.status(destination); + syncSwitch.off(destination); + } + try { + List paramArr = null; + if (params != null) { + String[] parmaArray = params.trim().split(";"); + paramArr = Arrays.asList(parmaArray); + } + return adapter.etl(task, paramArr); + } finally { + if (destination != null && oriSwithcStatus != null && oriSwithcStatus) { + syncSwitch.on(destination); + } } - return adapter.etl(task, paramArr); } finally { - if (destination != null && oriSwithcStatus != null && oriSwithcStatus) { - syncSwitch.on(destination); + try { + etlLock.unlock(ETL_LOCK_ZK_NODE + type + "-" + task); + } catch (Exception e) { + logger.error(e.getMessage(), e); } } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 9030cb71..43cc4e38 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -16,19 +16,19 @@ hbase.zookeeper.znode.parent: /hbase-unsecure canal.conf: canalServerHost: 127.0.0.1:11111 - zookeeperHosts: slave1:2181 +# zookeeperHosts: slave1:2181 # bootstrapServers: slave1:6667 #or rocketmq nameservers:host1:9876;host2:9876 flatMessage: true canalInstances: - instance: example adapterGroups: - outAdapters: -# - name: logger - - name: hbase - properties: - hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} - hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort} - zookeeper.znode.parent: ${hbase.zookeeper.znode.parent} + - name: logger +# - name: hbase +# properties: +# hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} +# hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort} +# zookeeper.znode.parent: ${hbase.zookeeper.znode.parent} # mqTopics: # - mqMode: kafka # topic: example @@ -37,11 +37,11 @@ canal.conf: # outAdapters: # - name: logger -adapter.conf: - datasourceConfigs: - defaultDS: - url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true - username: root - password: 121212 - adapterConfigs: - - hbase/mytest_person2.yml +#adapter.conf: +# datasourceConfigs: +# defaultDS: +# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true +# username: root +# password: 121212 +# adapterConfigs: +# - hbase/mytest_person2.yml From ddfc2464f379a66663720294a341ba533f1c7d83 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 17:43:11 +0800 Subject: [PATCH 200/360] =?UTF-8?q?etl=20=E5=A2=9E=E5=8A=A0=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/adapter/launcher/common/EtlLock.java | 8 ++++++-- .../otter/canal/adapter/launcher/rest/CommonRest.java | 6 +----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java index 35ba5ddf..67d0ac41 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java @@ -99,12 +99,16 @@ public class EtlLock { } } - public void unlock(String key) throws Exception { + public void unlock(String key) { if (mode == Mode.LOCAL) { getLock(key).unlock(); } else { InterProcessMutex lock = getRemoteLock(key); - lock.release(); + try { + lock.release(); + } catch (Exception e) { + // ignore + } } } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index 0c4392a3..bee78e04 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -79,11 +79,7 @@ public class CommonRest { } } } finally { - try { - etlLock.unlock(ETL_LOCK_ZK_NODE + type + "-" + task); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } + etlLock.unlock(ETL_LOCK_ZK_NODE + type + "-" + task); } } From d70dc8b88c5cd1a5506b191226fc89d3b8e72244 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 25 Oct 2018 21:21:06 +0800 Subject: [PATCH 201/360] =?UTF-8?q?=E8=A7=84=E8=8C=83=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/OuterAdapter.java | 2 +- .../adapter/support/AdapterConfigs.java | 17 ++++- .../adapter/support/CanalClientConfig.java | 41 +++++------- .../adapter/support/DatasourceConfig.java | 26 ++++--- .../canal/client/adapter/support/Dml.java | 20 +++--- .../client/adapter/support/EtlResult.java | 15 +++-- .../adapter/support/ExtensionLoader.java | 67 ++++++++----------- .../client/adapter/support/JdbcTypeUtil.java | 4 +- .../client/adapter/support/MessageUtil.java | 2 +- .../adapter/support/OuterAdapterConfig.java | 2 +- .../canal/client/adapter/support/Result.java | 8 ++- .../canal/client/adapter/support/SPI.java | 6 ++ .../adapter/hbase/config/MappingConfig.java | 45 ++++++------- .../hbase/config/MappingConfigLoader.java | 2 +- .../hbase/service/HbaseEtlService.java | 32 +++++++++ .../hbase/service/HbaseSyncService.java | 2 +- .../launcher/CanalAdapterApplication.java | 6 ++ .../adapter/launcher/common/EtlLock.java | 6 ++ .../adapter/launcher/common/SyncSwitch.java | 13 ++-- .../launcher/config/AdapterCanalConfig.java | 6 ++ .../launcher/config/AdapterConfig.java | 8 ++- .../launcher/config/CuratorClient.java | 6 ++ .../launcher/config/SpringContext.java | 6 ++ .../loader/AbstractCanalAdapterWorker.java | 2 +- .../loader/CanalAdapterKafkaWorker.java | 2 +- .../loader/CanalAdapterRocketMQWorker.java | 2 +- .../launcher/loader/CanalAdapterService.java | 6 ++ .../launcher/loader/CanalAdapterWorker.java | 2 +- .../adapter/launcher/rest/CommonRest.java | 6 ++ 29 files changed, 230 insertions(+), 132 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index 286ba398..edb34049 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -11,7 +11,7 @@ import com.alibaba.otter.canal.client.adapter.support.SPI; /** * 外部适配器接口 * - * @author machengyuan 2018-8-18 下午10:14:02 + * @author reweerma 2018-8-18 下午10:14:02 * @version 1.0.0 */ @SPI("logger") diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java index 44d29ee9..0ccfb509 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java @@ -1,13 +1,26 @@ package com.alibaba.otter.canal.client.adapter.support; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +/** + * 适配器配置集合, 用于配置加载, 线程不安全 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ public class AdapterConfigs { - private static Map> configs = new ConcurrentHashMap<>(); + /** + * 类型下对应所有配置名, 如: + * hbase + * ┗━ mytest_person.yml + * ┗━ mytest_role.yml + * ┗━ mytest_department.yml + */ + private static Map> configs = new HashMap<>(); public static void put(String key, String value) { Set values = configs.get(key); diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 08246a42..29da946e 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -2,29 +2,26 @@ package com.alibaba.otter.canal.client.adapter.support; import java.util.ArrayList; import java.util.List; -import java.util.Properties; /** * 配置信息类 * - * @author machengyuan 2018-8-18 下午10:40:12 + * @author rewerma 2018-8-18 下午10:40:12 * @version 1.0.0 */ public class CanalClientConfig { - private String canalServerHost; + private String canalServerHost; // 单机模式下canal server的 ip:port - private String zookeeperHosts; + private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! - private Properties properties; + private String bootstrapServers; // kafka or rocket mq 地址 - private String bootstrapServers; + private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 - private List mqTopics; + private List mqTopics; // mq topic 列表 - private Boolean flatMessage = true; - - private List canalInstances; + private List canalInstances; // tcp 模式下 canal 实例列表, 与mq模式不能共存!! public String getCanalServerHost() { return canalServerHost; @@ -42,14 +39,6 @@ public class CanalClientConfig { this.zookeeperHosts = zookeeperHosts; } - public Properties getProperties() { - return properties; - } - - public void setProperties(Properties properties) { - this.properties = properties; - } - public String getBootstrapServers() { return bootstrapServers; } @@ -84,9 +73,9 @@ public class CanalClientConfig { public static class CanalInstance { - private String instance; + private String instance; // 实例名 - private List adapterGroups; + private List adapterGroups; // 适配器分组列表 public String getInstance() { return instance; @@ -109,7 +98,7 @@ public class CanalClientConfig { public static class AdapterGroup { - private List outAdapters; + private List outAdapters; // 适配器列表 public List getOutAdapters() { return outAdapters; @@ -122,11 +111,11 @@ public class CanalClientConfig { public static class MQTopic { - private String mqMode; + private String mqMode; // mq模式 kafka or rocketMQ - private String topic; + private String topic; // topic名 - private List groups = new ArrayList<>(); + private List groups = new ArrayList<>(); // 分组列表 public String getMqMode() { return mqMode; @@ -155,11 +144,11 @@ public class CanalClientConfig { public static class Group { - private String groupId; + private String groupId; // group id // private List adapters = new ArrayList<>(); - private List outAdapters; + private List outAdapters; // 适配器配置列表 public String getGroupId() { return groupId; diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java index cf2ec471..acc07dd1 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/DatasourceConfig.java @@ -1,21 +1,27 @@ package com.alibaba.otter.canal.client.adapter.support; -import com.alibaba.druid.pool.DruidDataSource; - import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import com.alibaba.druid.pool.DruidDataSource; + +/** + * 数据源配置 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ public class DatasourceConfig { - public final static Map DATA_SOURCES = new ConcurrentHashMap<>(); + public final static Map DATA_SOURCES = new ConcurrentHashMap<>(); // key对应的数据源 - private String driver = "com.mysql.jdbc.Driver"; - private String url; - private String database; - private String type = "mysql"; - private String username; - private String password; - private Integer maxActive = 3; + private String driver = "com.mysql.jdbc.Driver"; // 默认为mysql jdbc驱动 + private String url; // jdbc url + private String database; // jdbc database + private String type = "mysql"; // 类型, 默认为mysql + private String username; // jdbc username + private String password; // jdbc password + private Integer maxActive = 3; // 连接池最大连接数,默认为3 public String getDriver() { return driver; diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java index 8e5af059..1d5357b4 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Dml.java @@ -7,24 +7,24 @@ import java.util.Map; /** * DML操作转换对象 * - * @author machengyuan 2018-8-19 下午11:30:49 + * @author rewerma 2018-8-19 下午11:30:49 * @version 1.0.0 */ public class Dml implements Serializable { private static final long serialVersionUID = 2611556444074013268L; - private String destination; - private String database; - private String table; - private String type; + private String destination; // 对应canal的实例或者MQ的topic + private String database; // 数据库或schema + private String table; // 表名 + private String type; // 类型: INSERT UPDATE DELETE // binlog executeTime - private Long es; + private Long es; // 执行耗时 // dml build timeStamp - private Long ts; - private String sql; - private List> data; - private List> old; + private Long ts; // 同步时间 + private String sql; // 执行的sql, dml sql为空 + private List> data; // 数据列表 + private List> old; // 旧数据列表, 用于update, size和data的size一一对应 public String getDestination() { return destination; diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java index 0004e51a..498af7a3 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/EtlResult.java @@ -2,14 +2,21 @@ package com.alibaba.otter.canal.client.adapter.support; import java.io.Serializable; +/** + * ETL的结果对象 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ public class EtlResult implements Serializable { + private static final long serialVersionUID = 4250522736289866505L; - private boolean succeeded = false; + private boolean succeeded = false; - private String resultMessage; + private String resultMessage; - private String errorMessage; + private String errorMessage; public boolean getSucceeded() { return succeeded; @@ -34,4 +41,4 @@ public class EtlResult implements Serializable { public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } -} \ No newline at end of file +} diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index b5de0d82..5b5c1bbb 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -1,22 +1,11 @@ package com.alibaba.otter.canal.client.adapter.support; -import java.io.BufferedReader; -import java.io.File; -import java.io.FilenameFilter; -import java.io.IOException; -import java.io.InputStreamReader; +import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Paths; -import java.util.Arrays; -import java.util.Collections; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; @@ -27,12 +16,13 @@ import org.slf4j.LoggerFactory; /** * SPI 类加载器 * - * @author machengyuan 2018-8-19 下午11:30:49 + * @author rewerma 2018-8-19 下午11:30:49 * @version 1.0.0 */ public class ExtensionLoader { - private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); + private static final Logger logger = LoggerFactory + .getLogger(ExtensionLoader.class); private static final String SERVICES_DIRECTORY = "META-INF/services/"; @@ -40,7 +30,8 @@ public class ExtensionLoader { private static final String DEFAULT_CLASSLOADER_POLICY = "internal"; - private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); + private static final Pattern NAME_SEPARATOR = Pattern + .compile("\\s*[,]+\\s*"); private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); @@ -271,7 +262,8 @@ public class ExtensionLoader { return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type - + ") could not be instantiated: " + t.getMessage(), t); + + ") could not be instantiated: " + t.getMessage(), + t); } } @@ -279,8 +271,8 @@ public class ExtensionLoader { if (type == null) throw new IllegalArgumentException("Extension type == null"); if (name == null) throw new IllegalArgumentException("Extension name == null"); Class clazz = getExtensionClasses().get(name); - if (clazz == null) throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() - + "!"); + if (clazz == null) + throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!"); return clazz; } @@ -342,8 +334,8 @@ public class ExtensionLoader { logger.info("extension classpath dir: " + dir); File externalLibDir = new File(dir); if (!externalLibDir.exists()) { - externalLibDir = new File(File.separator + this.getJarDirectoryPath() + File.separator + "canal_client" - + File.separator + "lib"); + externalLibDir = new File( + File.separator + this.getJarDirectoryPath() + File.separator + "canal_client" + File.separator + "lib"); } if (externalLibDir.exists()) { File[] files = externalLibDir.listFiles(new FilenameFilter() { @@ -495,12 +487,10 @@ public class ExtensionLoader { // Class.forName(line, true, // classLoader); if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Error when load extension class(interface: " - + type - + ", class line: " - + clazz.getName() - + "), class " - + clazz.getName() + throw new IllegalStateException( + "Error when load extension class(interface: " + type + + ", class line: " + clazz.getName() + + "), class " + clazz.getName() + "is not subtype of interface."); } else { try { @@ -518,9 +508,9 @@ public class ExtensionLoader { extensionClasses.put(n, clazz); } else if (c != clazz) { cachedNames.remove(clazz); - throw new IllegalStateException("Duplicate extension " - + type.getName() - + " name " + n + " on " + throw new IllegalStateException( + "Duplicate extension " + type.getName() + " name " + + n + " on " + c.getName() + " and " + clazz.getName()); } @@ -530,12 +520,9 @@ public class ExtensionLoader { } } } catch (Throwable t) { - IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " - + type - + ", class line: " - + line - + ") in " - + url + IllegalStateException e = new IllegalStateException( + "Failed to load extension class(interface: " + type + ", class line: " + + line + ") in " + url + ", cause: " + t.getMessage(), t); @@ -550,13 +537,15 @@ public class ExtensionLoader { } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + url - + ") in " + url, t); + + ") in " + url, + t); } } // end of while urls } } catch (Throwable t) { - logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName - + ").", t); + logger.error( + "Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", + t); } } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java index d4cb8a0e..c24ff7de 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/JdbcTypeUtil.java @@ -11,7 +11,7 @@ import org.slf4j.LoggerFactory; /** * 类型转换工具类 * - * @author machengyuan 2018-8-19 下午06:14:23 + * @author rewerma 2018-8-19 下午06:14:23 * @version 1.0.0 */ public class JdbcTypeUtil { @@ -30,7 +30,7 @@ public class JdbcTypeUtil { switch (jdbcType) { case Types.BIT: case Types.BOOLEAN: -// return Boolean.class; + // return Boolean.class; case Types.TINYINT: return Byte.TYPE; case Types.SMALLINT: diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index 826ed917..ed5a4358 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -9,7 +9,7 @@ import com.alibaba.otter.canal.protocol.Message; /** * Message对象解析工具类 * - * @author machengyuan 2018-8-19 下午06:14:23 + * @author rewerma 2018-8-19 下午06:14:23 * @version 1.0.0 */ public class MessageUtil { diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java index 3aed2f16..4e73791e 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java @@ -5,7 +5,7 @@ import java.util.Map; /** * 外部适配器配置信息类 * - * @author machengyuan 2018-8-18 下午10:15:12 + * @author rewerma 2018-8-18 下午10:15:12 * @version 1.0.0 */ public class OuterAdapterConfig { diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java index 03831f1f..a9602d05 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java @@ -3,9 +3,15 @@ package com.alibaba.otter.canal.client.adapter.support; import java.io.Serializable; import java.util.Date; +/** + * 用于rest的结果返回对象 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ public class Result implements Serializable { - public Integer code = 20000; + public Integer code = 20000; public Object data; public String message; public Date sysTime; diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/SPI.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/SPI.java index 7aa69938..b4f89b6a 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/SPI.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/SPI.java @@ -6,6 +6,12 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * SPI装载器注解 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE }) diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java index a5ea5707..8027fb51 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -1,21 +1,18 @@ package com.alibaba.otter.canal.client.adapter.hbase.config; -import java.util.LinkedHashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; /** * HBase表映射配置 * - * @author machengyuan 2018-8-21 下午06:45:49 + * @author rewerma 2018-8-21 下午06:45:49 * @version 1.0.0 */ public class MappingConfig { - private String dataSourceKey; - private HbaseOrm hbaseOrm; + private String dataSourceKey; // 数据源key + + private HbaseOrm hbaseOrm; // hbase映射配置 public String getDataSourceKey() { return dataSourceKey; @@ -130,7 +127,7 @@ public class MappingConfig { } public enum Mode { - STRING("STRING"), NATIVE("NATIVE"), PHOENIX("PHOENIX"); + STRING("STRING"), NATIVE("NATIVE"), PHOENIX("PHOENIX"); private String type; @@ -145,23 +142,23 @@ public class MappingConfig { public static class HbaseOrm { - private Mode mode = Mode.STRING; - private String destination; - private String database; - private String table; - private String hbaseTable; - private String family = "CF"; - private boolean uppercaseQualifier = true; - private boolean autoCreateTable = false; // 同步时HBase中表不存在的情况下自动建表 - private String rowKey; // 指定复合主键为rowKey - private Map columns; - private ColumnItem rowKeyColumn; - private String etlCondition; + private Mode mode = Mode.STRING; // hbase默认转换格式 + private String destination; // canal实例或MQ的topic + private String database; // 数据库名或schema名 + private String table; // 表面名 + private String hbaseTable; // hbase表名 + private String family = "CF"; // 默认统一column family + private boolean uppercaseQualifier = true; // 是否转大写 + private boolean autoCreateTable = false; // 同步时HBase中表不存在的情况下自动建表 + private String rowKey; // 指定复合主键为rowKey + private Map columns; // 字段映射 + private ColumnItem rowKeyColumn; // rowKey字段 + private String etlCondition; // etl条件sql - private Map columnItems = new LinkedHashMap<>(); - private Set families = new LinkedHashSet<>(); + private Map columnItems = new LinkedHashMap<>(); // 转换后的字段映射列表 + private Set families = new LinkedHashSet<>(); // column family列表 private int readBatch = 5000; - private int commitBatch = 5000; + private int commitBatch = 5000; // etl等批量提交大小 public Mode getMode() { return mode; diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 0fb727d8..83ba56df 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -18,7 +18,7 @@ import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; /** * HBase表映射配置加载器 * - * @author machengyuan 2018-8-21 下午06:45:49 + * @author rewerma 2018-8-21 下午06:45:49 * @version 1.0.0 */ public class MappingConfigLoader { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index ee110755..7e3c817a 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -24,6 +24,12 @@ import com.alibaba.otter.canal.client.adapter.support.EtlResult; import com.alibaba.otter.canal.client.adapter.support.JdbcTypeUtil; import com.google.common.base.Joiner; +/** + * HBase ETL 操作业务类 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ public class HbaseEtlService { private static Logger logger = LoggerFactory.getLogger(HbaseEtlService.class); @@ -66,6 +72,12 @@ public class HbaseEtlService { } } + /** + * 建表 + * + * @param hbaseTemplate + * @param config + */ public static void createTable(HbaseTemplate hbaseTemplate, MappingConfig config) { try { // 判断hbase表是否存在,不存在则建表 @@ -79,6 +91,15 @@ public class HbaseEtlService { } } + /** + * 导入数据 + * + * @param ds 数据源 + * @param hbaseTemplate hbaseTemplate + * @param config 配置 + * @param params 筛选条件 + * @return 导入结果 + */ public static EtlResult importData(DataSource ds, HbaseTemplate hbaseTemplate, MappingConfig config, List params) { EtlResult etlResult = new EtlResult(); @@ -209,6 +230,17 @@ public class HbaseEtlService { return etlResult; } + /** + * 执行导入 + * + * @param ds + * @param sql + * @param hbaseOrm + * @param hbaseTemplate + * @param successCount + * @param errMsg + * @return + */ private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseOrm hbaseOrm, HbaseTemplate hbaseTemplate, AtomicLong successCount, List errMsg) { try { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java index 1ff406fd..2edc396b 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -13,7 +13,7 @@ import com.alibaba.otter.canal.client.adapter.support.Dml; /** * HBase同步操作业务 * - * @author machengyuan 2018-8-21 下午06:45:49 + * @author rewerma 2018-8-21 下午06:45:49 * @version 1.0.0 */ public class HbaseSyncService { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java index 56bbde8e..99140b0c 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java @@ -3,6 +3,12 @@ package com.alibaba.otter.canal.adapter.launcher; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; +/** + * 启动入口 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @SpringBootApplication public class CanalAdapterApplication { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java index 67d0ac41..5fd5f3fe 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/EtlLock.java @@ -14,6 +14,12 @@ import org.springframework.stereotype.Component; import com.alibaba.otter.canal.adapter.launcher.config.CuratorClient; +/** + * Etl 同步锁 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Component public class EtlLock { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java index d96c753e..aa6e55bc 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java @@ -19,16 +19,22 @@ import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; import com.alibaba.otter.canal.adapter.launcher.config.CuratorClient; import com.alibaba.otter.canal.common.utils.BooleanMutex; +/** + * 同步开关 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Component public class SyncSwitch { private static final String SYN_SWITCH_ZK_NODE = "/sync-switch/"; - private static final Map LOCAL_LOCK = new ConcurrentHashMap<>(); + private static final Map LOCAL_LOCK = new ConcurrentHashMap<>(); - private static final Map DISTRIBUTED_LOCK = new ConcurrentHashMap<>(); + private static final Map DISTRIBUTED_LOCK = new ConcurrentHashMap<>(); - private static Mode mode = Mode.LOCAL; + private static Mode mode = Mode.LOCAL; @Resource private AdapterCanalConfig adapterCanalConfig; @@ -204,5 +210,4 @@ public class SyncSwitch { } } - } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index 18a12d7c..2579e1f7 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -9,6 +9,12 @@ import org.springframework.stereotype.Component; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; +/** + * canal 的相关配置类 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Component @ConfigurationProperties(prefix = "canal.conf") public class AdapterCanalConfig extends CanalClientConfig { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java index da37804f..f3e265f8 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java @@ -13,6 +13,12 @@ import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +/** + * 适配器数据源及配置文件列表配置类 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Component @ConfigurationProperties(prefix = "adapter.conf") public class AdapterConfig { @@ -55,7 +61,7 @@ public class AdapterConfig { try { ds.init(); } catch (SQLException e) { - logger.error("#Failed to initial datasource: " + datasourceConfig.getUrl(), e); + logger.error("ERROR ## failed to initial datasource: " + datasourceConfig.getUrl(), e); } DatasourceConfig.DATA_SOURCES.put(entry.getKey(), ds); } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java index b7944852..0ec60392 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/CuratorClient.java @@ -8,6 +8,12 @@ import org.apache.curator.framework.CuratorFrameworkFactory; import org.apache.curator.retry.ExponentialBackoffRetry; import org.springframework.stereotype.Component; +/** + * curator 配置类 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Component public class CuratorClient { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java index ac860bea..987371e2 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/SpringContext.java @@ -5,6 +5,12 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; +/** + * spring util配置类 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Component public class SpringContext implements ApplicationContextAware { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index bd3a47a4..cd818a9d 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -21,7 +21,7 @@ import com.alibaba.otter.canal.protocol.Message; /** * 适配器工作线程抽象类 * - * @author machengyuan 2018-8-19 下午11:30:49 + * @author rewerma 2018-8-19 下午11:30:49 * @version 1.0.0 */ public abstract class AbstractCanalAdapterWorker { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 22272e80..4c48125a 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -17,7 +17,7 @@ import com.alibaba.otter.canal.protocol.Message; /** * kafka对应的client适配器工作线程 * - * @author machengyuan 2018-8-19 下午11:30:49 + * @author rewerma 2018-8-19 下午11:30:49 * @version 1.0.0 */ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index 09863a33..4632d7f0 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -16,7 +16,7 @@ import com.alibaba.otter.canal.protocol.Message; /** * kafka对应的client适配器工作线程 * - * @author machengyuan 2018-8-19 下午11:30:49 + * @author rewerma 2018-8-19 下午11:30:49 * @version 1.0.0 */ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java index 03f515bf..2e535535 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java @@ -15,6 +15,12 @@ import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; import com.alibaba.otter.canal.adapter.launcher.config.AdapterConfig; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +/** + * 适配器启动业务类 + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @Component public class CanalAdapterService { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 1c324996..c728a15a 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -15,7 +15,7 @@ import com.alibaba.otter.canal.protocol.Message; /** * 原生canal-server对应的client适配器工作线程 * - * @author machengyuan 2018-8-19 下午11:30:49 + * @author rewrema 2018-8-19 下午11:30:49 * @version 1.0.0 */ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index bee78e04..d2c163fb 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -17,6 +17,12 @@ import com.alibaba.otter.canal.client.adapter.support.EtlResult; import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; import com.alibaba.otter.canal.client.adapter.support.Result; +/** + * 适配器操作Rest + * + * @author rewerma @ 2018-10-20 + * @version 1.0.0 + */ @RestController public class CommonRest { From 848cbe11dd024c8226544dbf59fed137a25e39e1 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 26 Oct 2018 10:33:16 +0800 Subject: [PATCH 202/360] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loader/AbstractCanalAdapterWorker.java | 120 ++++++++++-------- .../loader/CanalAdapterKafkaWorker.java | 49 +------ .../launcher/loader/CanalAdapterLoader.java | 14 +- .../loader/CanalAdapterRocketMQWorker.java | 68 ++++------ .../launcher/loader/CanalAdapterWorker.java | 55 +------- 5 files changed, 106 insertions(+), 200 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index cd818a9d..0486045c 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -2,20 +2,19 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; -import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; -import com.alibaba.otter.canal.client.adapter.support.Dml; -import com.alibaba.otter.canal.protocol.FlatMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; +import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.MessageUtil; +import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.Message; /** @@ -44,21 +43,21 @@ public abstract class AbstractCanalAdapterWorker { protected void writeOut(final Message message) { List> futures = new ArrayList<>(); // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { + canalOuterAdapters.forEach(outerAdapters -> { final List adapters = outerAdapters; futures.add(groupInnerExecutorService.submit(() -> { try { // 组内适配器穿行运行,尽量不要配置组内适配器 - for (final OuterAdapter c : adapters) { + adapters.forEach(adapter -> { long begin = System.currentTimeMillis(); - MessageUtil.parse4Dml(canalDestination, message, c::sync); + MessageUtil.parse4Dml(canalDestination, message, adapter::sync); if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", - c.getClass().getName(), + adapter.getClass().getName(), (System.currentTimeMillis() - begin)); } - } + }); return true; } catch (Exception e) { return false; @@ -67,79 +66,94 @@ public abstract class AbstractCanalAdapterWorker { // 等待所有适配器写入完成 // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 - for (Future f : futures) { + futures.forEach(future -> { try { - if (!f.get()) { + if (!future.get()) { logger.error("Outer adapter write failed"); } } catch (InterruptedException | ExecutionException e) { // ignore } - } - } + }); + }); } protected void writeOut(final FlatMessage flatMessage) { List> futures = new ArrayList<>(); // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { - final List adapters = outerAdapters; - futures.add(groupInnerExecutorService.submit(new Callable() { - - @Override - public Boolean call() { - try { - // 组内适配器穿行运行,尽量不要配置组内适配器 - for (OuterAdapter c : adapters) { - long begin = System.currentTimeMillis(); - Dml dml = MessageUtil.flatMessage2Dml(canalDestination, flatMessage); - c.sync(dml); - if (logger.isDebugEnabled()) { - logger.debug("{} elapsed time: {}", - c.getClass().getName(), - (System.currentTimeMillis() - begin)); - } + canalOuterAdapters.forEach(outerAdapters -> { + futures.add(groupInnerExecutorService.submit(() -> { + try { + // 组内适配器穿行运行,尽量不要配置组内适配器 + outerAdapters.forEach(adapter -> { + long begin = System.currentTimeMillis(); + Dml dml = MessageUtil.flatMessage2Dml(canalDestination, flatMessage); + adapter.sync(dml); + if (logger.isDebugEnabled()) { + logger.debug("{} elapsed time: {}", + adapter.getClass().getName(), + (System.currentTimeMillis() - begin)); } - return true; - } catch (Exception e) { - return false; - } + }); + return true; + } catch (Exception e) { + return false; } })); // 等待所有适配器写入完成 // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 - for (Future f : futures) { + futures.forEach(future -> { try { - if (!f.get()) { + if (!future.get()) { logger.error("Outer adapter write failed"); } } catch (InterruptedException | ExecutionException e) { // ignore } - } + }); + }); + } + + public void start() { + if (!running) { + thread = new Thread(this::process); + thread.setUncaughtExceptionHandler(handler); + thread.start(); + running = true; } } - protected void stopOutAdapters() { - if (thread != null) { - try { - thread.join(); - } catch (InterruptedException e) { - // ignore + protected abstract void process(); + + public void stop() { + try { + if (!running) { + return; } - } - groupInnerExecutorService.shutdown(); - logger.info("topic connectors' worker thread dead!"); - for (List outerAdapters : canalOuterAdapters) { - for (OuterAdapter adapter : outerAdapters) { - adapter.destroy(); + + closeConnection(); + running = false; + + syncSwitch.release(canalDestination); + + logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination); + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } } + groupInnerExecutorService.shutdown(); + logger.info("destination {} adapters worker thread dead!", canalDestination); + canalOuterAdapters.forEach(outerAdapters -> outerAdapters.forEach(OuterAdapter::destroy)); + logger.info("destination {} all adapters destroyed!", canalDestination); + } catch (Exception e) { + logger.error(e.getMessage(), e); } - logger.info("topic all connectors destroyed!"); } - public abstract void start(); + protected abstract void closeConnection(); - public abstract void stop(); } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 4c48125a..665ad38e 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -37,59 +37,18 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { this.flatMessage = flatMessage; connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage); // connector.setSessionTimeout(1L, TimeUnit.MINUTES); - - // super.initSwitcher(topic); } @Override - public void start() { - if (!running) { - thread = new Thread(() -> process()); - thread.setUncaughtExceptionHandler(handler); - running = true; - thread.start(); - } + protected void closeConnection(){ + connector.stopRunning(); } @Override - public void stop() { - try { - if (!running) { - return; - } - - connector.stopRunning(); - running = false; - - // if (switcher != null && !switcher.state()) { - // switcher.set(true); - // } - - if (thread != null) { - try { - thread.join(); - } catch (InterruptedException e) { - // ignore - } - } - groupInnerExecutorService.shutdown(); - logger.info("topic {} connectors' worker thread dead!", this.topic); - for (List outerAdapters : canalOuterAdapters) { - for (OuterAdapter adapter : outerAdapters) { - adapter.destroy(); - } - } - logger.info("topic {} all connectors destroyed!", this.topic); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - private void process() { + protected void process() { while (!running) ; ExecutorService executor = Executors.newSingleThreadExecutor(); - // final AtomicBoolean executing = new AtomicBoolean(true); while (running) { try { logger.info("=============> Start to connect topic: {} <=============", this.topic); @@ -99,7 +58,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { logger.info("=============> Subscribe topic: {} succeed <=============", this.topic); while (running) { try { - // switcher.get(); //等待开关开启 + syncSwitch.get(canalDestination); List messages; if (!flatMessage) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 3bb5b4a4..0447036a 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -135,20 +135,18 @@ public class CanalAdapterLoader { public void destroy() { if (canalWorkers.size() > 0) { ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size()); - for (CanalAdapterWorker v : canalWorkers.values()) { - final CanalAdapterWorker caw = v; - stopExecutorService.submit(caw::stop); + for (CanalAdapterWorker canalAdapterWorker : canalWorkers.values()) { + stopExecutorService.submit(canalAdapterWorker::stop); } stopExecutorService.shutdown(); } if (canalMQWorker.size() > 0) { - ExecutorService stopMQWokerService = Executors.newFixedThreadPool(canalMQWorker.size()); - for (AbstractCanalAdapterWorker tmp : canalMQWorker.values()) { - final AbstractCanalAdapterWorker worker = tmp; - stopMQWokerService.submit(worker::stop); + ExecutorService stopMQWorkerService = Executors.newFixedThreadPool(canalMQWorker.size()); + for (AbstractCanalAdapterWorker canalAdapterMQWorker : canalMQWorker.values()) { + stopMQWorkerService.submit(canalAdapterMQWorker::stop); } - stopMQWokerService.shutdown(); + stopMQWorkerService.shutdown(); } logger.info("All canal adapters destroyed"); } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index 4632d7f0..f4c2e704 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -36,38 +36,12 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { } @Override - public void start() { - if (!running) { - thread = new Thread(new Runnable() { - - @Override - public void run() { - process(); - } - }); - thread.setUncaughtExceptionHandler(handler); - running = true; - thread.start(); - } + protected void closeConnection() { + connector.stopRunning(); } @Override - public void stop() { - try { - if (!running) { - return; - } - connector.stopRunning(); - running = false; - logger.info("Stop topic {} out adapters begin", this.topic); - stopOutAdapters(); - logger.info("Stop topic {} out adapters end", this.topic); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - private void process() { + protected void process() { while (!running) ; ExecutorService executor = Executors.newSingleThreadExecutor(); @@ -84,25 +58,27 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { final Message message = connector.getWithoutAck(1); if (message != null) { - executor.submit(new Runnable() { - - @Override - public void run() { - try { - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId: {} batchSize: {} ", topic, message.getId(), message.getEntries().size()); - } - long begin = System.currentTimeMillis(); - writeOut(message); - long now = System.currentTimeMillis(); - if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { - logger.error("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); + executor.submit(() -> { + try { + if (logger.isDebugEnabled()) { + logger.debug("topic: {} batchId: {} batchSize: {} ", + topic, + message.getId(), + message.getEntries().size()); } - connector.ack(message.getId()); + long begin = System.currentTimeMillis(); + writeOut(message); + long now = System.currentTimeMillis(); + if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { + logger.error("topic: {} batchId {} elapsed time: {} ms", + topic, + message.getId(), + now - begin); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); } + connector.ack(message.getId()); }); } else { logger.debug("Message is null"); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index c728a15a..33a70f89 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -57,49 +57,12 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { } @Override - public void start() { - if (!running) { - thread = new Thread(this::process); - thread.setUncaughtExceptionHandler(handler); - thread.start(); - running = true; - } + protected void closeConnection() { + connector.stopRunning(); } @Override - public void stop() { - try { - if (!running) { - return; - } - - connector.stopRunning(); - running = false; - - syncSwitch.release(canalDestination); - - logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination); - if (thread != null) { - try { - thread.join(); - } catch (InterruptedException e) { - // ignore - } - } - groupInnerExecutorService.shutdown(); - logger.info("destination {} adapters' worker thread dead!", canalDestination); - for (List outerAdapters : canalOuterAdapters) { - for (OuterAdapter adapter : outerAdapters) { - adapter.destroy(); - } - } - logger.info("destination {} all adapters destroyed!", canalDestination); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - private void process() { + protected void process() { while (!running) ; // waiting until running == true while (running) { @@ -129,11 +92,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { int size = message.getEntries().size(); if (batchId == -1 || size == 0) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // ignore - } + Thread.sleep(1000); } else { if (logger.isDebugEnabled()) { logger.debug("destination: {} batchId: {} batchSize: {} ", @@ -143,18 +102,18 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { } long begin = System.currentTimeMillis(); writeOut(message); - long now = System.currentTimeMillis(); if (logger.isDebugEnabled()) { logger.debug("destination: {} batchId: {} elapsed time: {} ms", this.canalDestination, batchId, - now - begin); + System.currentTimeMillis() - begin); } } connector.ack(batchId); // 提交确认 } catch (Exception e) { connector.rollback(batchId); // 处理失败, 回滚数据 - throw e; + logger.error("sync error!", e); + Thread.sleep(500); } } From bbfd4b499662c91662acdb81cd2439c0844a6a0a Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 26 Oct 2018 13:23:22 +0800 Subject: [PATCH 203/360] canal-adapter readme.md --- client-adapter/README.md | 251 ++++++++++++++++++ .../main/resources/hbase/mytest_person2.yml | 2 +- 2 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 client-adapter/README.md diff --git a/client-adapter/README.md b/client-adapter/README.md new file mode 100644 index 00000000..47a5dcc4 --- /dev/null +++ b/client-adapter/README.md @@ -0,0 +1,251 @@ +## 基本说明 +canal 1.1.1版本之后, 增加客户端数据落地的适配及启动功能, 目前支持功能: +* 客户端启动器 +* 同步管理REST接口 +* 日志适配器, 作为DEMO +* HBase的数据同步(表对表同步), ETL功能 +* (后续支持) ElasticSearch多表数据同步,ETL功能 + +## 环境版本 +* 操作系统:无要求 +* java版本: jdk1.8 以上 +* canal 版本: 请下载最新的安装包,本文以当前v1.1.1 的canal.deployer-1.1.1.tar.gz为例 +* MySQL版本 :5.7.18 +* HBase版本: Apache HBase 1.1.2, 若和服务端版本不一致可自行替换客户端HBase依赖 + +## 一、适配器启动器 +client-adapter分为适配器和启动器两部分, 适配器为多个fat jar, 每个适配器会将自己所需的依赖打成一个包, 以SPI的方式让启动器动态加载 + + +启动器为 SpringBoot 项目, 支持canal-client启动的同时提供相关REST管理接口, 运行目录结构为: +``` +canal-adapter-launcher.jar +- lib + client-adapter.logger-1.1.1-jar-with-dependencies.jar + client-adapter.hbase-1.1.1-jar-with-dependencies.jar +- config + application.yml + - hbase + mytest_person2.yml +``` +以上目录结构最终会打包成 canal-adapter-launcher.tar.gz 压缩包 + +## 二、启动器 +### 2.1 启动器配置 application.yml +#### canal相关配置部分说明 +``` +canal.conf: + canalServerHost: 127.0.0.1:11111 # 对应单机模式下的canal server的ip:port + zookeeperHosts: slave1:2181 # 对应集群模式下的zk地址, 如果配置了canalServerHost, 则以canalServerHost为准 + bootstrapServers: slave1:6667 # kafka或rocketMQ地址, 与canalServerHost不能并存 + flatMessage: true # 扁平message开关, 是否以json字符串形式投递数据, 仅在kafka/rocketMQ模式下有效 + canalInstances: # canal实例组, 如果是tcp模式可配置此项 + - instance: example # 对应canal destination + adapterGroups: # 对应适配器分组, 分组间的适配器并行运行 + - outAdapters: # 适配器列表, 分组内的适配串行运行 + - name: logger # 适配器SPI名 + - name: hbase + properties: # HBase相关连接参数 + hbase.zookeeper.quorum: slave1 + hbase.zookeeper.property.clientPort: 2181 + zookeeper.znode.parent: /hbase + mqTopics: # MQ topic租, 如果是kafka或者rockeMQ模式可配置此项, 与canalInstances不能并存 + - mqMode: kafka # MQ的模式: kafak/rocketMQ + topic: example # MQ topic + groups: # group组 + - groupId: g2 # group id + outAdapters: # 适配器列表, 以下配置和canalInstances中一样 + - name: logger +``` +#### 适配器相关配置部分说明 +``` +adapter.conf: + datasourceConfigs: # 数据源配置列表, 数据源将在适配器中用于ETL、数据同步回查等使用 + defaultDS: # 数据源 dataSourceKey, 适配器中通过该值获取指定数据源 + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 + adapterConfigs: # 适配器内部配置列表 + - hbase/mytest_person2.yml # 类型/配置文件名, 这里示例就是对应HBase适配器hbase目录下的mytest_person2.yml文件 +``` +## 2.2 适配器管理REST接口 +#### 2.2.1 查询所有订阅同步的canal destination或MQ topic +``` +curl http://127.0.0.1:8081/destinations +``` +#### 2.2.2 数据同步开关 +``` +curl http://127.0.0.1:8081/syncSwitch/example/off -X PUT +``` +针对 example 这个canal destination/MQ topic 进行开关操作. off代表关闭, 该destination/topic下的同步将阻塞或者断开连接不再接收数据, on代表开启 + +注: 如果在配置文件中配置了 zookeeperHosts 项, 则会使用分布式锁来控制HA中的数据同步开关, 如果是单机模式则使用本地锁来控制开关 +#### 2.2.3 数据同步开关状态 +``` +curl http://127.0.0.1:8081/syncSwitch/example +``` +查看指定 canal destination/MQ topic 的数据同步开关状态 +#### 2.2.4 手动ETL +``` +curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST -d "params=2018-10-21 00:00:00" +``` +导入数据到指定类型的库 +#### 2.2.5 查看相关库总数据 +``` +curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml +``` +### 2.3 启动canal-adapter示例 +#### 2.3.1 启动canal server (单机模式), 参考: [Canal QuickStart](https://github.com/alibaba/canal/wiki/QuickStart) +#### 2.3.2 修改config/application.yml为: +``` +server: + port: 8081 +logging: + level: + com.alibaba.otter.canal.client.adapter.hbase: DEBUG +spring: + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + +canal.conf: + canalServerHost: 127.0.0.1:11111 + flatMessage: true + canalInstances: + - instance: example + adapterGroups: + - outAdapters: + - name: logger +``` +启动 canal-adapter-launcher.jar +``` +java -jar canal-adapter-launcher.jar +``` + +## 三、HBase适配器 +### 3.1 修改启动器配置: application.yml +``` +server: + port: 8081 +logging: + level: + com.alibaba.otter.canal.client.adapter.hbase: DEBUG +spring: + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + +canal.conf: + canalServerHost: 127.0.0.1:11111 + flatMessage: true + canalInstances: + - instance: example + adapterGroups: + - outAdapters: + - name: hbase + properties: + hbase.zookeeper.quorum: slave1 + hbase.zookeeper.property.clientPort: 2181 + zookeeper.znode.parent: /hbase + +adapter.conf: + datasourceConfigs: + defaultDS: + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 + adapterConfigs: + - hbase/mytest_person.yml +``` +其中指定了一个HBase表映射文件: mytest_person.yml +### 3.2 适配器表映射文件 +修改 config/hbase/mytest_person.yml文件: +``` +dataSourceKey: defaultDS # 对应application.yml中的datasourceConfigs下的配置 +hbaseOrm: # mysql--HBase的单表映射配置 + mode: STRING # HBase中的存储类型, 默认统一存为String, 可选: #PHOENIX #NATIVE #STRING + # NATIVE: 以java类型为主, PHOENIX: 将类型转换为Phoenix对应的类型 + destination: example # 对应 canal destination/MQ topic 名称 + database: mytest # 数据库名/schema名 + table: person # 表名 + hbaseTable: MYTEST.PERSON # HBase表名 + family: CF # 默认统一Column Family名称 + uppercaseQualifier: true # 字段名转大写, 默认为true + commitBatch: 3000 # 批量提交的大小, ETL中用到 + #rowKey: id,type # 复合字段rowKey不能和columns中的rowKey并存 + # 复合rowKey会以 '|' 分隔 + columns: # 字段映射, 如果不配置将自动映射所有字段, + # 并取第一个字段为rowKey, HBase字段名以mysql字段名为主 + id: ROWKE + name: CF:NAME + email: EMAIL # 如果column family为默认CF, 则可以省略 + type: # 如果HBase字段和mysql字段名一致, 则可以省略 + c_time: + birthday: +``` +如果涉及到类型转换,可以如下形式: +``` +... + columns: + id: ROWKE$STRING + ... + type: TYPE$BYTE + ... +``` +类型转换涉及到Java类型和Phoenix类型两种, 分别定义如下: +``` +#Java 类型转换, 对应配置 mode: NATIVE +$DEFAULT +$STRING +$INTEGER +$LONG +$SHORT +$BOOLEAN +$FLOAT +$DOUBLE +$BIGDECIMAL +$DATE +$BYTE +$BYTES +``` +``` +#Phoenix 类型转换, 对应配置 mode: PHOENIX +$DEFAULT 对应PHOENIX里的VARCHAR +$UNSIGNED_INT 对应PHOENIX里的UNSIGNED_INT 4字节 +$UNSIGNED_LONG 对应PHOENIX里的UNSIGNED_LONG 8字节 +$UNSIGNED_TINYINT 对应PHOENIX里的UNSIGNED_TINYINT 1字节 +$UNSIGNED_SMALLINT 对应PHOENIX里的UNSIGNED_SMALLINT 2字节 +$UNSIGNED_FLOAT 对应PHOENIX里的UNSIGNED_FLOAT 4字节 +$UNSIGNED_DOUBLE 对应PHOENIX里的UNSIGNED_DOUBLE 8字节 +$INTEGER 对应PHOENIX里的INTEGER 4字节 +$BIGINT 对应PHOENIX里的BIGINT 8字节 +$TINYINT 对应PHOENIX里的TINYINT 1字节 +$SMALLINT 对应PHOENIX里的SMALLINT 2字节 +$FLOAT 对应PHOENIX里的FLOAT 4字节 +$DOUBLE 对应PHOENIX里的DOUBLE 8字节 +$BOOLEAN 对应PHOENIX里的BOOLEAN 1字节 +$TIME 对应PHOENIX里的TIME 8字节 +$DATE 对应PHOENIX里的DATE 8字节 +$TIMESTAMP 对应PHOENIX里的TIMESTAMP 12字节 +$UNSIGNED_TIME 对应PHOENIX里的UNSIGNED_TIME 8字节 +$UNSIGNED_DATE 对应PHOENIX里的UNSIGNED_DATE 8字节 +$UNSIGNED_TIMESTAMP 对应PHOENIX里的UNSIGNED_TIMESTAMP 12字节 +$VARCHAR 对应PHOENIX里的VARCHAR 动态长度 +$VARBINARY 对应PHOENIX里的VARBINARY 动态长度 +$DECIMAL 对应PHOENIX里的DECIMAL 动态长度 +``` +如果不配置将以java对象原生类型默认映射转换 +### 3.3 启动HBase数据同步 +#### 创建HBase表 +在HBase shell中运行: +``` +create 'MYTEST.PERSON', {NAME=>'CF'} +``` +#### 启动canal-adapter启动器 +``` +java -jar canal-adapter-launcher.jar +``` +#### 验证 +修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log \ No newline at end of file diff --git a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml index 88cd99e2..b22ffe5c 100644 --- a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml +++ b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml @@ -45,7 +45,7 @@ hbaseOrm: # $TINYINT 对应PHOENIX里的TINYINT 1字节 # $SMALLINT 对应PHOENIX里的SMALLINT 2字节 # $FLOAT 对应PHOENIX里的FLOAT 4字节 -# DOUBLE 对应PHOENIX里的DOUBLE 8字节 +# $DOUBLE 对应PHOENIX里的DOUBLE 8字节 # $BOOLEAN 对应PHOENIX里的BOOLEAN 1字节 # $TIME 对应PHOENIX里的TIME 8字节 # $DATE 对应PHOENIX里的DATE 8字节 From 3b1f6ce4fbb0ab00a82d097093d2b7c8958426f3 Mon Sep 17 00:00:00 2001 From: rewerma Date: Fri, 26 Oct 2018 13:25:40 +0800 Subject: [PATCH 204/360] Update README.md --- client-adapter/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client-adapter/README.md b/client-adapter/README.md index 47a5dcc4..642bf50b 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -224,7 +224,7 @@ $BIGINT 对应PHOENIX里的BIGINT 8字节 $TINYINT 对应PHOENIX里的TINYINT 1字节 $SMALLINT 对应PHOENIX里的SMALLINT 2字节 $FLOAT 对应PHOENIX里的FLOAT 4字节 -$DOUBLE 对应PHOENIX里的DOUBLE 8字节 +$DOUBLE 对应PHOENIX里的DOUBLE 8字节 $BOOLEAN 对应PHOENIX里的BOOLEAN 1字节 $TIME 对应PHOENIX里的TIME 8字节 $DATE 对应PHOENIX里的DATE 8字节 @@ -248,4 +248,4 @@ create 'MYTEST.PERSON', {NAME=>'CF'} java -jar canal-adapter-launcher.jar ``` #### 验证 -修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log \ No newline at end of file +修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log From 5cfbc6b91d0e55baba1fe525c8c3f3d4f63dd82a Mon Sep 17 00:00:00 2001 From: rewerma Date: Fri, 26 Oct 2018 13:36:59 +0800 Subject: [PATCH 205/360] Update README.md --- client-adapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-adapter/README.md b/client-adapter/README.md index 642bf50b..31d6b5a1 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -68,7 +68,7 @@ adapter.conf: adapterConfigs: # 适配器内部配置列表 - hbase/mytest_person2.yml # 类型/配置文件名, 这里示例就是对应HBase适配器hbase目录下的mytest_person2.yml文件 ``` -## 2.2 适配器管理REST接口 +## 2.2 同步管理REST接口 #### 2.2.1 查询所有订阅同步的canal destination或MQ topic ``` curl http://127.0.0.1:8081/destinations From 0d0fcbbc0b885d577ebe08eed33ae1b9e413e0fc Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 26 Oct 2018 14:03:29 +0800 Subject: [PATCH 206/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9HBase=E9=80=82?= =?UTF-8?q?=E9=85=8D=E5=99=A8=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/hbase/config/MappingConfig.java | 9 ++++++ .../hbase/service/HbaseSyncService.java | 6 ++++ .../main/resources/hbase/mytest_person2.yml | 2 ++ .../src/main/resources/application.yml | 28 +++++++++---------- 4 files changed, 31 insertions(+), 14 deletions(-) diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java index 8027fb51..93968610 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -152,6 +152,7 @@ public class MappingConfig { private boolean autoCreateTable = false; // 同步时HBase中表不存在的情况下自动建表 private String rowKey; // 指定复合主键为rowKey private Map columns; // 字段映射 + private List excludeColumns; // 不映射的字段 private ColumnItem rowKeyColumn; // rowKey字段 private String etlCondition; // etl条件sql @@ -292,6 +293,14 @@ public class MappingConfig { } } + public List getExcludeColumns() { + return excludeColumns; + } + + public void setExcludeColumns(List excludeColumns) { + this.excludeColumns = excludeColumns; + } + public String getFamily() { return family; } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java index 2edc396b..4f31d0d3 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -110,6 +110,9 @@ public class HbaseSyncService { Map columnItems = hbaseOrm.getColumnItems(); int i = 0; for (Map.Entry entry : data.entrySet()) { + if (hbaseOrm.getExcludeColumns() != null && hbaseOrm.getExcludeColumns().contains(entry.getKey())) { + continue; + } if (entry.getValue() != null) { MappingConfig.ColumnItem columnItem = columnItems.get(entry.getKey()); @@ -196,6 +199,9 @@ public class HbaseSyncService { Map columnItems = hbaseOrm.getColumnItems(); HRow hRow = new HRow(rowKeyBytes); for (String updateColumn : old.get(index).keySet()) { + if (hbaseOrm.getExcludeColumns() != null && hbaseOrm.getExcludeColumns().contains(updateColumn)) { + continue; + } MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn); if (columnItem == null) { String family = hbaseOrm.getFamily(); diff --git a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml index b22ffe5c..d80b95eb 100644 --- a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml +++ b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml @@ -17,6 +17,8 @@ hbaseOrm: type: $DECIMAL c_time: C_TIME$UNSIGNED_TIMESTAMP birthday: BIRTHDAY$DATE + excludeColumns: + - lat # -- NATIVE类型 # $DEFAULT diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 43cc4e38..1b1139c1 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -23,12 +23,12 @@ canal.conf: - instance: example adapterGroups: - outAdapters: - - name: logger -# - name: hbase -# properties: -# hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} -# hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort} -# zookeeper.znode.parent: ${hbase.zookeeper.znode.parent} +# - name: logger + - name: hbase + properties: + hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} + hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort} + zookeeper.znode.parent: ${hbase.zookeeper.znode.parent} # mqTopics: # - mqMode: kafka # topic: example @@ -37,11 +37,11 @@ canal.conf: # outAdapters: # - name: logger -#adapter.conf: -# datasourceConfigs: -# defaultDS: -# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true -# username: root -# password: 121212 -# adapterConfigs: -# - hbase/mytest_person2.yml +adapter.conf: + datasourceConfigs: + defaultDS: + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 + adapterConfigs: + - hbase/mytest_person2.yml From fb936f896eb6d2b33c1e3ad6a95c6ddfe4b26ffa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 26 Oct 2018 14:57:52 +0800 Subject: [PATCH 207/360] fixed client-launcher deploy --- client-adapter/example/pom.xml | 149 -------------- .../example/src/main/assembly/dev.xml | 57 ------ .../example/src/main/assembly/release.xml | 57 ------ .../example/src/main/bin/startup.bat | 25 --- .../example/src/main/bin/startup.sh | 100 ---------- client-adapter/example/src/main/bin/stop.sh | 65 ------ .../otter/canal/client/ClientLauncher.java | 66 ------- .../loader/AbstractCanalAdapterWorker.java | 168 ---------------- .../loader/CanalAdapterKafkaWorker.java | 178 ----------------- .../adapter/loader/CanalAdapterLoader.java | 177 ----------------- .../loader/CanalAdapterRocketMQWorker.java | 124 ------------ .../adapter/loader/CanalAdapterWorker.java | 186 ------------------ .../src/main/resources/canal-client.yml | 18 -- .../example/src/main/resources/logback.xml | 47 ----- client-adapter/hbase/pom.xml | 98 +++++---- client-adapter/launcher/pom.xml | 67 +++++-- .../main/assembly/release.xml} | 0 client-adapter/logger/pom.xml | 58 +++--- client-adapter/pom.xml | 1 - 19 files changed, 139 insertions(+), 1502 deletions(-) delete mode 100644 client-adapter/example/pom.xml delete mode 100644 client-adapter/example/src/main/assembly/dev.xml delete mode 100644 client-adapter/example/src/main/assembly/release.xml delete mode 100644 client-adapter/example/src/main/bin/startup.bat delete mode 100644 client-adapter/example/src/main/bin/startup.sh delete mode 100644 client-adapter/example/src/main/bin/stop.sh delete mode 100644 client-adapter/example/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java delete mode 100644 client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java delete mode 100644 client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java delete mode 100644 client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java delete mode 100644 client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java delete mode 100644 client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java delete mode 100644 client-adapter/example/src/main/resources/canal-client.yml delete mode 100644 client-adapter/example/src/main/resources/logback.xml rename client-adapter/launcher/{assembly.xml => src/main/assembly/release.xml} (100%) diff --git a/client-adapter/example/pom.xml b/client-adapter/example/pom.xml deleted file mode 100644 index bf68d731..00000000 --- a/client-adapter/example/pom.xml +++ /dev/null @@ -1,149 +0,0 @@ - - - - canal.client-adapter - com.alibaba.otter - 1.1.1-SNAPSHOT - - 4.0.0 - com.alibaba.otter - client-adapter.example - jar - canal client adapter example module for otter ${project.version} - - - - com.alibaba.otter - client-adapter.common - ${project.version} - - - com.alibaba.otter - canal.client - ${canal_version} - - - - org.apache.rocketmq - rocketmq-client - 4.3.0 - - - - org.apache.kafka - kafka-clients - 1.1.1 - - - org.yaml - snakeyaml - 1.17 - - - - - com.alibaba.otter - client-adapter.logger - ${project.version} - - - - - - - - maven-jar-plugin - 3.0.2 - - - true - - - **/logback.xml - **/canal-client.yml - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - 2.2.1 - - - assemble - - single - - package - - - - false - false - - - - - - - - dev - - true - - env - !release - - - - - - - maven-assembly-plugin - - - - ${basedir}/src/main/assembly/dev.xml - - canal_client - ${project.build.directory} - - - - - - - - - release - - - env - release - - - - - - - maven-assembly-plugin - - - - ${basedir}/src/main/assembly/release.xml - - - ${project.artifactId}-${project.version} - - ${project.parent.build.directory} - - - - - - - diff --git a/client-adapter/example/src/main/assembly/dev.xml b/client-adapter/example/src/main/assembly/dev.xml deleted file mode 100644 index a3cbc20c..00000000 --- a/client-adapter/example/src/main/assembly/dev.xml +++ /dev/null @@ -1,57 +0,0 @@ - - dist - - dir - - false - - - . - / - - README* - - - - ./src/main/bin - bin - - **/* - - 0755 - - - ./src/main/conf - /conf - - **/* - - - - ./src/main/resources - /conf - - **/* - - - META-INF/** - - - - target - logs - - **/* - - - - - - lib - - junit:junit - - - - diff --git a/client-adapter/example/src/main/assembly/release.xml b/client-adapter/example/src/main/assembly/release.xml deleted file mode 100644 index 9350da6e..00000000 --- a/client-adapter/example/src/main/assembly/release.xml +++ /dev/null @@ -1,57 +0,0 @@ - - dist - - tar.gz - - false - - - . - / - - README* - - - - ./src/main/bin - bin - - **/* - - 0755 - - - ./src/main/conf - /conf - - **/* - - - - ./src/main/resources - /conf - - **/* - - - META-INF/** - - - - target - logs - - **/* - - - - - - lib - - junit:junit - - - - diff --git a/client-adapter/example/src/main/bin/startup.bat b/client-adapter/example/src/main/bin/startup.bat deleted file mode 100644 index 33fd7945..00000000 --- a/client-adapter/example/src/main/bin/startup.bat +++ /dev/null @@ -1,25 +0,0 @@ -@echo off -@if not "%ECHO%" == "" echo %ECHO% -@if "%OS%" == "Windows_NT" setlocal - -set ENV_PATH=.\ -if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% - -set conf_dir=%ENV_PATH%\..\conf -set canal_conf=%conf_dir%\canal-client.yml -set logback_configurationFile=%conf_dir%\logback.xml - -set CLASSPATH=%conf_dir% -set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% - -set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m -set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 -set JAVA_DEBUG_OPT= -server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9099,server=y,suspend=n -set CANAL_OPTS= -DappName=otter-canal -Dlogback.configurationFile="%logback_configurationFile%" -Dcanal.conf="%canal_conf%" - -set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %JAVA_DEBUG_OPT% %CANAL_OPTS% - -set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher -echo start cmd : %CMD_STR% - -java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.client.ClientLauncher \ No newline at end of file diff --git a/client-adapter/example/src/main/bin/startup.sh b/client-adapter/example/src/main/bin/startup.sh deleted file mode 100644 index 0e09ddff..00000000 --- a/client-adapter/example/src/main/bin/startup.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash - -current_path=`pwd` -case "`uname`" in - Linux) - bin_abs_path=$(readlink -f $(dirname $0)) - ;; - *) - bin_abs_path=`cd $(dirname $0); pwd` - ;; -esac -base=${bin_abs_path}/.. -canal_conf=$base/conf/canal-client.yml -logback_configurationFile=$base/conf/logback.xml -export LANG=en_US.UTF-8 -export BASE=$base - -if [ -f $base/bin/canal_client.pid ] ; then - echo "found canal_client.pid , Please run stop.sh first ,then startup.sh" 2>&2 - exit 1 -fi - -## set java path -if [ -z "$JAVA" ] ; then - JAVA=$(which java) -fi - -ALIBABA_JAVA="/usr/alibaba/java/bin/java" -TAOBAO_JAVA="/opt/taobao/java/bin/java" -if [ -z "$JAVA" ]; then - if [ -f $ALIBABA_JAVA ] ; then - JAVA=$ALIBABA_JAVA - elif [ -f $TAOBAO_JAVA ] ; then - JAVA=$TAOBAO_JAVA - else - echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2 - exit 1 - fi -fi - -case "$#" -in -0 ) - ;; -1 ) - var=$* - if [ -f $var ] ; then - canal_conf=$var - else - echo "THE PARAMETER IS NOT CORRECT.PLEASE CHECK AGAIN." - exit - fi;; -2 ) - var=$1 - if [ -f $var ] ; then - canal_conf=$var - else - if [ "$1" = "debug" ]; then - DEBUG_PORT=$2 - DEBUG_SUSPEND="n" - JAVA_DEBUG_OPT="-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=$DEBUG_PORT,server=y,suspend=$DEBUG_SUSPEND" - fi - fi;; -* ) - echo "THE PARAMETERS MUST BE TWO OR LESS.PLEASE CHECK AGAIN." - exit;; -esac - -str=`file -L $JAVA | grep 64-bit` -if [ -n "$str" ]; then - JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError" -else - JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m " -fi - -JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" -CANAL_OPTS="-DappName=otter-canal-client -Dlogback.configurationFile=$logback_configurationFile -Dcanal.conf=$canal_conf" - -if [ -e $canal_conf -a -e $logback_configurationFile ] -then - - for i in $base/lib/*; - do CLASSPATH=$i:"$CLASSPATH"; - done - CLASSPATH="$base/conf:$CLASSPATH"; - - echo "cd to $bin_abs_path for workaround relative path" - cd $bin_abs_path - - echo LOG CONFIGURATION : $logback_configurationFile - echo canal conf : $canal_conf - echo CLASSPATH :$CLASSPATH - $JAVA $JAVA_OPTS $JAVA_DEBUG_OPT $CANAL_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.client.ClientLauncher 1>>$base/logs/canal_client.log 2>&1 & - echo $! > $base/bin/canal_client.pid - - echo "cd to $current_path for continue" - cd $current_path -else - echo "canal client conf("$canal_conf") OR log configration file($logback_configurationFile) is not exist,please create then first!" -fi diff --git a/client-adapter/example/src/main/bin/stop.sh b/client-adapter/example/src/main/bin/stop.sh deleted file mode 100644 index c96f2807..00000000 --- a/client-adapter/example/src/main/bin/stop.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/bin/bash - -cygwin=false; -linux=false; -case "`uname`" in - CYGWIN*) - cygwin=true - ;; - Linux*) - linux=true - ;; -esac - -get_pid() { - STR=$1 - PID=$2 - if $cygwin; then - JAVA_CMD="$JAVA_HOME\bin\java" - JAVA_CMD=`cygpath --path --unix $JAVA_CMD` - JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'` - else - if $linux; then - if [ ! -z "$PID" ]; then - JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` - else - JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'` - fi - else - if [ ! -z "$PID" ]; then - JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` - else - JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'` - fi - fi - fi - echo $JAVA_PID; -} - -base=`dirname $0`/.. -pidfile=$base/bin/canal_client.pid -if [ ! -f "$pidfile" ];then - echo "canal client is not running. exists" - exit -fi - -pid=`cat $pidfile` -if [ "$pid" == "" ] ; then - pid=`get_pid "appName=otter-canal-client"` -fi - -echo -e "`hostname`: stopping canal $pid ... " -kill $pid - -LOOPS=0 -while (true); -do - gpid=`get_pid "appName=otter-canal-client" "$pid"` - if [ "$gpid" == "" ] ; then - echo "Oook! cost:$LOOPS" - `rm $pidfile` - break; - fi - let LOOPS=LOOPS+1 - sleep 1 -done \ No newline at end of file diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java deleted file mode 100644 index a705a526..00000000 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/ClientLauncher.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.alibaba.otter.canal.client; - -import java.io.FileInputStream; - -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.Yaml; - -import com.alibaba.otter.canal.client.adapter.loader.CanalAdapterLoader; -import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; - -public class ClientLauncher { - - private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(ClientLauncher.class); - - public static void main(String[] args) { - try { - logger.info("## set default uncaught exception handler"); - setGlobalUncaughtExceptionHandler(); - - logger.info("## load canal client configurations"); - String conf = System.getProperty("client.conf", "classpath:canal-client.yml"); - CanalClientConfig canalClientConfig; - if (conf.startsWith(CLASSPATH_URL_PREFIX)) { - conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX); - canalClientConfig = new Yaml().loadAs(ClientLauncher.class.getClassLoader().getResourceAsStream(conf), - CanalClientConfig.class); - } else { - canalClientConfig = new Yaml().loadAs(new FileInputStream(conf), CanalClientConfig.class); - } - logger.info("## start the canal client adapters."); - final CanalAdapterLoader adapterLoader = new CanalAdapterLoader(canalClientConfig); - adapterLoader.init(); - logger.info("## the canal client adapters are running now ......"); - Runtime.getRuntime().addShutdownHook(new Thread() { - - public void run() { - try { - logger.info("## stop the canal client adapters"); - adapterLoader.destroy(); - } catch (Throwable e) { - logger.warn("## something goes wrong when stopping canal client adapters:", e); - } finally { - logger.info("## canal client adapters are down."); - } - } - - }); - } catch (Throwable e) { - logger.error("## something goes wrong when starting up the canal client adapters:", e); - System.exit(0); - } - } - - private static void setGlobalUncaughtExceptionHandler() { - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - logger.error("UnCaughtException", e); - } - }); - } -} diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java deleted file mode 100644 index bbfd462d..00000000 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/AbstractCanalAdapterWorker.java +++ /dev/null @@ -1,168 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.loader; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.adapter.support.Dml; -import com.alibaba.otter.canal.client.adapter.support.MessageUtil; -import com.alibaba.otter.canal.protocol.FlatMessage; -import com.alibaba.otter.canal.protocol.Message; - -/** - * 适配器工作线程抽象类 - * - * @author machengyuan 2018-8-19 下午11:30:49 - * @version 1.0.0 - */ -public abstract class AbstractCanalAdapterWorker { - - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); - - protected String canalDestination; // canal实例 - protected List> canalOuterAdapters; // 外部适配器 - protected ExecutorService groupInnerExecutorService; // 组内工作线程池 - protected volatile boolean running = false; // 是否运行中 - protected Thread thread = null; - protected Thread.UncaughtExceptionHandler handler = new Thread.UncaughtExceptionHandler() { - - @Override - public void uncaughtException(Thread t, Throwable e) { - logger.error("parse events has an error", e); - } - }; - - protected void writeOut(final Message message) { - List> futures = new ArrayList<>(); - // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { - final List adapters = outerAdapters; - futures.add(groupInnerExecutorService.submit(new Callable() { - - @Override - public Boolean call() { - try { - // 组内适配器穿行运行,尽量不要配置组内适配器 - for (final OuterAdapter c : adapters) { - long begin = System.currentTimeMillis(); - MessageUtil.parse4Dml(canalDestination, message, new MessageUtil.Consumer() { - - @Override - public void accept(Dml dml) { - c.sync(dml); - } - }); - - if (logger.isDebugEnabled()) { - logger.debug("{} elapsed time: {}", - c.getClass().getName(), - (System.currentTimeMillis() - begin)); - } - } - return true; - } catch (Exception e) { - return false; - } - } - })); - - // 等待所有适配器写入完成 - // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 - for (Future f : futures) { - try { - if (!f.get()) { - logger.error("Outer adapter write failed"); - } - } catch (InterruptedException | ExecutionException e) { - // ignore - } - } - } - } - - protected void writeOut(final FlatMessage flatMessage) { - List> futures = new ArrayList<>(); - // 组间适配器并行运行 - for (List outerAdapters : canalOuterAdapters) { - final List adapters = outerAdapters; - futures.add(groupInnerExecutorService.submit(new Callable() { - - @Override - public Boolean call() { - try { - // 组内适配器穿行运行,尽量不要配置组内适配器 - for (OuterAdapter c : adapters) { - long begin = System.currentTimeMillis(); - Dml dml = MessageUtil.flatMessage2Dml(canalDestination, flatMessage); - c.sync(dml); - if (logger.isDebugEnabled()) { - logger.debug("{} elapsed time: {}", - c.getClass().getName(), - (System.currentTimeMillis() - begin)); - } - } - return true; - } catch (Exception e) { - return false; - } - } - })); - - // 等待所有适配器写入完成 - // 由于是组间并发操作,所以将阻塞直到耗时最久的工作组操作完成 - for (Future f : futures) { - try { - if (!f.get()) { - logger.error("Outer adapter write failed"); - } - } catch (InterruptedException | ExecutionException e) { - // ignore - } - } - } - } - - protected void writeOut(Message message, String topic) { - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId: {} batchSize: {} ", topic, message.getId(), message.getEntries().size()); - } - long begin = System.currentTimeMillis(); - writeOut(message); - long now = System.currentTimeMillis(); - if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { - logger.error("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); - } - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId {} elapsed time: {} ms", topic, message.getId(), now - begin); - } - } - - protected void stopOutAdapters() { - if (thread != null) { - try { - thread.join(); - } catch (InterruptedException e) { - // ignore - } - } - groupInnerExecutorService.shutdown(); - logger.info("topic connectors' worker thread dead!"); - for (List outerAdapters : canalOuterAdapters) { - for (OuterAdapter adapter : outerAdapters) { - adapter.destroy(); - } - } - logger.info("topic all connectors destroyed!"); - } - - public abstract void start(); - - public abstract void stop(); -} diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java deleted file mode 100644 index b2d3e583..00000000 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterKafkaWorker.java +++ /dev/null @@ -1,178 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.loader; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.apache.kafka.clients.consumer.CommitFailedException; -import org.apache.kafka.common.errors.WakeupException; - -import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; -import com.alibaba.otter.canal.client.kafka.KafkaCanalConnectors; -import com.alibaba.otter.canal.protocol.FlatMessage; -import com.alibaba.otter.canal.protocol.Message; - -/** - * kafka对应的client适配器工作线程 - * - * @author machengyuan 2018-8-19 下午11:30:49 - * @version 1.0.0 - */ -public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { - - private KafkaCanalConnector connector; - - private String topic; - - private boolean flatMessage; - - public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId, - List> canalOuterAdapters, boolean flatMessage){ - this.canalOuterAdapters = canalOuterAdapters; - this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); - this.topic = topic; - this.canalDestination = topic; - this.flatMessage = flatMessage; - connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage); - // connector.setSessionTimeout(1L, TimeUnit.MINUTES); - - // super.initSwitcher(topic); - } - - @Override - public void start() { - if (!running) { - thread = new Thread(new Runnable() { - - @Override - public void run() { - process(); - } - }); - thread.setUncaughtExceptionHandler(handler); - running = true; - thread.start(); - } - } - - @Override - public void stop() { - try { - if (!running) { - return; - } - - connector.stopRunning(); - running = false; - - // if (switcher != null && !switcher.state()) { - // switcher.set(true); - // } - - if (thread != null) { - try { - thread.join(); - } catch (InterruptedException e) { - // ignore - } - } - groupInnerExecutorService.shutdown(); - logger.info("topic {} connectors' worker thread dead!", this.topic); - for (List outerAdapters : canalOuterAdapters) { - for (OuterAdapter adapter : outerAdapters) { - adapter.destroy(); - } - } - logger.info("topic {} all connectors destroyed!", this.topic); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - private void process() { - while (!running) - ; - ExecutorService executor = Executors.newSingleThreadExecutor(); - // final AtomicBoolean executing = new AtomicBoolean(true); - while (running) { - try { - logger.info("=============> Start to connect topic: {} <=============", this.topic); - connector.connect(); - logger.info("=============> Start to subscribe topic: {} <=============", this.topic); - connector.subscribe(); - logger.info("=============> Subscribe topic: {} succeed <=============", this.topic); - while (running) { - try { - // switcher.get(); //等待开关开启 - - List messages; - if (!flatMessage) { - messages = connector.getWithoutAck(); - } else { - messages = connector.getFlatMessageWithoutAck(100L, TimeUnit.MILLISECONDS); - } - if (messages != null) { - for (final Object message : messages) { - if (message instanceof FlatMessage) { - writeOut((FlatMessage) message); - } else { - writeOut((Message) message); - } - // executing.set(true); - // if (message != null) { - // executor.submit(new Runnable() { - // - // @Override - // public void run() { - // try { - // if (message instanceof FlatMessage) { - // writeOut((FlatMessage) message); - // } else { - // writeOut((Message) message); - // } - // } catch (Exception e) { - // logger.error(e.getMessage(), e); - // } finally { - // executing.compareAndSet(true, false); - // } - // } - // }); - // - // // 间隔一段时间ack一次, 防止因超时未响应切换到另外台客户端 - // long currentTS = System.currentTimeMillis(); - // while (executing.get()) { - // // 大于10秒未消费完ack一次keep alive - // if (System.currentTimeMillis() - currentTS > 10000) { - // connector.ack(); - // currentTS = System.currentTimeMillis(); - // } - // } - // } - } - } - connector.ack(); - } catch (CommitFailedException e) { - logger.warn(e.getMessage()); - } catch (Exception e) { - logger.error(e.getMessage(), e); - TimeUnit.SECONDS.sleep(1L); - } - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - executor.shutdown(); - - try { - connector.unsubscribe(); - } catch (WakeupException e) { - // No-op. Continue process - } - connector.disconnect(); - logger.info("=============> Disconnect topic: {} <=============", this.topic); - } -} diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java deleted file mode 100644 index 51b12a0a..00000000 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java +++ /dev/null @@ -1,177 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.loader; - -import java.net.InetSocketAddress; -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; -import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; -import com.alibaba.otter.canal.client.adapter.support.ExtensionLoader; - -/** - * MQ外部适配器的加载器 - * - * @version 1.0.0 - */ -public class CanalAdapterLoader { - - private static final Logger logger = LoggerFactory.getLogger(CanalAdapterLoader.class); - - private CanalClientConfig canalClientConfig; - - private Map canalWorkers = new HashMap<>(); - - private Map canalMQWorker = new HashMap<>(); - - private ExtensionLoader loader; - - public CanalAdapterLoader(CanalClientConfig canalClientConfig){ - this.canalClientConfig = canalClientConfig; - } - - /** - * 初始化canal-client、 canal-client-rocketmq的适配器 - */ - public void init() { - // canal instances 和 mq topics 配置不能同时为空 - if (canalClientConfig.getCanalInstances() == null && canalClientConfig.getMqTopics() == null) { - throw new RuntimeException("Blank config property: canalInstances or canalMQTopics"); - } - - loader = ExtensionLoader.getExtensionLoader(OuterAdapter.class, "" /* - * TODO - * canalClientConfig - * . - * getClassloaderPolicy - * ( - * ) - */); - - String canalServerHost = this.canalClientConfig.getCanalServerHost(); - SocketAddress sa = null; - if (canalServerHost != null) { - String[] ipPort = canalServerHost.split(":"); - sa = new InetSocketAddress(ipPort[0], Integer.parseInt(ipPort[1])); - } - String zkHosts = this.canalClientConfig.getZookeeperHosts(); - - // 初始化canal-client的适配器 - if (canalClientConfig.getCanalInstances() != null) { - for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { - List> canalOuterAdapterGroups = new ArrayList<>(); - - for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { - List canalOutConnectors = new ArrayList<>(); - for (OuterAdapterConfig c : connectorGroup.getOutAdapters()) { - loadConnector(c, canalOutConnectors); - } - canalOuterAdapterGroups.add(canalOutConnectors); - } - CanalAdapterWorker worker; - if (zkHosts != null) { - worker = new CanalAdapterWorker(instance.getInstance(), zkHosts, canalOuterAdapterGroups); - } else { - worker = new CanalAdapterWorker(instance.getInstance(), sa, canalOuterAdapterGroups); - } - canalWorkers.put(instance.getInstance(), worker); - worker.start(); - logger.info("Start adapter for canal instance: {} succeed", instance.getInstance()); - } - } - - // 初始化canal-client-mq的适配器 - if (canalClientConfig.getMqTopics() != null) { - for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) { - for (CanalClientConfig.Group group : topic.getGroups()) { - List> canalOuterAdapterGroups = new ArrayList<>(); - - List canalOuterAdapters = new ArrayList<>(); - - for (OuterAdapterConfig config : group.getOutAdapters()) { - loadConnector(config, canalOuterAdapters); - } - canalOuterAdapterGroups.add(canalOuterAdapters); - if (StringUtils.isBlank(topic.getMqMode()) || "rocketmq".equalsIgnoreCase(topic.getMqMode())) { - CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig.getBootstrapServers(), - topic.getTopic(), - group.getGroupId(), - canalOuterAdapterGroups); - canalMQWorker.put(topic.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); - rocketMQWorker.start(); - } else if ("kafka".equalsIgnoreCase(topic.getMqMode())) { - CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig.getBootstrapServers(), - topic.getTopic(), - group.getGroupId(), - canalOuterAdapterGroups, - canalClientConfig.getFlatMessage()); - canalMQWorker.put(topic.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker); - canalKafkaWorker.start(); - } - logger.info("Start adapter for canal-client rocketmq topic: {} succeed", topic.getTopic() + "-" - + group.getGroupId()); - - } - } - } - } - - private void loadConnector(OuterAdapterConfig config, List canalOutConnectors) { - try { - OuterAdapter adapter = loader.getExtension(config.getName()); - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - // 替换ClassLoader - Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader()); - adapter.init(config); - Thread.currentThread().setContextClassLoader(cl); - canalOutConnectors.add(adapter); - logger.info("Load canal adapter: {} succeed", config.getName()); - } catch (Exception e) { - logger.error("Load canal adapter: {} failed", config.getName(), e); - } - } - - /** - * 销毁所有适配器 为防止canal实例太多造成销毁阻塞, 并行销毁 - */ - public void destroy() { - if (canalWorkers.size() > 0) { - ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size()); - for (CanalAdapterWorker v : canalWorkers.values()) { - final CanalAdapterWorker caw = v; - stopExecutorService.submit(new Runnable() { - - @Override - public void run() { - caw.stop(); - } - }); - } - stopExecutorService.shutdown(); - } - if (canalMQWorker.size() > 0) { - ExecutorService stopMQWokerService = Executors.newFixedThreadPool(canalMQWorker.size()); - for (AbstractCanalAdapterWorker tmp : canalMQWorker.values()) { - final AbstractCanalAdapterWorker worker = tmp; - stopMQWokerService.submit(new Runnable() { - - @Override - public void run() { - worker.stop(); - } - }); - } - stopMQWokerService.shutdown(); - } - logger.info("All canal adapters destroyed"); - } -} diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java deleted file mode 100644 index 9c126297..00000000 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.loader; - -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; - -import org.apache.kafka.clients.consumer.CommitFailedException; -import org.apache.kafka.common.errors.WakeupException; - -import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector; -import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider; -import com.alibaba.otter.canal.protocol.Message; - -/** - * kafka对应的client适配器工作线程 - * - * @author machengyuan 2018-8-19 下午11:30:49 - * @version 1.0.0 - */ -public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { - - private RocketMQCanalConnector connector; - - private String topic; - - public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId, - List> canalOuterAdapters){ - logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); - this.canalOuterAdapters = canalOuterAdapters; - this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); - this.topic = topic; - this.canalDestination = topic; - connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId); - } - - @Override - public void start() { - if (!running) { - thread = new Thread(new Runnable() { - - @Override - public void run() { - process(); - } - }); - thread.setUncaughtExceptionHandler(handler); - running = true; - thread.start(); - } - } - - @Override - public void stop() { - try { - if (!running) { - return; - } - connector.stopRunning(); - running = false; - logger.info("Stop topic {} out adapters begin", this.topic); - stopOutAdapters(); - logger.info("Stop topic {} out adapters end", this.topic); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - private void process() { - while (!running) - ; - ExecutorService executor = Executors.newSingleThreadExecutor(); - while (running) { - try { - logger.info("=============> Start to connect topic: {} <=============", this.topic); - connector.connect(); - logger.info("=============> Start to subscribe topic: {}<=============", this.topic); - connector.subscribe(); - logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); - while (running) { - try { - // switcher.get(); //等待开关开启 - - final Message message = connector.getWithoutAck(1); - if (message != null) { - executor.submit(new Runnable() { - - @Override - public void run() { - try { - writeOut(message, topic); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - connector.ack(message.getId()); - } - }); - } else { - logger.debug("Message is null"); - } - } catch (CommitFailedException e) { - logger.warn(e.getMessage()); - } catch (Exception e) { - logger.error(e.getMessage(), e); - TimeUnit.SECONDS.sleep(1L); - } - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - executor.shutdown(); - - try { - connector.unsubscribe(); - } catch (WakeupException e) { - // No-op. Continue process - } - connector.stopRunning(); - logger.info("=============> Disconnect topic: {} <=============", this.topic); - } -} diff --git a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java b/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java deleted file mode 100644 index e72ed2d5..00000000 --- a/client-adapter/example/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterWorker.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.loader; - -import java.net.SocketAddress; -import java.util.List; -import java.util.concurrent.Executors; - -import com.alibaba.otter.canal.client.CanalConnector; -import com.alibaba.otter.canal.client.CanalConnectors; -import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; -import com.alibaba.otter.canal.protocol.Message; - -/** - * 原生canal-server对应的client适配器工作线程 - * - * @author machengyuan 2018-8-19 下午11:30:49 - * @version 1.0.0 - */ -public class CanalAdapterWorker extends AbstractCanalAdapterWorker { - - private static final int BATCH_SIZE = 50; - private static final int SO_TIMEOUT = 0; - - private CanalConnector connector; - - /** - * 单台client适配器worker的构造方法 - * - * @param canalDestination canal实例名 - * @param address canal-server地址 - * @param canalOuterAdapters 外部适配器组 - */ - public CanalAdapterWorker(String canalDestination, SocketAddress address, - List> canalOuterAdapters){ - this.canalOuterAdapters = canalOuterAdapters; - this.canalDestination = canalDestination; - groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); - connector = CanalConnectors.newSingleConnector(address, canalDestination, "", ""); - } - - /** - * HA模式下client适配器worker的构造方法 - * - * @param canalDestination canal实例名 - * @param zookeeperHosts zookeeper地址 - * @param canalOuterAdapters 外部适配器组 - */ - public CanalAdapterWorker(String canalDestination, String zookeeperHosts, - List> canalOuterAdapters){ - this.canalOuterAdapters = canalOuterAdapters; - this.canalDestination = canalDestination; - groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); - connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", ""); - ((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT); - - // super.initSwitcher(canalDestination); - } - - @Override - public void start() { - if (!running) { - thread = new Thread(new Runnable() { - - @Override - public void run() { - process(); - } - }); - thread.setUncaughtExceptionHandler(handler); - thread.start(); - running = true; - } - } - - @Override - public void stop() { - try { - if (!running) { - return; - } - - // if (switcher != null && !switcher.state()) { - // switcher.set(true); - // } - - connector.stopRunning(); - running = false; - - logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination); - if (thread != null) { - try { - thread.join(); - } catch (InterruptedException e) { - // ignore - } - } - groupInnerExecutorService.shutdown(); - logger.info("destination {} adapters' worker thread dead!", canalDestination); - for (List outerAdapters : canalOuterAdapters) { - for (OuterAdapter adapter : outerAdapters) { - adapter.destroy(); - } - } - logger.info("destination {} all adapters destroyed!", canalDestination); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - - private void process() { - while (!running) - ; // waiting until running == true - while (running) { - try { - // if (switcher != null) { - // switcher.get(); - // } - logger.info("=============> Start to connect destination: {} <=============", this.canalDestination); - connector.connect(); - logger.info("=============> Start to subscribe destination: {} <=============", this.canalDestination); - connector.subscribe(); - logger.info("=============> Subscribe destination: {} succeed <=============", this.canalDestination); - while (running) { - // try { - // if (switcher != null) { - // switcher.get(); - // } - // } catch (TimeoutException e) { - // break; - // } - - // server配置canal.instance.network.soTimeout(默认: 30s) - // 范围内未与server交互,server将关闭本次socket连接 - Message message = connector.getWithoutAck(BATCH_SIZE); // 获取指定数量的数据 - long batchId = message.getId(); - try { - int size = message.getEntries().size(); - - if (batchId == -1 || size == 0) { - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // ignore - } - } else { - if (logger.isDebugEnabled()) { - logger.debug("destination: {} batchId: {} batchSize: {} ", - this.canalDestination, - batchId, - size); - } - long begin = System.currentTimeMillis(); - writeOut(message); - long now = System.currentTimeMillis(); - if (logger.isDebugEnabled()) { - logger.debug("destination: {} batchId: {} elapsed time: {} ms", - this.canalDestination, - batchId, - now - begin); - } - } - connector.ack(batchId); // 提交确认 - } catch (Exception e) { - connector.rollback(batchId); // 处理失败, 回滚数据 - throw e; - } - } - - } catch (Exception e) { - logger.error("process error!", e); - } finally { - connector.disconnect(); - logger.info("=============> Disconnect destination: {} <=============", this.canalDestination); - } - - if (running) { // is reconnect - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - // ignore - } - } - - } - } -} diff --git a/client-adapter/example/src/main/resources/canal-client.yml b/client-adapter/example/src/main/resources/canal-client.yml deleted file mode 100644 index de24e756..00000000 --- a/client-adapter/example/src/main/resources/canal-client.yml +++ /dev/null @@ -1,18 +0,0 @@ -canalServerHost: 127.0.0.1:11111 -#zookeeperHosts: slave1:2181 -#bootstrapServers: slave1:6667,slave2:6667 #or rocketmq nameservers:host1:9876;host2:9876 -flatMessage: false - -canalInstances: -- instance: example - adapterGroups: - - outAdapters: - - name: logger - -#mqTopics: -#- mqMode: rocketmq -# topic: example -# groups: -# - groupId: example -# outAdapters: -# - name: logger diff --git a/client-adapter/example/src/main/resources/logback.xml b/client-adapter/example/src/main/resources/logback.xml deleted file mode 100644 index e74c84f0..00000000 --- a/client-adapter/example/src/main/resources/logback.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n - - - - - - - destination - canal_client - - - - ../logs/${destination}.log - - - ../logs/%d{yyyy-MM-dd}/${destination}-%d{yyyy-MM-dd}-%i.log.gz - - - 512MB - - 60 - - - - %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml index a48d7401..f48cdd10 100644 --- a/client-adapter/hbase/pom.xml +++ b/client-adapter/hbase/pom.xml @@ -43,48 +43,60 @@ - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - maven-antrun-plugin - - - package - - run - - - - - - - - - - - - - - - + + + release + + + env + release + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + + + + + diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index cb8aee72..f10dfaac 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -104,6 +104,18 @@ org.springframework.boot spring-boot-maven-plugin 2.0.1.RELEASE + + + + com.alibaba.otter + client-adapter.hbase + + + com.alibaba.otter + client-adapter.logger + + + @@ -150,24 +162,43 @@ - - maven-assembly-plugin - - false - - assembly.xml - - - - - make-assembly - package - - single - - - - + + + + release + + + env + release + + + + + + + maven-assembly-plugin + + false + + ${basedir}/src/main/assembly/release.xml + + ${project.artifactId}-${project.version} + ${project.basedir}/../../target + + + + make-assembly + package + + single + + + + + + + + diff --git a/client-adapter/launcher/assembly.xml b/client-adapter/launcher/src/main/assembly/release.xml similarity index 100% rename from client-adapter/launcher/assembly.xml rename to client-adapter/launcher/src/main/assembly/release.xml diff --git a/client-adapter/logger/pom.xml b/client-adapter/logger/pom.xml index 8f2b4edb..e9a146aa 100644 --- a/client-adapter/logger/pom.xml +++ b/client-adapter/logger/pom.xml @@ -21,27 +21,39 @@ - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - + + + release + + + env + release + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml index 7ca65de1..aafe6395 100644 --- a/client-adapter/pom.xml +++ b/client-adapter/pom.xml @@ -24,7 +24,6 @@ logger hbase launcher - example From 0d22dbc9828e24cc5ffbf8bd77914db8b93b990a Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 26 Oct 2018 15:56:05 +0800 Subject: [PATCH 208/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=89=93=E5=8C=85?= =?UTF-8?q?=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/hbase/pom.xml | 78 ++++++++++----------------------- client-adapter/launcher/pom.xml | 5 +++ client-adapter/logger/pom.xml | 57 ++++++++++-------------- 3 files changed, 51 insertions(+), 89 deletions(-) diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml index f48cdd10..b3d274e4 100644 --- a/client-adapter/hbase/pom.xml +++ b/client-adapter/hbase/pom.xml @@ -43,60 +43,28 @@ + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + - - - release - - - env - release - - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - maven-antrun-plugin - - - package - - run - - - - - - - - - - - - - - - - - diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index f10dfaac..8159bb66 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -139,6 +139,11 @@ + + + + + diff --git a/client-adapter/logger/pom.xml b/client-adapter/logger/pom.xml index e9a146aa..af7ec379 100644 --- a/client-adapter/logger/pom.xml +++ b/client-adapter/logger/pom.xml @@ -21,39 +21,28 @@ - - - release - - - env - release - - - - - - org.apache.maven.plugins - maven-assembly-plugin - 2.4 - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - - - + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + From 2d930894ffc944af6d62bffe60118a7a0c98d3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 26 Oct 2018 21:50:12 +0800 Subject: [PATCH 209/360] fixed issue #1046, tinyint(1) unsigned with value 255 --- .../canal/parse/inbound/mysql/dbsync/LogEventConvert.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 7fcd1a1f..ec71465b 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -671,9 +671,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar } int javaType = buffer.getJavaType(); - if (isSingleBit && javaType == Types.TINYINT) { - javaType = Types.BIT; - } if (buffer.isNull()) { columnBuilder.setIsNull(true); } else { @@ -722,6 +719,10 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar // 对象为number类型,直接valueof即可 columnBuilder.setValue(String.valueOf(value)); } + + if (isSingleBit && javaType == Types.TINYINT) { + javaType = Types.BIT; + } break; case Types.REAL: // float case Types.DOUBLE: // double From 1e2cf9d47636b382fe1c76f8d00e9e641699603a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 26 Oct 2018 21:51:19 +0800 Subject: [PATCH 210/360] fixed bit(8) with value 255 --- .../java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java index 9fe22820..8cd57332 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/event/RowsLogBuffer.java @@ -367,7 +367,7 @@ public final class RowsLogBuffer { // 转化为unsign long switch (len) { case 1: - value = buffer.getInt8(); + value = buffer.getUint8(); break; case 2: value = buffer.getBeUint16(); From f822761ab27bc5ee262ca8b80cd2e0dcb283624a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Fri, 26 Oct 2018 23:46:41 +0800 Subject: [PATCH 211/360] fixed issue #1047 , support snapshot expire --- deployer/src/main/resources/canal.properties | 4 ++ .../resources/spring/default-instance.xml | 2 + .../main/resources/spring/file-instance.xml | 2 + .../main/resources/spring/memory-instance.xml | 2 + .../spring/tsdb/sql-map/sqlmap_history.xml | 4 +- .../spring/tsdb/sql-map/sqlmap_snapshot.xml | 4 +- .../manager/CanalInstanceWithManager.java | 6 ++ .../manager/model/CanalParameter.java | 18 ++++++ .../mysql/AbstractMysqlEventParser.java | 18 ++++++ .../inbound/mysql/LocalBinlogEventParser.java | 2 + .../parse/inbound/mysql/MysqlEventParser.java | 2 + .../inbound/mysql/tsdb/DatabaseTableMeta.java | 61 ++++++++++++++----- .../mysql/tsdb/dao/MetaHistoryDAO.java | 11 ++-- .../mysql/tsdb/dao/MetaSnapshotDAO.java | 11 ++-- .../mysql/tsdb/MetaHistoryDAOTest.java | 19 +++++- .../mysql/tsdb/MetaSnapshotDAOTest.java | 41 +++++++++++++ .../resources/tsdb/sql-map/sqlmap_history.xml | 4 +- .../tsdb/sql-map/sqlmap_snapshot.xml | 4 +- 18 files changed, 176 insertions(+), 39 deletions(-) create mode 100644 parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaSnapshotDAOTest.java diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 2a24b66f..b5284dec 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -69,6 +69,10 @@ canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destinat canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; canal.instance.tsdb.dbUsername=canal canal.instance.tsdb.dbPassword=canal +# dump snapshot interval, default 24 hour +canal.instance.tsdb.snapshot.interval=24 +# purge snapshot expire , default 360 hour(15 days) +canal.instance.tsdb.snapshot.expire=360 # rds oss binlog account canal.instance.rds.accesskey = diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index fe9cc7a5..f8c19bd0 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -176,6 +176,8 @@ + + diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 23d8e4bd..5524f73f 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -161,6 +161,8 @@ + + diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index f785a46e..8fdfb178 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -149,6 +149,8 @@ + + diff --git a/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_history.xml b/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_history.xml index 7445bf69..58957c28 100644 --- a/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_history.xml +++ b/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_history.xml @@ -36,10 +36,10 @@ - + \ No newline at end of file diff --git a/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_snapshot.xml b/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_snapshot.xml index 79aa25fe..2ee0f8f4 100644 --- a/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_snapshot.xml +++ b/deployer/src/main/resources/spring/tsdb/sql-map/sqlmap_snapshot.xml @@ -42,10 +42,10 @@ where destination=#destination# - + 0 ]]> \ No newline at end of file diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index dff18278..154cd53f 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -296,6 +296,12 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { mysqlEventParser.setFilterTableError(parameters.getFilterTableError()); mysqlEventParser.setIsGTIDMode(BooleanUtils.toBoolean(parameters.getGtidEnable())); // tsdb + if (parameters.getTsdbSnapshotInterval() != null) { + mysqlEventParser.setTsdbSnapshotInterval(parameters.getTsdbSnapshotInterval()); + } + if (parameters.getTsdbSnapshotExpire() != null) { + mysqlEventParser.setTsdbSnapshotExpire(parameters.getTsdbSnapshotExpire()); + } boolean tsdbEnable = BooleanUtils.toBoolean(parameters.getTsdbEnable()); if (tsdbEnable) { mysqlEventParser.setEnableTsdb(tsdbEnable); diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java index ca5e1af7..af3841dd 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/model/CanalParameter.java @@ -98,6 +98,8 @@ public class CanalParameter implements Serializable { private String tsdbJdbcUrl; private String tsdbJdbcUserName; private String tsdbJdbcPassword; + private Integer tsdbSnapshotInterval = 24; + private Integer tsdbSnapshotExpire = 360; private String rdsAccesskey; private String rdsSecretkey; private String rdsInstanceId; @@ -964,6 +966,22 @@ public class CanalParameter implements Serializable { this.memoryStorageRawEntry = memoryStorageRawEntry; } + public Integer getTsdbSnapshotInterval() { + return tsdbSnapshotInterval; + } + + public void setTsdbSnapshotInterval(Integer tsdbSnapshotInterval) { + this.tsdbSnapshotInterval = tsdbSnapshotInterval; + } + + public Integer getTsdbSnapshotExpire() { + return tsdbSnapshotExpire; + } + + public void setTsdbSnapshotExpire(Integer tsdbSnapshotExpire) { + this.tsdbSnapshotExpire = tsdbSnapshotExpire; + } + public String toString() { return ToStringBuilder.reflectionToString(this, CanalToStringStyle.DEFAULT_STYLE); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index 00e302e6..8050186d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -26,6 +26,8 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory(); protected boolean enableTsdb = false; + protected int tsdbSnapshotInterval = 24; + protected int tsdbSnapshotExpire = 360; protected String tsdbSpringXml; protected TableMetaTSDB tableMetaTSDB; @@ -210,4 +212,20 @@ public abstract class AbstractMysqlEventParser extends AbstractEventParser { return this.receivedBinlogBytes; } + public int getTsdbSnapshotInterval() { + return tsdbSnapshotInterval; + } + + public void setTsdbSnapshotInterval(int tsdbSnapshotInterval) { + this.tsdbSnapshotInterval = tsdbSnapshotInterval; + } + + public int getTsdbSnapshotExpire() { + return tsdbSnapshotExpire; + } + + public void setTsdbSnapshotExpire(int tsdbSnapshotExpire) { + this.tsdbSnapshotExpire = tsdbSnapshotExpire; + } + } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java index ea6b0365..4f6ace4f 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java @@ -55,6 +55,8 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements ((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection); ((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter); ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter); + ((DatabaseTableMeta) tableMetaTSDB).setSnapshotInterval(tsdbSnapshotInterval); + ((DatabaseTableMeta) tableMetaTSDB).setSnapshotExpire(tsdbSnapshotExpire); } tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index cf102d3d..7f83b6d2 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -121,6 +121,8 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE ((DatabaseTableMeta) tableMetaTSDB).setConnection(metaConnection); ((DatabaseTableMeta) tableMetaTSDB).setFilter(eventFilter); ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter); + ((DatabaseTableMeta) tableMetaTSDB).setSnapshotInterval(tsdbSnapshotInterval); + ((DatabaseTableMeta) tableMetaTSDB).setSnapshotExpire(tsdbSnapshotExpire); } tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index bb2af45f..d7b2f7d2 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -44,19 +44,21 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition; */ public class DatabaseTableMeta implements TableMetaTSDB { - public static final EntryPosition INIT_POSITION = new EntryPosition("0", 0L, -2L, -1L); - private static Logger logger = LoggerFactory.getLogger(DatabaseTableMeta.class); - private static Pattern pattern = Pattern.compile("Duplicate entry '.*' for key '*'"); - private static Pattern h2Pattern = Pattern.compile("Unique index or primary key violation"); + public static final EntryPosition INIT_POSITION = new EntryPosition("0", 0L, -2L, -1L); + private static Logger logger = LoggerFactory.getLogger(DatabaseTableMeta.class); + private static Pattern pattern = Pattern.compile("Duplicate entry '.*' for key '*'"); + private static Pattern h2Pattern = Pattern.compile("Unique index or primary key violation"); private String destination; private MemoryTableMeta memoryTableMeta; - private MysqlConnection connection; // 查询meta信息的链接 + private MysqlConnection connection; // 查询meta信息的链接 private CanalEventFilter filter; private CanalEventFilter blackFilter; private EntryPosition lastPosition; private ScheduledExecutorService scheduler; private MetaHistoryDAO metaHistoryDAO; private MetaSnapshotDAO metaSnapshotDAO; + private int snapshotInterval = 24; + private int snapshotExpire = 360; public DatabaseTableMeta(){ @@ -77,18 +79,27 @@ public class DatabaseTableMeta implements TableMetaTSDB { }); // 24小时生成一份snapshot - scheduler.scheduleWithFixedDelay(new Runnable() { + if (snapshotInterval > 0) { + scheduler.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - try { - MDC.put("destination", destination); - applySnapshotToDB(lastPosition, false); - } catch (Throwable e) { - logger.error("scheudle applySnapshotToDB faield", e); + @Override + public void run() { + try { + MDC.put("destination", destination); + applySnapshotToDB(lastPosition, false); + } catch (Throwable e) { + logger.error("scheudle applySnapshotToDB faield", e); + } + + try { + MDC.put("destination", destination); + snapshotExpire((int) TimeUnit.HOURS.toSeconds(snapshotExpire)); + } catch (Throwable e) { + logger.error("scheudle snapshotExpire faield", e); + } } - } - }, 24, 24, TimeUnit.HOURS); + }, snapshotInterval, snapshotInterval, TimeUnit.HOURS); + } return true; } @@ -461,6 +472,10 @@ public class DatabaseTableMeta implements TableMetaTSDB { return true; } + private int snapshotExpire(int expireTimestamp) { + return metaSnapshotDAO.deleteByTimestamp(destination, expireTimestamp); + } + public void setConnection(MysqlConnection connection) { this.connection = connection; } @@ -489,6 +504,22 @@ public class DatabaseTableMeta implements TableMetaTSDB { this.blackFilter = blackFilter; } + public int getSnapshotInterval() { + return snapshotInterval; + } + + public void setSnapshotInterval(int snapshotInterval) { + this.snapshotInterval = snapshotInterval; + } + + public int getSnapshotExpire() { + return snapshotExpire; + } + + public void setSnapshotExpire(int snapshotExpire) { + this.snapshotExpire = snapshotExpire; + } + public MysqlConnection getConnection() { return connection; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaHistoryDAO.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaHistoryDAO.java index 6ec51614..c214d88f 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaHistoryDAO.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaHistoryDAO.java @@ -1,7 +1,5 @@ package com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.HashMap; import java.util.List; @@ -37,13 +35,12 @@ public class MetaHistoryDAO extends MetaBaseDAO { /** * 删除interval秒之前的数据 */ - public Integer deleteByGmtModified(int interval) { + public Integer deleteByTimestamp(String destination, int interval) { HashMap params = Maps.newHashMapWithExpectedSize(2); long timestamp = System.currentTimeMillis() - interval * 1000; - Date date = new Date(timestamp); - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - params.put("timestamp", format.format(date)); - return getSqlMapClientTemplate().delete("meta_history.deleteByGmtModified", params); + params.put("timestamp", timestamp); + params.put("destination", destination); + return getSqlMapClientTemplate().delete("meta_history.deleteByTimestamp", params); } protected void initDao() throws Exception { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaSnapshotDAO.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaSnapshotDAO.java index f525031a..ff79b107 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaSnapshotDAO.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/dao/MetaSnapshotDAO.java @@ -1,7 +1,5 @@ package com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao; -import java.text.SimpleDateFormat; -import java.util.Date; import java.util.HashMap; import com.google.common.collect.Maps; @@ -40,13 +38,12 @@ public class MetaSnapshotDAO extends MetaBaseDAO { /** * 删除interval秒之前的数据 */ - public Integer deleteByGmtModified(int interval) { + public Integer deleteByTimestamp(String destination, int interval) { HashMap params = Maps.newHashMapWithExpectedSize(2); long timestamp = System.currentTimeMillis() - interval * 1000; - Date date = new Date(timestamp); - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - params.put("timestamp", format.format(date)); - return getSqlMapClientTemplate().delete("meta_snapshot.deleteByGmtModified", params); + params.put("timestamp", timestamp); + params.put("destination", destination); + return getSqlMapClientTemplate().delete("meta_snapshot.deleteByTimestamp", params); } protected void initDao() throws Exception { diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaHistoryDAOTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaHistoryDAOTest.java index b199a326..cea6c087 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaHistoryDAOTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaHistoryDAOTest.java @@ -16,7 +16,7 @@ import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaHistoryDO; * Created by wanshao Date: 2017/9/20 Time: 下午5:00 **/ @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(locations = { "/tsdb/mysql-tsdb.xml" }) +@ContextConfiguration(locations = { "/tsdb/h2-tsdb.xml" }) public class MetaHistoryDAOTest { @Resource @@ -24,7 +24,22 @@ public class MetaHistoryDAOTest { @Test public void testSimple() { - List metaHistoryDOList = metaHistoryDAO.findByTimestamp("test", 0L, 0L); + MetaHistoryDO historyDO = new MetaHistoryDO(); + historyDO.setDestination("test"); + historyDO.setBinlogFile("000001"); + historyDO.setBinlogOffest(4L); + historyDO.setBinlogMasterId("1"); + historyDO.setBinlogTimestamp(System.currentTimeMillis() - 7300 * 1000); + historyDO.setSqlSchema("test"); + historyDO.setUseSchema("test"); + historyDO.setSqlTable("testTable"); + historyDO.setSqlTable("drop table testTable"); + metaHistoryDAO.insert(historyDO); + + int count = metaHistoryDAO.deleteByTimestamp("test", 7200); + System.out.println(count); + + List metaHistoryDOList = metaHistoryDAO.findByTimestamp("test", 0L, System.currentTimeMillis()); for (MetaHistoryDO metaHistoryDO : metaHistoryDOList) { System.out.println(metaHistoryDO.getId()); } diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaSnapshotDAOTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaSnapshotDAOTest.java new file mode 100644 index 00000000..f503e4b2 --- /dev/null +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MetaSnapshotDAOTest.java @@ -0,0 +1,41 @@ +package com.alibaba.otter.canal.parse.inbound.mysql.tsdb; + +import javax.annotation.Resource; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDAO; +import com.alibaba.otter.canal.parse.inbound.mysql.tsdb.dao.MetaSnapshotDO; + +/** + * Created by wanshao Date: 2017/9/20 Time: 下午5:00 + **/ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(locations = { "/tsdb/h2-tsdb.xml" }) +public class MetaSnapshotDAOTest { + + @Resource + MetaSnapshotDAO metaSnapshotDAO; + + @Test + public void testSimple() { + MetaSnapshotDO metaSnapshotDO = new MetaSnapshotDO(); + metaSnapshotDO.setDestination("test"); + metaSnapshotDO.setBinlogFile("000001"); + metaSnapshotDO.setBinlogOffest(4L); + metaSnapshotDO.setBinlogMasterId("1"); + metaSnapshotDO.setBinlogTimestamp(System.currentTimeMillis() - 7300 * 1000); + metaSnapshotDO.setData("test"); + metaSnapshotDAO.insert(metaSnapshotDO); + + MetaSnapshotDO snapshotDO = metaSnapshotDAO.findByTimestamp("test", System.currentTimeMillis()); + System.out.println(snapshotDO); + + int count = metaSnapshotDAO.deleteByTimestamp("test", 7200); + System.out.println(count); + } + +} diff --git a/parse/src/test/resources/tsdb/sql-map/sqlmap_history.xml b/parse/src/test/resources/tsdb/sql-map/sqlmap_history.xml index 7445bf69..58957c28 100644 --- a/parse/src/test/resources/tsdb/sql-map/sqlmap_history.xml +++ b/parse/src/test/resources/tsdb/sql-map/sqlmap_history.xml @@ -36,10 +36,10 @@ - + \ No newline at end of file diff --git a/parse/src/test/resources/tsdb/sql-map/sqlmap_snapshot.xml b/parse/src/test/resources/tsdb/sql-map/sqlmap_snapshot.xml index 7ba0a435..60827c6c 100644 --- a/parse/src/test/resources/tsdb/sql-map/sqlmap_snapshot.xml +++ b/parse/src/test/resources/tsdb/sql-map/sqlmap_snapshot.xml @@ -54,10 +54,10 @@ where destination=#destination# - + 0 ]]> \ No newline at end of file From fd7b9d766a89f8a6621d37ac6a109e98f57cd97f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Sat, 27 Oct 2018 00:47:50 +0800 Subject: [PATCH 212/360] fixed snapshot expire --- .../canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index d7b2f7d2..b0dd89ce 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -84,16 +84,19 @@ public class DatabaseTableMeta implements TableMetaTSDB { @Override public void run() { + boolean applyResult = false; try { MDC.put("destination", destination); - applySnapshotToDB(lastPosition, false); + applyResult = applySnapshotToDB(lastPosition, false); } catch (Throwable e) { logger.error("scheudle applySnapshotToDB faield", e); } try { MDC.put("destination", destination); - snapshotExpire((int) TimeUnit.HOURS.toSeconds(snapshotExpire)); + if (applyResult) { + snapshotExpire((int) TimeUnit.HOURS.toSeconds(snapshotExpire)); + } } catch (Throwable e) { logger.error("scheudle snapshotExpire faield", e); } From 87ac3daecc3f45e796ed56bf1a1922200533c26b Mon Sep 17 00:00:00 2001 From: mcy Date: Sat, 27 Oct 2018 20:03:26 +0800 Subject: [PATCH 213/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9hbase=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/adapter/hbase/HbaseAdapter.java | 12 +- .../adapter/hbase/config/MappingConfig.java | 42 +++---- .../hbase/config/MappingConfigLoader.java | 18 +-- .../hbase/service/HbaseEtlService.java | 72 +++++------ .../hbase/service/HbaseSyncService.java | 116 +++++++++--------- .../main/resources/hbase/mytest_person2.yml | 4 +- .../src/main/resources/application.yml | 32 ++--- 7 files changed, 148 insertions(+), 148 deletions(-) diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index ababeb6e..e97035ed 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -53,9 +53,9 @@ public class HbaseAdapter implements OuterAdapter { hbaseMapping = MappingConfigLoader.load(); mappingConfigCache = new HashMap<>(); for (MappingConfig mappingConfig : hbaseMapping.values()) { - mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getHbaseOrm().getDestination()) - + "." + mappingConfig.getHbaseOrm().getDatabase() + "." - + mappingConfig.getHbaseOrm().getTable(), + mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getHbaseMapping().getDestination()) + + "." + mappingConfig.getHbaseMapping().getDatabase() + "." + + mappingConfig.getHbaseMapping().getTable(), mappingConfig); } } @@ -136,7 +136,7 @@ public class HbaseAdapter implements OuterAdapter { @Override public Map count(String task) { MappingConfig config = hbaseMapping.get(task); - String hbaseTable = config.getHbaseOrm().getHbaseTable(); + String hbaseTable = config.getHbaseMapping().getHbaseTable(); long rowCount = 0L; try { HTable table = (HTable) conn.getTable(TableName.valueOf(hbaseTable)); @@ -169,8 +169,8 @@ public class HbaseAdapter implements OuterAdapter { @Override public String getDestination(String task) { MappingConfig config = hbaseMapping.get(task); - if (config != null && config.getHbaseOrm() != null) { - return config.getHbaseOrm().getDestination(); + if (config != null && config.getHbaseMapping() != null) { + return config.getHbaseMapping().getDestination(); } return null; } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java index 93968610..3ff1bb85 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -10,9 +10,9 @@ import java.util.*; */ public class MappingConfig { - private String dataSourceKey; // 数据源key + private String dataSourceKey; // 数据源key - private HbaseOrm hbaseOrm; // hbase映射配置 + private HbaseMapping hbaseMapping; // hbase映射配置 public String getDataSourceKey() { return dataSourceKey; @@ -22,28 +22,28 @@ public class MappingConfig { this.dataSourceKey = dataSourceKey; } - public HbaseOrm getHbaseOrm() { - return hbaseOrm; + public HbaseMapping getHbaseMapping() { + return hbaseMapping; } - public void setHbaseOrm(HbaseOrm hbaseOrm) { - this.hbaseOrm = hbaseOrm; + public void setHbaseMapping(HbaseMapping hbaseMapping) { + this.hbaseMapping = hbaseMapping; } public void validate() { - if (hbaseOrm.database == null || hbaseOrm.database.isEmpty()) { - throw new NullPointerException("hbaseOrm.database"); + if (hbaseMapping.database == null || hbaseMapping.database.isEmpty()) { + throw new NullPointerException("hbaseMapping.database"); } - if (hbaseOrm.table == null || hbaseOrm.table.isEmpty()) { - throw new NullPointerException("hbaseOrm.table"); + if (hbaseMapping.table == null || hbaseMapping.table.isEmpty()) { + throw new NullPointerException("hbaseMapping.table"); } - if (hbaseOrm.hbaseTable == null || hbaseOrm.hbaseTable.isEmpty()) { - throw new NullPointerException("hbaseOrm.hbaseTable"); + if (hbaseMapping.hbaseTable == null || hbaseMapping.hbaseTable.isEmpty()) { + throw new NullPointerException("hbaseMapping.hbaseTable"); } - if (hbaseOrm.mode == null) { - throw new NullPointerException("hbaseOrm.mode"); + if (hbaseMapping.mode == null) { + throw new NullPointerException("hbaseMapping.mode"); } - if (hbaseOrm.rowKey != null && hbaseOrm.rowKeyColumn != null) { + if (hbaseMapping.rowKey != null && hbaseMapping.rowKeyColumn != null) { throw new RuntimeException("已配置了复合主键作为RowKey,无需再指定RowKey列"); } } @@ -55,12 +55,12 @@ public class MappingConfig { MappingConfig config = (MappingConfig) o; - return hbaseOrm != null ? hbaseOrm.equals(config.hbaseOrm) : config.hbaseOrm == null; + return hbaseMapping != null ? hbaseMapping.equals(config.hbaseMapping) : config.hbaseMapping == null; } @Override public int hashCode() { - return hbaseOrm != null ? hbaseOrm.hashCode() : 0; + return hbaseMapping != null ? hbaseMapping.hashCode() : 0; } public static class ColumnItem { @@ -140,7 +140,7 @@ public class MappingConfig { } } - public static class HbaseOrm { + public static class HbaseMapping { private Mode mode = Mode.STRING; // hbase默认转换格式 private String destination; // canal实例或MQ的topic @@ -349,10 +349,10 @@ public class MappingConfig { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - HbaseOrm hbaseOrm = (HbaseOrm) o; + HbaseMapping hbaseMapping = (HbaseMapping) o; - if (table != null ? !table.equals(hbaseOrm.table) : hbaseOrm.table != null) return false; - return hbaseTable != null ? hbaseTable.equals(hbaseOrm.hbaseTable) : hbaseOrm.hbaseTable == null; + if (table != null ? !table.equals(hbaseMapping.table) : hbaseMapping.table != null) return false; + return hbaseTable != null ? hbaseTable.equals(hbaseMapping.hbaseTable) : hbaseMapping.hbaseTable == null; } @Override diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 83ba56df..c0e9496a 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -80,21 +80,21 @@ public class MappingConfigLoader { if (dbTable.length == 2) { config = new MappingConfig(); - MappingConfig.HbaseOrm hbaseOrm = new MappingConfig.HbaseOrm(); - hbaseOrm.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase()); - hbaseOrm.setAutoCreateTable(true); - hbaseOrm.setDatabase(dbTable[0]); - hbaseOrm.setTable(dbTable[1]); - hbaseOrm.setMode(MappingConfig.Mode.PHOENIX); - hbaseOrm.setRowKey(rowKey); + MappingConfig.HbaseMapping hbaseMapping = new MappingConfig.HbaseMapping(); + hbaseMapping.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase()); + hbaseMapping.setAutoCreateTable(true); + hbaseMapping.setDatabase(dbTable[0]); + hbaseMapping.setTable(dbTable[1]); + hbaseMapping.setMode(MappingConfig.Mode.PHOENIX); + hbaseMapping.setRowKey(rowKey); // 有定义rowKey if (rowKey != null) { MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem(); columnItem.setRowKey(true); columnItem.setColumn(rowKey); - hbaseOrm.setRowKeyColumn(columnItem); + hbaseMapping.setRowKeyColumn(columnItem); } - config.setHbaseOrm(hbaseOrm); + config.setHbaseMapping(hbaseMapping); config.setDataSourceKey(dsKey); } else { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index 7e3c817a..7c57a9ab 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -81,9 +81,9 @@ public class HbaseEtlService { public static void createTable(HbaseTemplate hbaseTemplate, MappingConfig config) { try { // 判断hbase表是否存在,不存在则建表 - MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); - if (!hbaseTemplate.tableExists(hbaseOrm.getHbaseTable())) { - hbaseTemplate.createTable(hbaseOrm.getHbaseTable(), hbaseOrm.getFamily()); + MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping(); + if (!hbaseTemplate.tableExists(hbaseMapping.getHbaseTable())) { + hbaseTemplate.createTable(hbaseMapping.getHbaseTable(), hbaseMapping.getFamily()); } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -113,29 +113,29 @@ public class HbaseEtlService { etlResult.setErrorMessage("Config is null!"); return etlResult; } - MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); - hbaseTable = hbaseOrm.getHbaseTable(); + MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping(); + hbaseTable = hbaseMapping.getHbaseTable(); long start = System.currentTimeMillis(); if (params != null && params.size() == 1 && "rebuild".equalsIgnoreCase(params.get(0))) { - logger.info(hbaseOrm.getHbaseTable() + " rebuild is starting!"); + logger.info(hbaseMapping.getHbaseTable() + " rebuild is starting!"); // 如果表存在则删除 - if (hbaseTemplate.tableExists(hbaseOrm.getHbaseTable())) { - hbaseTemplate.disableTable(hbaseOrm.getHbaseTable()); - hbaseTemplate.deleteTable(hbaseOrm.getHbaseTable()); + if (hbaseTemplate.tableExists(hbaseMapping.getHbaseTable())) { + hbaseTemplate.disableTable(hbaseMapping.getHbaseTable()); + hbaseTemplate.deleteTable(hbaseMapping.getHbaseTable()); } params = null; } else { - logger.info(hbaseOrm.getHbaseTable() + " etl is starting!"); + logger.info(hbaseMapping.getHbaseTable() + " etl is starting!"); } createTable(hbaseTemplate, config); // 拼接sql - String sql = "SELECT * FROM " + config.getHbaseOrm().getDatabase() + "." + hbaseOrm.getTable(); + String sql = "SELECT * FROM " + config.getHbaseMapping().getDatabase() + "." + hbaseMapping.getTable(); // 拼接条件 - if (params != null && params.size() == 1 && hbaseOrm.getEtlCondition() == null) { + if (params != null && params.size() == 1 && hbaseMapping.getEtlCondition() == null) { AtomicBoolean stExists = new AtomicBoolean(false); // 验证是否有SYS_TIME字段 sqlRS(ds, sql, rs -> { @@ -157,8 +157,8 @@ public class HbaseEtlService { if (stExists.get()) { sql += " WHERE SYS_TIME >= '" + params.get(0) + "' "; } - } else if (hbaseOrm.getEtlCondition() != null && params != null) { - String etlCondition = hbaseOrm.getEtlCondition(); + } else if (hbaseMapping.getEtlCondition() != null && params != null) { + String etlCondition = hbaseMapping.getEtlCondition(); int size = params.size(); for (int i = 0; i < size; i++) { etlCondition = etlCondition.replace("{" + i + "}", params.get(i)); @@ -200,7 +200,7 @@ public class HbaseEtlService { sqlFinal = sql + " LIMIT " + offset + "," + cnt; } Future future = executor - .submit(() -> executeSqlImport(ds, sqlFinal, hbaseOrm, hbaseTemplate, successCount, errMsg)); + .submit(() -> executeSqlImport(ds, sqlFinal, hbaseMapping, hbaseTemplate, successCount, errMsg)); futures.add(future); } @@ -210,13 +210,13 @@ public class HbaseEtlService { executor.shutdown(); } else { - executeSqlImport(ds, sql, hbaseOrm, hbaseTemplate, successCount, errMsg); + executeSqlImport(ds, sql, hbaseMapping, hbaseTemplate, successCount, errMsg); } logger.info( - hbaseOrm.getHbaseTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); + hbaseMapping.getHbaseTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); - etlResult.setResultMessage("导入HBase表 " + hbaseOrm.getHbaseTable() + " 数据:" + successCount.get() + " 条"); + etlResult.setResultMessage("导入HBase表 " + hbaseMapping.getHbaseTable() + " 数据:" + successCount.get() + " 条"); } catch (Exception e) { logger.error(e.getMessage(), e); errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage()); @@ -235,13 +235,13 @@ public class HbaseEtlService { * * @param ds * @param sql - * @param hbaseOrm + * @param hbaseMapping * @param hbaseTemplate * @param successCount * @param errMsg * @return */ - private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseOrm hbaseOrm, + private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseMapping hbaseMapping, HbaseTemplate hbaseTemplate, AtomicLong successCount, List errMsg) { try { sqlRS(ds, sql, rs -> { @@ -251,8 +251,8 @@ public class HbaseEtlService { boolean complete = false; List rows = new ArrayList<>(); String[] rowKeyColumns = null; - if (hbaseOrm.getRowKey() != null) { - rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + if (hbaseMapping.getRowKey() != null) { + rowKeyColumns = hbaseMapping.getRowKey().trim().split(","); } while (rs.next()) { int cc = rs.getMetaData().getColumnCount(); @@ -290,30 +290,30 @@ public class HbaseEtlService { continue; } - MappingConfig.ColumnItem columnItem = hbaseOrm.getColumnItems().get(columnName); + MappingConfig.ColumnItem columnItem = hbaseMapping.getColumnItems().get(columnName); // 没有配置映射 if (columnItem == null) { - String family = hbaseOrm.getFamily(); + String family = hbaseMapping.getFamily(); String qualifile = columnName; - if (hbaseOrm.isUppercaseQualifier()) { + if (hbaseMapping.isUppercaseQualifier()) { qualifile = qualifile.toUpperCase(); } - if (MappingConfig.Mode.STRING == hbaseOrm.getMode()) { - if (hbaseOrm.getRowKey() == null && j == 1) { + if (MappingConfig.Mode.STRING == hbaseMapping.getMode()) { + if (hbaseMapping.getRowKey() == null && j == 1) { row.setRowKey(Bytes.toBytes(val.toString())); } else { row.addCell(family, qualifile, Bytes.toBytes(val.toString())); } - } else if (MappingConfig.Mode.NATIVE == hbaseOrm.getMode()) { + } else if (MappingConfig.Mode.NATIVE == hbaseMapping.getMode()) { Type type = Type.getType(classes[j - 1]); - if (hbaseOrm.getRowKey() == null && j == 1) { + if (hbaseMapping.getRowKey() == null && j == 1) { row.setRowKey(TypeUtil.toBytes(val, type)); } else { row.addCell(family, qualifile, TypeUtil.toBytes(val, type)); } - } else if (MappingConfig.Mode.PHOENIX == hbaseOrm.getMode()) { + } else if (MappingConfig.Mode.PHOENIX == hbaseMapping.getMode()) { PhType phType = PhType.getType(classes[j - 1]); - if (hbaseOrm.getRowKey() == null && j == 1) { + if (hbaseMapping.getRowKey() == null && j == 1) { row.setRowKey(PhTypeUtil.toBytes(val, phType)); } else { row.addCell(family, qualifile, PhTypeUtil.toBytes(val, phType)); @@ -353,8 +353,8 @@ public class HbaseEtlService { rows.add(row); complete = false; - if (i % hbaseOrm.getCommitBatch() == 0 && !rows.isEmpty()) { - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + if (i % hbaseMapping.getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); rows.clear(); complete = true; } @@ -366,12 +366,12 @@ public class HbaseEtlService { } if (!complete && !rows.isEmpty()) { - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); } } catch (Exception e) { - logger.error(hbaseOrm.getHbaseTable() + " etl failed! ==>" + e.getMessage(), e); - errMsg.add(hbaseOrm.getHbaseTable() + " etl failed! ==>" + e.getMessage()); + logger.error(hbaseMapping.getHbaseTable() + " etl failed! ==>" + e.getMessage(), e); + errMsg.add(hbaseMapping.getHbaseTable() + " etl failed! ==>" + e.getMessage()); // throw new RuntimeException(e); } return i; diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java index 4f31d0d3..3dd03b91 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -59,11 +59,11 @@ public class HbaseSyncService { return; } - MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping(); // if (!validHTable(config)) { // logger.error("HBase table '{}' not exists", - // hbaseOrm.getHbaseTable()); + // hbaseMapping.getHbaseTable()); // return; // } int i = 1; @@ -73,28 +73,28 @@ public class HbaseSyncService { HRow hRow = new HRow(); // 拼接复合rowKey - if (hbaseOrm.getRowKey() != null) { - String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + if (hbaseMapping.getRowKey() != null) { + String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(","); String rowKeyVale = getRowKeys(rowKeyColumns, r); // params.put("rowKey", Bytes.toBytes(rowKeyVale)); hRow.setRowKey(Bytes.toBytes(rowKeyVale)); } - convertData2Row(hbaseOrm, hRow, r); + convertData2Row(hbaseMapping, hRow, r); if (hRow.getRowKey() == null) { throw new RuntimeException("empty rowKey"); } rows.add(hRow); complete = false; - if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) { - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); rows.clear(); complete = true; } i++; } if (!complete && !rows.isEmpty()) { - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); } } @@ -102,30 +102,30 @@ public class HbaseSyncService { /** * 将Map数据转换为HRow行数据 * - * @param hbaseOrm hbase映射配置 + * @param hbaseMapping hbase映射配置 * @param hRow 行对象 * @param data Map数据 */ - private static void convertData2Row(MappingConfig.HbaseOrm hbaseOrm, HRow hRow, Map data) { - Map columnItems = hbaseOrm.getColumnItems(); + private static void convertData2Row(MappingConfig.HbaseMapping hbaseMapping, HRow hRow, Map data) { + Map columnItems = hbaseMapping.getColumnItems(); int i = 0; for (Map.Entry entry : data.entrySet()) { - if (hbaseOrm.getExcludeColumns() != null && hbaseOrm.getExcludeColumns().contains(entry.getKey())) { + if (hbaseMapping.getExcludeColumns() != null && hbaseMapping.getExcludeColumns().contains(entry.getKey())) { continue; } if (entry.getValue() != null) { MappingConfig.ColumnItem columnItem = columnItems.get(entry.getKey()); - byte[] bytes = typeConvert(columnItem, hbaseOrm, entry.getValue()); + byte[] bytes = typeConvert(columnItem, hbaseMapping, entry.getValue()); if (columnItem == null) { - String familyName = hbaseOrm.getFamily(); + String familyName = hbaseMapping.getFamily(); String qualifier = entry.getKey(); - if (hbaseOrm.isUppercaseQualifier()) { + if (hbaseMapping.isUppercaseQualifier()) { qualifier = qualifier.toUpperCase(); } - if (hbaseOrm.getRowKey() == null && i == 0) { + if (hbaseMapping.getRowKey() == null && i == 0) { hRow.setRowKey(bytes); } else { hRow.addCell(familyName, qualifier, bytes); @@ -156,15 +156,15 @@ public class HbaseSyncService { return; } - MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping(); // if (!validHTable(config)) { // logger.error("HBase table '{}' not exists", - // hbaseOrm.getHbaseTable()); + // hbaseMapping.getHbaseTable()); // return; // } - MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn(); + MappingConfig.ColumnItem rowKeyColumn = hbaseMapping.getRowKeyColumn(); int index = 0; int i = 1; boolean complete = false; @@ -172,8 +172,8 @@ public class HbaseSyncService { out: for (Map r : data) { byte[] rowKeyBytes; - if (hbaseOrm.getRowKey() != null) { - String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + if (hbaseMapping.getRowKey() != null) { + String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(","); // 判断是否有复合主键修改 for (String updateColumn : old.get(index).keySet()) { @@ -190,23 +190,23 @@ public class HbaseSyncService { rowKeyBytes = Bytes.toBytes(rowKeyVale); } else if (rowKeyColumn == null) { Map rowKey = data.get(0); - rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next()); + rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next()); } else { - rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, r.get(rowKeyColumn.getColumn())); + rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, r.get(rowKeyColumn.getColumn())); } if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空"); - Map columnItems = hbaseOrm.getColumnItems(); + Map columnItems = hbaseMapping.getColumnItems(); HRow hRow = new HRow(rowKeyBytes); for (String updateColumn : old.get(index).keySet()) { - if (hbaseOrm.getExcludeColumns() != null && hbaseOrm.getExcludeColumns().contains(updateColumn)) { + if (hbaseMapping.getExcludeColumns() != null && hbaseMapping.getExcludeColumns().contains(updateColumn)) { continue; } MappingConfig.ColumnItem columnItem = columnItems.get(updateColumn); if (columnItem == null) { - String family = hbaseOrm.getFamily(); + String family = hbaseMapping.getFamily(); String qualifier = updateColumn; - if (hbaseOrm.isUppercaseQualifier()) { + if (hbaseMapping.isUppercaseQualifier()) { qualifier = qualifier.toUpperCase(); } @@ -215,7 +215,7 @@ public class HbaseSyncService { if (newVal == null) { hRow.addCell(family, qualifier, null); } else { - hRow.addCell(family, qualifier, typeConvert(null, hbaseOrm, newVal)); + hRow.addCell(family, qualifier, typeConvert(null, hbaseMapping, newVal)); } } else { // 排除修改id的情况 @@ -227,14 +227,14 @@ public class HbaseSyncService { } else { hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), - typeConvert(columnItem, hbaseOrm, newVal)); + typeConvert(columnItem, hbaseMapping, newVal)); } } } rows.add(hRow); complete = false; - if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) { - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); rows.clear(); complete = true; } @@ -242,7 +242,7 @@ public class HbaseSyncService { index++; } if (!complete && !rows.isEmpty()) { - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); } } @@ -252,45 +252,45 @@ public class HbaseSyncService { return; } - MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping(); // if (!validHTable(config)) { // logger.error("HBase table '{}' not exists", - // hbaseOrm.getHbaseTable()); + // hbaseMapping.getHbaseTable()); // return; // } - MappingConfig.ColumnItem rowKeyColumn = hbaseOrm.getRowKeyColumn(); + MappingConfig.ColumnItem rowKeyColumn = hbaseMapping.getRowKeyColumn(); boolean complete = false; int i = 1; Set rowKeys = new HashSet<>(); for (Map r : data) { byte[] rowKeyBytes; - if (hbaseOrm.getRowKey() != null) { - String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + if (hbaseMapping.getRowKey() != null) { + String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(","); String rowKeyVale = getRowKeys(rowKeyColumns, r); rowKeyBytes = Bytes.toBytes(rowKeyVale); } else if (rowKeyColumn == null) { // 如果不需要类型转换 Map rowKey = data.get(0); - rowKeyBytes = typeConvert(null, hbaseOrm, rowKey.values().iterator().next()); + rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next()); } else { Object val = r.get(rowKeyColumn.getColumn()); - rowKeyBytes = typeConvert(rowKeyColumn, hbaseOrm, val); + rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, val); } if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空"); rowKeys.add(rowKeyBytes); complete = false; - if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rowKeys.isEmpty()) { - hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); + if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rowKeys.isEmpty()) { + hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys); rowKeys.clear(); complete = true; } i++; } if (!complete && !rowKeys.isEmpty()) { - hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); + hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys); } } @@ -300,9 +300,9 @@ public class HbaseSyncService { if (old == null || old.isEmpty() || data == null || data.isEmpty()) { return; } - MappingConfig.HbaseOrm hbaseOrm = config.getHbaseOrm(); + MappingConfig.HbaseMapping hbaseMapping = config.getHbaseMapping(); - String[] rowKeyColumns = hbaseOrm.getRowKey().trim().split(","); + String[] rowKeyColumns = hbaseMapping.getRowKey().trim().split(","); int index = 0; int i = 1; @@ -343,13 +343,13 @@ public class HbaseSyncService { rowKeys.add(oldRowKeyBytes); HRow row = new HRow(newRowKeyBytes); - convertData2Row(hbaseOrm, row, r); + convertData2Row(hbaseMapping, row, r); rows.add(row); complete = false; - if (i % config.getHbaseOrm().getCommitBatch() == 0 && !rows.isEmpty()) { - hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); + if (i % config.getHbaseMapping().getCommitBatch() == 0 && !rows.isEmpty()) { + hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys); - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); rowKeys.clear(); rows.clear(); complete = true; @@ -358,8 +358,8 @@ public class HbaseSyncService { index++; } if (!complete && !rows.isEmpty()) { - hbaseTemplate.deletes(hbaseOrm.getHbaseTable(), rowKeys); - hbaseTemplate.puts(hbaseOrm.getHbaseTable(), rows); + hbaseTemplate.deletes(hbaseMapping.getHbaseTable(), rowKeys); + hbaseTemplate.puts(hbaseMapping.getHbaseTable(), rows); } } @@ -367,32 +367,32 @@ public class HbaseSyncService { * 根据对应的类型进行转换 * * @param columnItem 列项配置 - * @param hbaseOrm hbase映射配置 + * @param hbaseMapping hbase映射配置 * @param value 值 * @return 复合字段rowKey */ - private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseOrm hbaseOrm, + private static byte[] typeConvert(MappingConfig.ColumnItem columnItem, MappingConfig.HbaseMapping hbaseMapping, Object value) { if (value == null) { return null; } byte[] bytes = null; if (columnItem == null || columnItem.getType() == null || "".equals(columnItem.getType())) { - if (MappingConfig.Mode.STRING == hbaseOrm.getMode()) { + if (MappingConfig.Mode.STRING == hbaseMapping.getMode()) { bytes = Bytes.toBytes(value.toString()); - } else if (MappingConfig.Mode.NATIVE == hbaseOrm.getMode()) { + } else if (MappingConfig.Mode.NATIVE == hbaseMapping.getMode()) { bytes = TypeUtil.toBytes(value); - } else if (MappingConfig.Mode.PHOENIX == hbaseOrm.getMode()) { + } else if (MappingConfig.Mode.PHOENIX == hbaseMapping.getMode()) { PhType phType = PhType.getType(value.getClass()); bytes = PhTypeUtil.toBytes(value, phType); } } else { - if (hbaseOrm.getMode() == MappingConfig.Mode.STRING) { + if (hbaseMapping.getMode() == MappingConfig.Mode.STRING) { bytes = Bytes.toBytes(value.toString()); - } else if (hbaseOrm.getMode() == MappingConfig.Mode.NATIVE) { + } else if (hbaseMapping.getMode() == MappingConfig.Mode.NATIVE) { Type type = Type.getType(columnItem.getType()); bytes = TypeUtil.toBytes(value, type); - } else if (hbaseOrm.getMode() == MappingConfig.Mode.PHOENIX) { + } else if (hbaseMapping.getMode() == MappingConfig.Mode.PHOENIX) { PhType phType = PhType.getType(columnItem.getType()); bytes = PhTypeUtil.toBytes(value, phType); } diff --git a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml index d80b95eb..f31341a3 100644 --- a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml +++ b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml @@ -1,5 +1,5 @@ dataSourceKey: defaultDS -hbaseOrm: +hbaseMapping: mode: PHOENIX #NATIVE #STRING destination: example database: mytest # 数据库名 @@ -18,7 +18,7 @@ hbaseOrm: c_time: C_TIME$UNSIGNED_TIMESTAMP birthday: BIRTHDAY$DATE excludeColumns: - - lat + - lat # 忽略字段 # -- NATIVE类型 # $DEFAULT diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 1b1139c1..19fa6182 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -10,9 +10,9 @@ spring: default-property-inclusion: non_null -hbase.zookeeper.quorum: slave1 +hbasezookeeper.quorum: 127.0.0.1 hbase.zookeeper.property.clientPort: 2181 -hbase.zookeeper.znode.parent: /hbase-unsecure +hbase.zookeeper.znode.parent: /hbase canal.conf: canalServerHost: 127.0.0.1:11111 @@ -23,12 +23,12 @@ canal.conf: - instance: example adapterGroups: - outAdapters: -# - name: logger - - name: hbase - properties: - hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} - hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort} - zookeeper.znode.parent: ${hbase.zookeeper.znode.parent} + - name: logger +# - name: hbase +# properties: +# hbase.zookeeper.quorum: ${hbase.zookeeper.quorum} +# hbase.zookeeper.property.clientPort: ${hbase.zookeeper.property.clientPort} +# zookeeper.znode.parent: ${hbase.zookeeper.znode.parent} # mqTopics: # - mqMode: kafka # topic: example @@ -37,11 +37,11 @@ canal.conf: # outAdapters: # - name: logger -adapter.conf: - datasourceConfigs: - defaultDS: - url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true - username: root - password: 121212 - adapterConfigs: - - hbase/mytest_person2.yml +#adapter.conf: +# datasourceConfigs: +# defaultDS: +# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true +# username: root +# password: 121212 +# adapterConfigs: +# - hbase/mytest_person2.yml From a7bf6258f7b611eda80049d79a534073d5e9bb48 Mon Sep 17 00:00:00 2001 From: mcy Date: Sat, 27 Oct 2018 22:23:41 +0800 Subject: [PATCH 214/360] =?UTF-8?q?hbase=E7=B1=BB=E5=9E=8B=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E5=B7=A5=E5=85=B7=E7=B1=BB=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/adapter/hbase/support/PhType.java | 115 ++++-------------- .../client/adapter/hbase/support/Type.java | 71 ++++------- .../adapter/hbase/support/TypeUtil.java | 61 +++++----- 3 files changed, 80 insertions(+), 167 deletions(-) diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java index 662f9348..0a6a74d5 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/PhType.java @@ -1,5 +1,8 @@ package com.alibaba.otter.canal.client.adapter.hbase.support; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Time; @@ -13,65 +16,44 @@ import java.util.Date; * @version 1.0.0 */ public enum PhType { - DEFAULT(-1, "VARCHAR"), UNSIGNED_INT(4, "UNSIGNED_INT"), UNSIGNED_LONG(8, "UNSIGNED_LONG"), - UNSIGNED_TINYINT(1, "UNSIGNED_TINYINT"), UNSIGNED_SMALLINT(2, "UNSIGNED_SMALLINT"), - UNSIGNED_FLOAT(4, "UNSIGNED_FLOAT"), UNSIGNED_DOUBLE(8, "UNSIGNED_DOUBLE"), INTEGER(4, "INTEGER"), - BIGINT(8, "BIGINT"), TINYINT(1, "TINYINT"), SMALLINT(2, "SMALLINT"), FLOAT(4, "FLOAT"), DOUBLE(8, "DOUBLE"), - DECIMAL(-1, "DECIMAL"), BOOLEAN(1, "BOOLEAN"), UNSIGNED_TIME(8, "UNSIGNED_TIME"), - UNSIGNED_DATE(8, "UNSIGNED_DATE"), UNSIGNED_TIMESTAMP(12, "UNSIGNED_TIMESTAMP"), TIME(8, "TIME"), DATE(8, "DATE"), - TIMESTAMP(12, "TIMESTAMP"), VARCHAR(-1, "VARCHAR"), VARBINARY(-1, "VARBINARY"); + DEFAULT, UNSIGNED_INT, UNSIGNED_LONG, UNSIGNED_TINYINT, UNSIGNED_SMALLINT, UNSIGNED_FLOAT, + UNSIGNED_DOUBLE, INTEGER, BIGINT, TINYINT, SMALLINT, FLOAT, DOUBLE, DECIMAL, BOOLEAN, UNSIGNED_TIME, + UNSIGNED_DATE, UNSIGNED_TIMESTAMP, TIME, DATE, TIMESTAMP, VARCHAR, VARBINARY; - /** - * -1:长度可变 - */ - private int len; - private String type; - - PhType(int len, String type){ - this.len = len; - this.type = type; - } - - public int getLen() { - return len; - } - - public String getType() { - return this.type; - } + private static Logger logger = LoggerFactory.getLogger(PhType.class); public static PhType getType(Class javaType) { if (javaType == null) return DEFAULT; PhType phType; - if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) { + if (Integer.class == javaType || int.class == javaType) { phType = INTEGER; - } else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) { + } else if (Long.class == javaType || long.class == javaType) { phType = BIGINT; - } else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) { + } else if (Byte.class == javaType || byte.class == javaType) { phType = TINYINT; - } else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) { + } else if (Short.class == javaType || short.class == javaType) { phType = SMALLINT; - } else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) { + } else if (Float.class == javaType || float.class == javaType) { phType = FLOAT; - } else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) { + } else if (Double.class == javaType || double.class == javaType) { phType = DOUBLE; - } else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) { + } else if (Boolean.class == javaType || boolean.class == javaType) { phType = BOOLEAN; - } else if (java.sql.Date.class.isAssignableFrom(javaType)) { + } else if (java.sql.Date.class == javaType) { phType = DATE; - } else if (Time.class.isAssignableFrom(javaType)) { + } else if (Time.class == javaType) { phType = DATE; - } else if (Timestamp.class.isAssignableFrom(javaType)) { + } else if (Timestamp.class == javaType) { phType = TIMESTAMP; - } else if (Date.class.isAssignableFrom(javaType)) { + } else if (Date.class == javaType) { phType = DATE; - } else if (byte[].class.isAssignableFrom(javaType)) { + } else if (byte[].class == javaType) { phType = VARBINARY; - } else if (String.class.isAssignableFrom(javaType)) { + } else if (String.class == javaType) { phType = VARCHAR; - } else if (BigDecimal.class.isAssignableFrom(javaType)) { + } else if (BigDecimal.class == javaType) { phType = DECIMAL; - } else if (BigInteger.class.isAssignableFrom(javaType)) { + } else if (BigInteger.class == javaType) { phType = UNSIGNED_LONG; } else { phType = DEFAULT; @@ -81,54 +63,11 @@ public enum PhType { public static PhType getType(String type) { if (type == null) return DEFAULT; - PhType phType; - if (type.equalsIgnoreCase(UNSIGNED_INT.type)) { - phType = UNSIGNED_INT; - } else if (type.equalsIgnoreCase(UNSIGNED_LONG.type)) { - phType = UNSIGNED_LONG; - } else if (type.equalsIgnoreCase(UNSIGNED_TINYINT.type)) { - phType = UNSIGNED_TINYINT; - } else if (type.equalsIgnoreCase(UNSIGNED_SMALLINT.type)) { - phType = UNSIGNED_SMALLINT; - } else if (type.equalsIgnoreCase(UNSIGNED_FLOAT.type)) { - phType = UNSIGNED_FLOAT; - } else if (type.equalsIgnoreCase(UNSIGNED_DOUBLE.type)) { - phType = UNSIGNED_DOUBLE; - } else if (type.equalsIgnoreCase(INTEGER.type)) { - phType = INTEGER; - } else if (type.equalsIgnoreCase(BIGINT.type)) { - phType = BIGINT; - } else if (type.equalsIgnoreCase(TINYINT.type)) { - phType = TINYINT; - } else if (type.equalsIgnoreCase(SMALLINT.type)) { - phType = SMALLINT; - } else if (type.equalsIgnoreCase(FLOAT.type)) { - phType = FLOAT; - } else if (type.equalsIgnoreCase(DOUBLE.type)) { - phType = DOUBLE; - } else if (type.equalsIgnoreCase(BOOLEAN.type)) { - phType = BOOLEAN; - } else if (type.equalsIgnoreCase(UNSIGNED_TIME.type)) { - phType = UNSIGNED_TIME; - } else if (type.equalsIgnoreCase(UNSIGNED_DATE.type)) { - phType = UNSIGNED_DATE; - } else if (type.equalsIgnoreCase(UNSIGNED_TIMESTAMP.type)) { - phType = UNSIGNED_TIMESTAMP; - } else if (type.equalsIgnoreCase(TIME.type)) { - phType = TIME; - } else if (type.equalsIgnoreCase(DATE.type)) { - phType = DATE; - } else if (type.equalsIgnoreCase(TIMESTAMP.type)) { - phType = TIMESTAMP; - } else if (type.equalsIgnoreCase(VARCHAR.type)) { - phType = VARCHAR; - } else if (type.equalsIgnoreCase(VARBINARY.type)) { - phType = VARBINARY; - } else if (type.equalsIgnoreCase(DECIMAL.type)) { - phType = DECIMAL; - } else { - phType = DEFAULT; + try { + return PhType.valueOf(type.toUpperCase()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return DEFAULT; } - return phType; } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java index 2a44cb30..bbe3f879 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/Type.java @@ -1,5 +1,8 @@ package com.alibaba.otter.canal.client.adapter.hbase.support; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.math.BigDecimal; import java.math.BigInteger; import java.util.Date; @@ -11,50 +14,20 @@ import java.util.Date; * @version 1.0.0 */ public enum Type { - DEFAULT("STRING"), STRING("STRING"), INTEGER("INTEGER"), LONG("LONG"), SHORT("SHORT"), BOOLEAN("BOOLEAN"), - FLOAT("FLOAT"), DOUBLE("DOUBLE"), BIGDECIMAL("BIGDECIMAL"), DATE("DATE"), BYTE("BYTE"), BYTES("BYTES"); + DEFAULT, STRING, INTEGER, LONG, SHORT, BOOLEAN, FLOAT, DOUBLE, BIGDECIMAL, DATE, BYTE, BYTES; - private String type; - - Type(String type){ - this.type = type; - } - - public String getType() { - return type; - } + private static Logger logger = LoggerFactory.getLogger(Type.class); public static Type getType(String type) { if (type == null) { return DEFAULT; } - Type res; - if (type.equalsIgnoreCase("STRING")) { - res = STRING; - } else if (type.equalsIgnoreCase("INTEGER")) { - res = INTEGER; - } else if (type.equalsIgnoreCase("LONG")) { - res = LONG; - } else if (type.equalsIgnoreCase("SHORT")) { - res = SHORT; - } else if (type.equalsIgnoreCase("BOOLEAN")) { - res = BOOLEAN; - } else if (type.equalsIgnoreCase("FLOAT")) { - res = FLOAT; - } else if (type.equalsIgnoreCase("DOUBLE")) { - res = DOUBLE; - } else if (type.equalsIgnoreCase("BIGDECIMAL")) { - res = BIGDECIMAL; - } else if (type.equalsIgnoreCase("DATE")) { - res = DATE; - } else if (type.equalsIgnoreCase("BYTE")) { - res = BYTE; - } else if (type.equalsIgnoreCase("BYTES")) { - res = BYTES; - } else { - res = DEFAULT; + try { + return Type.valueOf(type); + } catch (Exception e) { + logger.error(e.getMessage(), e); + return DEFAULT; } - return res; } public static Type getType(Class javaType) { @@ -62,29 +35,29 @@ public enum Type { return DEFAULT; } Type type; - if (Integer.class.isAssignableFrom(javaType) || int.class.isAssignableFrom(javaType)) { + if (Integer.class == javaType || int.class == javaType) { type = INTEGER; - } else if (Long.class.isAssignableFrom(javaType) || long.class.isAssignableFrom(javaType)) { + } else if (Long.class == javaType || long.class == javaType) { type = LONG; - } else if (Byte.class.isAssignableFrom(javaType) || byte.class.isAssignableFrom(javaType)) { + } else if (Byte.class == javaType || byte.class == javaType) { type = BYTE; - } else if (Short.class.isAssignableFrom(javaType) || short.class.isAssignableFrom(javaType)) { + } else if (Short.class == javaType || short.class == javaType) { type = SHORT; - } else if (Float.class.isAssignableFrom(javaType) || float.class.isAssignableFrom(javaType)) { + } else if (Float.class == javaType || float.class == javaType) { type = FLOAT; - } else if (Double.class.isAssignableFrom(javaType) || double.class.isAssignableFrom(javaType)) { + } else if (Double.class == javaType || double.class == javaType) { type = DOUBLE; - } else if (Boolean.class.isAssignableFrom(javaType) || boolean.class.isAssignableFrom(javaType)) { + } else if (Boolean.class == javaType || boolean.class == javaType) { type = BOOLEAN; - } else if (Date.class.isAssignableFrom(javaType)) { + } else if (Date.class == javaType) { type = DATE; - } else if (byte[].class.isAssignableFrom(javaType)) { + } else if (byte[].class == javaType) { type = BYTES; - } else if (String.class.isAssignableFrom(javaType)) { + } else if (String.class == javaType) { type = STRING; - } else if (BigDecimal.class.isAssignableFrom(javaType)) { + } else if (BigDecimal.class == javaType) { type = BIGDECIMAL; - } else if (BigInteger.class.isAssignableFrom(javaType)) { + } else if (BigInteger.class == javaType) { type = LONG; } else { type = DEFAULT; diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java index f90e5bd8..22500a47 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/support/TypeUtil.java @@ -21,29 +21,30 @@ public class TypeUtil { return null; } byte[] bytes; - if (obj instanceof String) { + Class clazz = obj.getClass(); + if (clazz == String.class) { bytes = Bytes.toBytes((String) obj); - } else if (obj instanceof Integer) { + } else if (clazz == Integer.class || clazz == int.class) { bytes = Bytes.toBytes((Integer) obj); - } else if (obj instanceof Long) { + } else if (clazz == Long.class || clazz == long.class) { bytes = Bytes.toBytes((Long) obj); - } else if (obj instanceof Short) { + } else if (clazz == Short.class || clazz == short.class) { bytes = Bytes.toBytes((Short) obj); - } else if (obj instanceof Boolean) { + } else if (clazz == Boolean.class || clazz == boolean.class) { bytes = Bytes.toBytes((Boolean) obj); - } else if (obj instanceof Float) { + } else if (clazz == Float.class || clazz == float.class) { bytes = Bytes.toBytes((Float) obj); - } else if (obj instanceof Double) { + } else if (clazz == Double.class || clazz == double.class) { bytes = Bytes.toBytes((Double) obj); - } else if (obj instanceof BigDecimal) { - bytes = Bytes.toBytes((BigDecimal) obj); - } else if (obj instanceof BigInteger) { - bytes = Bytes.toBytes(((BigInteger) obj).longValue()); - } else if (obj instanceof Date) { - bytes = Bytes.toBytes(((Date) obj).getTime()); - } else if (obj instanceof Byte) { + } else if (clazz == Byte.class || clazz == byte.class) { bytes = new byte[] { (byte) obj }; - } else if (obj instanceof byte[]) { + } else if (clazz == BigDecimal.class) { + bytes = Bytes.toBytes((BigDecimal) obj); + } else if (clazz == BigInteger.class) { + bytes = Bytes.toBytes(((BigInteger) obj).longValue()); + } else if (clazz == Date.class) { + bytes = Bytes.toBytes(((Date) obj).getTime()); + } else if (clazz == byte[].class) { bytes = (byte[]) obj; } else { // 其余类型统一转换为string @@ -98,38 +99,38 @@ public class TypeUtil { return null; } Object res; - if (String.class.isAssignableFrom(clazz)) { + if (String.class == clazz) { res = Bytes.toString(bytes); - } else if (Integer.class.isAssignableFrom(clazz)) { + } else if (Integer.class == clazz || int.class == clazz) { res = Bytes.toInt(bytes); - } else if (Long.class.isAssignableFrom(clazz)) { + } else if (Long.class == clazz || long.class == clazz) { res = Bytes.toLong(bytes); - } else if (Short.class.isAssignableFrom(clazz)) { + } else if (Short.class == clazz || short.class == clazz) { res = Bytes.toShort(bytes); - } else if (Boolean.class.isAssignableFrom(clazz)) { + } else if (Boolean.class == clazz || boolean.class == clazz) { res = Bytes.toBoolean(bytes); - } else if (Float.class.isAssignableFrom(clazz)) { + } else if (Float.class == clazz || float.class == clazz) { res = Bytes.toFloat(bytes); - } else if (Double.class.isAssignableFrom(clazz)) { + } else if (Double.class == clazz || double.class == clazz) { res = Bytes.toDouble(bytes); - } else if (BigDecimal.class.isAssignableFrom(clazz)) { + } else if (Byte.class == clazz || byte.class == clazz) { + res = bytes[0]; + } else if (BigDecimal.class == clazz) { res = Bytes.toBigDecimal(bytes); - } else if (BigInteger.class.isAssignableFrom(clazz)) { + } else if (BigInteger.class == clazz) { res = Bytes.toLong(bytes); - } else if (java.sql.Date.class.isAssignableFrom(clazz)) { + } else if (java.sql.Date.class == clazz) { long ts = Bytes.toLong(bytes); res = new java.sql.Date(ts); - } else if (Time.class.isAssignableFrom(clazz)) { + } else if (Time.class == clazz) { long ts = Bytes.toLong(bytes); res = new Time(ts); - } else if (Timestamp.class.isAssignableFrom(clazz)) { + } else if (Timestamp.class == clazz) { long ts = Bytes.toLong(bytes); res = new Timestamp(ts); - } else if (Date.class.isAssignableFrom(clazz)) { + } else if (Date.class == clazz) { long ts = Bytes.toLong(bytes); res = new Date(ts); - } else if (Byte.class.isAssignableFrom(clazz)) { - res = bytes[0]; } else { throw new IllegalArgumentException("mismatch class type"); } From b4845938693b2c947c764d10b2208c1823968bf4 Mon Sep 17 00:00:00 2001 From: duhengforever Date: Sun, 28 Oct 2018 16:09:31 +0800 Subject: [PATCH 215/360] Add flat message support to rocketmq --- .../adapter/loader/CanalAdapterLoader.java | 2 +- .../loader/CanalAdapterRocketMQWorker.java | 34 +++-- .../src/main/resources/canal-client.yml | 2 +- .../rocketmq/RocketMQCanalConnector.java | 134 +++++++++++++++--- .../RocketMQCanalConnectorProvider.java | 6 +- .../otter/canal/deployer/CanalLauncher.java | 1 + deployer/src/main/resources/mq.yml | 2 +- .../canal/rocketmq/CanalRocketMQProducer.java | 91 +++++++++--- 8 files changed, 221 insertions(+), 51 deletions(-) diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java index 471b6a3d..6dd29c80 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterLoader.java @@ -105,7 +105,7 @@ public class CanalAdapterLoader { CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig.getBootstrapServers(), topic.getTopic(), group.getGroupId(), - canalOuterAdapterGroups); + canalOuterAdapterGroups, canalClientConfig.getFlatMessage()); canalMQWorker.put(topic.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); rocketMQWorker.start(); } else if ("kafka".equalsIgnoreCase(topic.getMqMode())) { diff --git a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java index f0f674db..3c3b4abf 100644 --- a/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java +++ b/client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.client.adapter.loader; +import com.alibaba.otter.canal.protocol.FlatMessage; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -23,16 +24,19 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { private RocketMQCanalConnector connector; - private String topic; + private String topic; + + private boolean flatMessage; public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId, - List> canalOuterAdapters){ + List> canalOuterAdapters, boolean flatMessage) { logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); this.canalOuterAdapters = canalOuterAdapters; this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); this.topic = topic; + this.flatMessage = flatMessage; this.canalDestination = topic; - connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId); + connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId, flatMessage); } @Override @@ -80,20 +84,32 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); while (running) { try { - // switcher.get(); //等待开关开启 - - final Message message = connector.getWithoutAck(1); + Object message = null; + if (!flatMessage) { + message = connector.getWithoutAck(1); + } else { + message = connector.getFlatMessageWithoutAck(); + } if (message != null) { + final Object msg = message; executor.submit(new Runnable() { - @Override public void run() { try { - writeOut(message, topic); + if (msg != null) { + if (msg instanceof Message) { + Message receive = (Message) msg; + writeOut(receive, topic); + connector.ack(receive.getId()); + } else { + FlatMessage receive = (FlatMessage) msg; + writeOut(receive); + connector.ack(receive.getId()); + } + } } catch (Exception e) { logger.error(e.getMessage(), e); } - connector.ack(message.getId()); } }); } else { diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml index d2cb2175..211296b5 100644 --- a/client-launcher/src/main/resources/canal-client.yml +++ b/client-launcher/src/main/resources/canal-client.yml @@ -1,7 +1,7 @@ #canalServerHost: 127.0.0.1:11111 #zookeeperHosts: slave1:2181 bootstrapServers: slave1:6667,slave2:6667 #or rocketmq nameservers:host1:9876;host2:9876 -flatMessage: true +flatMessage: false #canalInstances: #- instance: example diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java index 0e086822..85a6119a 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.client.rocketmq; +import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.protocol.FlatMessage; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; @@ -24,21 +26,23 @@ import com.alibaba.otter.canal.protocol.exception.CanalClientException; public class RocketMQCanalConnector implements CanalConnector { - private static final Logger logger = LoggerFactory.getLogger(RocketMQCanalConnector.class); + private static final Logger logger = LoggerFactory.getLogger(RocketMQCanalConnector.class); - private String nameServer; - private String topic; - private String groupName; - private volatile boolean connected = false; - private DefaultMQPushConsumer rocketMQConsumer; - private BlockingQueue> messageBlockingQueue; - Map> messageCache; - private long batchProcessTimeout = 10000; + private String nameServer; + private String topic; + private String groupName; + private volatile boolean connected = false; + private DefaultMQPushConsumer rocketMQConsumer; + private BlockingQueue messageBlockingQueue; + Map messageCache; + private long batchProcessTimeout = 3000; + private boolean flatMessage; - public RocketMQCanalConnector(String nameServer, String topic, String groupName){ + public RocketMQCanalConnector(String nameServer, String topic, String groupName, boolean flatMessage) { this.nameServer = nameServer; this.topic = topic; this.groupName = groupName; + this.flatMessage = flatMessage; messageBlockingQueue = new LinkedBlockingQueue<>(); messageCache = new ConcurrentHashMap<>(); } @@ -70,11 +74,12 @@ public class RocketMQCanalConnector implements CanalConnector { if (rocketMQConsumer == null) { this.connect(); } - rocketMQConsumer.subscribe(topic, "*"); + rocketMQConsumer.subscribe(this.topic, "*"); rocketMQConsumer.registerMessageListener(new MessageListenerOrderly() { @Override - public ConsumeOrderlyStatus consumeMessage(List messageExts, ConsumeOrderlyContext context) { + public ConsumeOrderlyStatus consumeMessage(List messageExts, + ConsumeOrderlyContext context) { context.setAutoCommit(true); boolean isSuccess = process(messageExts); if (isSuccess) { @@ -93,17 +98,33 @@ public class RocketMQCanalConnector implements CanalConnector { } private boolean process(List messageExts) { - BlockingQueue messageList = new LinkedBlockingQueue<>(); + logger.info("Get Message:{}", messageExts); + BlockingQueue messageList = new LinkedBlockingQueue<>(); for (MessageExt messageExt : messageExts) { byte[] data = messageExt.getBody(); - Message message = CanalMessageDeserializer.deserializer(data); - try { - messageList.put(message); - } catch (InterruptedException ex) { - logger.error("Add message error"); + if (data != null){ + try { + if (!flatMessage) { + Message message = CanalMessageDeserializer.deserializer(data); + messageList.put(message); + } else { + FlatMessage flatMessage = JSON.parseObject(data, FlatMessage.class); + messageList.put(flatMessage); + } + } catch (Exception ex) { + logger.error("Add message error", ex); + throw new CanalClientException(ex); + } + }else{ + logger.warn("Received message data is null"); } } - ConsumerBatchMessage batchMessage = new ConsumerBatchMessage<>(messageList); + ConsumerBatchMessage batchMessage; + if (!flatMessage) { + batchMessage = new ConsumerBatchMessage(messageList); + } else { + batchMessage = new ConsumerBatchMessage(messageList); + } try { messageBlockingQueue.put(batchMessage); } catch (InterruptedException e) { @@ -131,17 +152,28 @@ public class RocketMQCanalConnector implements CanalConnector { this.rocketMQConsumer.unsubscribe(this.topic); } + /** + * 暂时不支持batchSize 参数 + * + * @param batchSize 暂时不支持 + * @return + * @throws CanalClientException + */ @Override public Message get(int batchSize) throws CanalClientException { Message message = getWithoutAck(batchSize); - ack(message.getId()); + if (message != null) { + ack(message.getId()); + } return message; } @Override public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { Message message = getWithoutAck(batchSize, timeout, unit); - ack(message.getId()); + if (message != null) { + ack(message.getId()); + } return message; } @@ -155,6 +187,23 @@ public class RocketMQCanalConnector implements CanalConnector { return null; } + private FlatMessage getFlatMessage(ConsumerBatchMessage consumerBatchMessage) { + BlockingQueue messageList = consumerBatchMessage.getData(); + if (messageList != null & messageList.size() > 0) { + FlatMessage message = messageList.poll(); + messageCache.put(message.getId(), consumerBatchMessage); + return message; + } + return null; + } + + /** + * 暂时不支持该参数设置 + * + * @param batchSize + * @return + * @throws CanalClientException + */ @Override public Message getWithoutAck(int batchSize) throws CanalClientException { ConsumerBatchMessage batchMessage = messageBlockingQueue.poll(); @@ -168,11 +217,51 @@ public class RocketMQCanalConnector implements CanalConnector { public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { try { ConsumerBatchMessage batchMessage = messageBlockingQueue.poll(timeout, unit); - return getMessage(batchMessage); + if (batchMessage != null) { + return getMessage(batchMessage); + } } catch (InterruptedException ex) { logger.warn("Get message timeout", ex); throw new CanalClientException("Failed to fetch the data after: " + timeout); } + return null; + } + + public FlatMessage getFlatMessageWithoutAck() { + return getFlatMessageWithoutAck(null, null); + } + + public FlatMessage getFlatMessageWithoutAck(Long timeout, + TimeUnit unit) throws CanalClientException { + try { + ConsumerBatchMessage batchMessage = null; + if (timeout == null || timeout == 0) { + batchMessage = messageBlockingQueue.poll(); + } else { + batchMessage = messageBlockingQueue.poll(timeout, unit); + } + if (batchMessage != null) { + return getFlatMessage(batchMessage); + } + } catch (InterruptedException ex) { + logger.warn("Get flat message timeout", ex); + throw new CanalClientException("Failed to fetch the flat message data after: " + timeout); + } + return null; + } + + public FlatMessage getFlatMessage() throws CanalClientException { + FlatMessage message = getFlatMessageWithoutAck(null, null); + if (message != null) { + ack(message.getId()); + } + return message; + } + + public FlatMessage getFlatMessage(Long timeout, TimeUnit unit) throws CanalClientException { + FlatMessage message = getFlatMessageWithoutAck(timeout, unit); + ack(message.getId()); + return message; } @Override @@ -180,6 +269,7 @@ public class RocketMQCanalConnector implements CanalConnector { ConsumerBatchMessage batchMessage = messageCache.get(batchId); if (batchMessage != null) { batchMessage.ack(); + messageCache.remove(batchId); } } diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java index f651ccb3..32ed7a2d 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java @@ -14,7 +14,11 @@ public class RocketMQCanalConnectorProvider { * @return {@link RocketMQCanalConnector} */ public static RocketMQCanalConnector newRocketMQConnector(String nameServers, String topic, String groupId) { - return new RocketMQCanalConnector(nameServers, topic, groupId); + return new RocketMQCanalConnector(nameServers, topic, groupId, false); } + public static RocketMQCanalConnector newRocketMQConnector(String nameServers, String topic, String groupId, + boolean flatMessage) { + return new RocketMQCanalConnector(nameServers, topic, groupId, flatMessage); + } } diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 081248e5..6c36cb0c 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -57,6 +57,7 @@ public class CanalLauncher { CanalMQProducer canalMQProducer = null; String serverMode = controller.getProperty(properties, CanalConstants.CANAL_SERVER_MODE); + serverMode = "rocketmq"; if (serverMode.equalsIgnoreCase("kafka")) { canalMQProducer = new CanalKafkaProducer(); } else if (serverMode.equalsIgnoreCase("rocketmq")) { diff --git a/deployer/src/main/resources/mq.yml b/deployer/src/main/resources/mq.yml index b4bb9c29..2b217fb6 100644 --- a/deployer/src/main/resources/mq.yml +++ b/deployer/src/main/resources/mq.yml @@ -8,7 +8,7 @@ bufferMemory: 33554432 canalBatchSize: 50 # Canal get数据的超时时间, 单位: 毫秒, 空为不限超时 canalGetTimeout: 100 -flatMessage: true +flatMessage: false canalDestinations: - canalDestination: example diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index de19bae9..e8ae17e8 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -1,10 +1,13 @@ package com.alibaba.otter.canal.rocketmq; +import com.alibaba.fastjson.JSON; import com.alibaba.otter.canal.common.CanalMessageSerializer; import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.server.exception.CanalServerException; import com.alibaba.otter.canal.spi.CanalMQProducer; import java.util.List; +import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; @@ -19,10 +22,13 @@ public class CanalRocketMQProducer implements CanalMQProducer { private static final Logger logger = LoggerFactory.getLogger(CanalRocketMQProducer.class); - private DefaultMQProducer defaultMQProducer; + private DefaultMQProducer defaultMQProducer; + + private MQProperties mqProperties; @Override public void init(MQProperties rocketMQProperties) { + this.mqProperties = rocketMQProperties; defaultMQProducer = new DefaultMQProducer(); defaultMQProducer.setNamesrvAddr(rocketMQProperties.getServers()); defaultMQProducer.setProducerGroup(rocketMQProperties.getProducerGroup()); @@ -37,25 +43,78 @@ public class CanalRocketMQProducer implements CanalMQProducer { @Override public void send(final MQProperties.CanalDestination destination, com.alibaba.otter.canal.protocol.Message data, - Callback callback) { - try { - Message message = new Message(destination.getTopic(), CanalMessageSerializer.serializer(data)); - this.defaultMQProducer.send(message, new MessageQueueSelector() { + Callback callback) { + if (!mqProperties.getFlatMessage()) { + try { + Message message = new Message(destination.getTopic(), CanalMessageSerializer.serializer(data)); + logger.debug("send message:{} to destination:{}, partition: {}", message, destination.getCanalDestination(), destination.getPartition()); + this.defaultMQProducer.send(message, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + int partition = 0; + if (destination.getPartition() != null) { + partition = destination.getPartition(); + } + return mqs.get(partition); + } + }, null); + callback.commit(); + } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { + logger.error("Send message error!", e); + callback.rollback(); + } + } else { + List flatMessages = FlatMessage.messageConverter(data); + if (flatMessages != null) { + for (FlatMessage flatMessage : flatMessages) { + if (destination.getPartition() != null) { + try { + logger.info("send flat message: {} to topic: {} fixed partition: {}", JSON.toJSONString(flatMessage),destination.getTopic(), destination.getPartition()); + Message message = new Message(destination.getTopic(), JSON.toJSONString(flatMessage).getBytes()); + this.defaultMQProducer.send(message, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + return mqs.get(destination.getPartition()); + } + }, null); + } catch (Exception e) { + logger.error("send flat message to fixed partition error", e); + callback.rollback(); + } + } else { + if (destination.getPartitionHash() != null + && !destination.getPartitionHash().isEmpty()) { + FlatMessage[] partitionFlatMessage = FlatMessage.messagePartition(flatMessage, + destination.getPartitionsNum(), + destination.getPartitionHash()); + int length = partitionFlatMessage.length; + for (int i = 0; i < length; i++) { + FlatMessage flatMessagePart = partitionFlatMessage[i]; + logger.debug("flatMessagePart: {}, partition: {}", JSON.toJSONString(flatMessagePart), i); + final int index = i; + try { + Message message = new Message(destination.getTopic(), JSON.toJSONString(flatMessagePart).getBytes()); + this.defaultMQProducer.send(message, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + if (index > mqs.size()) { + throw new CanalServerException("partition number is error,config num:" + destination.getPartitionsNum() + ", mq num: " + mqs.size()); + } + return mqs.get(index); + } + }, null); + } catch (Exception e) { + logger.error("send flat message to hashed partition error", e); + callback.rollback(); + } - @Override - public MessageQueue select(List mqs, Message msg, Object arg) { - int partition = 0; - if (destination.getPartition() != null) { - partition = destination.getPartition(); + } + } } - return mqs.get(partition); } - }, null); - callback.commit(); - } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) { - logger.error("Send message error!", e); - callback.rollback(); + } } + } @Override From 86fd77f9c4dd89137ef044ed38f1a17160b1bf1d Mon Sep 17 00:00:00 2001 From: duhengforever Date: Sun, 28 Oct 2018 17:00:33 +0800 Subject: [PATCH 216/360] Resolve conflict --- .../loader/CanalAdapterRocketMQWorker.java | 39 +++++++------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index 5b41e926..f4360df8 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -15,9 +15,8 @@ import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider; import com.alibaba.otter.canal.protocol.Message; /** - * kafka对应的client适配器工作线程 + * rocketmq对应的client适配器工作线程 * - * @author rewerma 2018-8-19 下午11:30:49 * @version 1.0.0 */ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { @@ -65,13 +64,19 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { message = connector.getFlatMessageWithoutAck(); } if (message != null) { -<<<<<<< HEAD:client-launcher/src/main/java/com/alibaba/otter/canal/client/adapter/loader/CanalAdapterRocketMQWorker.java final Object msg = message; executor.submit(new Runnable() { @Override public void run() { try { + if (logger.isDebugEnabled()) { + logger.debug("topic: {} batchId: {} batchSize: {} ", + topic, + message.getId(), + message.getEntries().size()); + } if (msg != null) { + long begin = System.currentTimeMillis(); if (msg instanceof Message) { Message receive = (Message) msg; writeOut(receive, topic); @@ -81,31 +86,17 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { writeOut(receive); connector.ack(receive.getId()); } + long now = System.currentTimeMillis(); + if ((now - begin) > 5 * 60 * 1000) { + logger.error("topic: {} batchId {} elapsed time: {} ms", + topic, + message.getId(), + now - begin); + } } } catch (Exception e) { logger.error(e.getMessage(), e); } -======= - executor.submit(() -> { - try { - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId: {} batchSize: {} ", - topic, - message.getId(), - message.getEntries().size()); - } - long begin = System.currentTimeMillis(); - writeOut(message); - long now = System.currentTimeMillis(); - if ((System.currentTimeMillis() - begin) > 5 * 60 * 1000) { - logger.error("topic: {} batchId {} elapsed time: {} ms", - topic, - message.getId(), - now - begin); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); ->>>>>>> 2c6bd3ee1b23f30fdb2b1f748805284a4fed872c:client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java } connector.ack(message.getId()); }); From a0b38a9cacbebca6b001de233e55cded25172a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Sun, 28 Oct 2018 19:52:26 +0800 Subject: [PATCH 217/360] fixed MQConnector --- .../adapter/support/CanalClientConfig.java | 23 +- .../canal/client/adapter/support/Result.java | 1 + client-adapter/example/.gitignore | 1 + .../loader/CanalAdapterKafkaWorker.java | 16 +- .../launcher/loader/CanalAdapterLoader.java | 15 +- .../loader/CanalAdapterRocketMQWorker.java | 77 ++--- .../launcher/loader/CanalAdapterWorker.java | 2 - .../src/main/resources/application.yml | 7 + .../otter/canal/client/CanalMQConnector.java | 91 ++++++ .../client/kafka/KafkaCanalConnector.java | 137 ++++++--- .../kafka/running/ClientRunningData.java | 39 --- .../kafka/running/ClientRunningListener.java | 21 -- .../kafka/running/ClientRunningMonitor.java | 280 ------------------ .../client/rocketmq/ConsumerBatchMessage.java | 10 +- .../rocketmq/RocketMQCanalConnector.java | 232 +++++++-------- ...ider.java => RocketMQCanalConnectors.java} | 4 +- .../rocketmq/RocketMQCanalListener.java | 13 - .../otter/canal/deployer/CanalLauncher.java | 15 +- .../canal/rocketmq/CanalRocketMQProducer.java | 51 ++-- 19 files changed, 383 insertions(+), 652 deletions(-) create mode 100644 client-adapter/example/.gitignore create mode 100644 client/src/main/java/com/alibaba/otter/canal/client/CanalMQConnector.java delete mode 100644 client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningData.java delete mode 100644 client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningListener.java delete mode 100644 client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningMonitor.java rename client/src/main/java/com/alibaba/otter/canal/client/rocketmq/{RocketMQCanalConnectorProvider.java => RocketMQCanalConnectors.java} (84%) delete mode 100644 client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 29da946e..f08c6435 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -75,7 +75,7 @@ public class CanalClientConfig { private String instance; // 实例名 - private List adapterGroups; // 适配器分组列表 + private List groups; // 适配器分组列表 public String getInstance() { return instance; @@ -87,16 +87,17 @@ public class CanalClientConfig { } } - public List getAdapterGroups() { - return adapterGroups; + public List getGroups() { + return groups; } - public void setAdapterGroups(List adapterGroups) { - this.adapterGroups = adapterGroups; + public void setGroups(List groups) { + this.groups = groups; } + } - public static class AdapterGroup { + public static class Group { private List outAdapters; // 适配器列表 @@ -115,7 +116,7 @@ public class CanalClientConfig { private String topic; // topic名 - private List groups = new ArrayList<>(); // 分组列表 + private List groups = new ArrayList<>(); // 分组列表 public String getMqMode() { return mqMode; @@ -133,21 +134,19 @@ public class CanalClientConfig { this.topic = topic; } - public List getGroups() { + public List getGroups() { return groups; } - public void setGroups(List groups) { + public void setGroups(List groups) { this.groups = groups; } } - public static class Group { + public static class MQGroup { private String groupId; // group id - // private List adapters = new ArrayList<>(); - private List outAdapters; // 适配器配置列表 public String getGroupId() { diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java index a9602d05..6e9a9954 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Result.java @@ -11,6 +11,7 @@ import java.util.Date; */ public class Result implements Serializable { + private static final long serialVersionUID = -3276409502352405716L; public Integer code = 20000; public Object data; public String message; diff --git a/client-adapter/example/.gitignore b/client-adapter/example/.gitignore new file mode 100644 index 00000000..ae3c1726 --- /dev/null +++ b/client-adapter/example/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 665ad38e..1ff58e9a 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -1,8 +1,6 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.apache.kafka.clients.consumer.CommitFailedException; @@ -23,19 +21,16 @@ import com.alibaba.otter.canal.protocol.Message; public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { private KafkaCanalConnector connector; - private String topic; - private boolean flatMessage; public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId, List> canalOuterAdapters, boolean flatMessage){ this.canalOuterAdapters = canalOuterAdapters; - this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); this.topic = topic; this.canalDestination = topic; this.flatMessage = flatMessage; - connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage); + this.connector = KafkaCanalConnectors.newKafkaConnector(bootstrapServers, topic, null, groupId, flatMessage); // connector.setSessionTimeout(1L, TimeUnit.MINUTES); } @@ -48,7 +43,6 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { protected void process() { while (!running) ; - ExecutorService executor = Executors.newSingleThreadExecutor(); while (running) { try { logger.info("=============> Start to connect topic: {} <=============", this.topic); @@ -62,9 +56,9 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { List messages; if (!flatMessage) { - messages = connector.getWithoutAck(); + messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS); } else { - messages = connector.getFlatMessageWithoutAck(100L, TimeUnit.MILLISECONDS); + messages = connector.getFlatListWithoutAck(100L, TimeUnit.MILLISECONDS); } if (messages != null) { for (final Object message : messages) { @@ -78,7 +72,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { connector.ack(); } catch (CommitFailedException e) { logger.warn(e.getMessage()); - } catch (Exception e) { + } catch (Throwable e) { logger.error(e.getMessage(), e); TimeUnit.SECONDS.sleep(1L); } @@ -88,8 +82,6 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { } } - executor.shutdown(); - try { connector.unsubscribe(); } catch (WakeupException e) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 73606998..627ca8f6 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -58,7 +58,7 @@ public class CanalAdapterLoader { for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { List> canalOuterAdapterGroups = new ArrayList<>(); - for (CanalClientConfig.AdapterGroup connectorGroup : instance.getAdapterGroups()) { + for (CanalClientConfig.Group connectorGroup : instance.getGroups()) { List canalOutConnectors = new ArrayList<>(); for (OuterAdapterConfig c : connectorGroup.getOutAdapters()) { loadConnector(c, canalOutConnectors); @@ -82,11 +82,9 @@ public class CanalAdapterLoader { // 初始化canal-client-mq的适配器 if (canalClientConfig.getMqTopics() != null) { for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) { - for (CanalClientConfig.Group group : topic.getGroups()) { + for (CanalClientConfig.MQGroup group : topic.getGroups()) { List> canalOuterAdapterGroups = new ArrayList<>(); - List canalOuterAdapters = new ArrayList<>(); - for (OuterAdapterConfig config : group.getOutAdapters()) { loadConnector(config, canalOuterAdapters); } @@ -95,12 +93,12 @@ public class CanalAdapterLoader { CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig.getBootstrapServers(), topic.getTopic(), group.getGroupId(), - canalOuterAdapterGroups, canalClientConfig.getFlatMessage()); + canalOuterAdapterGroups, + canalClientConfig.getFlatMessage()); canalMQWorker.put(topic.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); rocketMQWorker.start(); } else if ("kafka".equalsIgnoreCase(topic.getMqMode())) { - CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker( - canalClientConfig.getBootstrapServers(), + CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig.getBootstrapServers(), topic.getTopic(), group.getGroupId(), canalOuterAdapterGroups, @@ -108,9 +106,8 @@ public class CanalAdapterLoader { canalMQWorker.put(topic.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker); canalKafkaWorker.start(); } - logger.info("Start adapter for canal-client rocketmq topic: {} succeed", + logger.info("Start adapter for canal-client mq topic: {} succeed", topic.getTopic() + "-" + group.getGroupId()); - } } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index f4360df8..fce63946 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -1,17 +1,14 @@ package com.alibaba.otter.canal.adapter.launcher.loader; -import com.alibaba.otter.canal.protocol.FlatMessage; import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import org.apache.kafka.clients.consumer.CommitFailedException; import org.apache.kafka.common.errors.WakeupException; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector; -import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider; +import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectors; +import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.Message; /** @@ -22,20 +19,17 @@ import com.alibaba.otter.canal.protocol.Message; public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { private RocketMQCanalConnector connector; - - private String topic; - - private boolean flatMessage; + private String topic; + private boolean flatMessage; public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId, - List> canalOuterAdapters, boolean flatMessage) { + List> canalOuterAdapters, boolean flatMessage){ logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); this.canalOuterAdapters = canalOuterAdapters; - this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); this.topic = topic; this.flatMessage = flatMessage; this.canalDestination = topic; - connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId, flatMessage); + this.connector = RocketMQCanalConnectors.newRocketMQConnector(nameServers, topic, groupId, flatMessage); } @Override @@ -47,7 +41,6 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { protected void process() { while (!running) ; - ExecutorService executor = Executors.newSingleThreadExecutor(); while (running) { try { logger.info("=============> Start to connect topic: {} <=============", this.topic); @@ -57,55 +50,23 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); while (running) { try { - Object message = null; + List messages; if (!flatMessage) { - message = connector.getWithoutAck(1); + messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS); } else { - message = connector.getFlatMessageWithoutAck(); + messages = connector.getFlatListWithoutAck(100L, TimeUnit.MILLISECONDS); } - if (message != null) { - final Object msg = message; - executor.submit(new Runnable() { - @Override - public void run() { - try { - if (logger.isDebugEnabled()) { - logger.debug("topic: {} batchId: {} batchSize: {} ", - topic, - message.getId(), - message.getEntries().size()); - } - if (msg != null) { - long begin = System.currentTimeMillis(); - if (msg instanceof Message) { - Message receive = (Message) msg; - writeOut(receive, topic); - connector.ack(receive.getId()); - } else { - FlatMessage receive = (FlatMessage) msg; - writeOut(receive); - connector.ack(receive.getId()); - } - long now = System.currentTimeMillis(); - if ((now - begin) > 5 * 60 * 1000) { - logger.error("topic: {} batchId {} elapsed time: {} ms", - topic, - message.getId(), - now - begin); - } - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } + if (messages != null) { + for (final Object message : messages) { + if (message instanceof FlatMessage) { + writeOut((FlatMessage) message); + } else { + writeOut((Message) message); } - connector.ack(message.getId()); - }); - } else { - logger.debug("Message is null"); + } } - } catch (CommitFailedException e) { - logger.warn(e.getMessage()); - } catch (Exception e) { + connector.ack(); + } catch (Throwable e) { logger.error(e.getMessage(), e); TimeUnit.SECONDS.sleep(1L); } @@ -115,8 +76,6 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { } } - executor.shutdown(); - try { connector.unsubscribe(); } catch (WakeupException e) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 33a70f89..2a6dc1f7 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -90,7 +90,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { long batchId = message.getId(); try { int size = message.getEntries().size(); - if (batchId == -1 || size == 0) { Thread.sleep(1000); } else { @@ -131,7 +130,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { // ignore } } - } } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 19fa6182..9182e43f 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -36,6 +36,13 @@ canal.conf: # - groupId: g2 # outAdapters: # - name: logger +# mqTopics: +# - mqMode: rocketmq +# topic: example +# groups: +# - groupId: g2 +# outAdapters: +# - name: logger #adapter.conf: # datasourceConfigs: diff --git a/client/src/main/java/com/alibaba/otter/canal/client/CanalMQConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/CanalMQConnector.java new file mode 100644 index 00000000..8ad86022 --- /dev/null +++ b/client/src/main/java/com/alibaba/otter/canal/client/CanalMQConnector.java @@ -0,0 +1,91 @@ +package com.alibaba.otter.canal.client; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import com.alibaba.otter.canal.protocol.FlatMessage; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; + +/** + * canal MQ数据操作客户端 + * + *
      + * 1. canal server写入MQ消息,考虑性能会合并多条数据写入为一个MQ消息,一个Message对应一个MQ消息
      + * 2. canal client消费MQ消息,因为client性能会弱于server的写入,MQ数据获取时会拿到堆积的多条MQ消息,会拿到List
      + * 3. client的ack/rollback,都是和MQ直接交互,不存在对应的batchId概念
      + * 
      + * + * @author agapple 2018年10月28日 下午6:42:27 + * @since 1.1.1 + */ +public interface CanalMQConnector extends CanalConnector { + + /** + * 获取数据,自动进行确认,设置timeout时间直到拿到数据为止 + * + *
      +     * 该方法返回的条件:
      +     *  a. 如果timeout=0,有多少取多少,不会阻塞等待
      +     *  b. 如果timeout不为0,尝试阻塞对应的超时时间,直到拿到数据就返回
      +     * 
      + * + * @return + * @throws CanalClientException + */ + List getList(Long timeout, TimeUnit unit) throws CanalClientException; + + /** + * 获取数据,设置timeout时间直到拿到数据为止 + * + *
      +     * 该方法返回的条件:
      +     *  a. 如果timeout=0,有多少取多少,不会阻塞等待
      +     *  b. 如果timeout不为0,尝试阻塞对应的超时时间,直到拿到数据就返回
      +     * 
      + * + * @throws CanalClientException + */ + List getListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException; + + /** + * 获取数据,自动进行确认,设置timeout时间直到拿到数据为止 + * + *
      +     * 该方法返回的条件:
      +     *  a. 如果timeout=0,有多少取多少,不会阻塞等待
      +     *  b. 如果timeout不为0,尝试阻塞对应的超时时间,直到拿到数据就返回
      +     * 
      + * + * @return + * @throws CanalClientException + */ + List getFlatList(Long timeout, TimeUnit unit) throws CanalClientException; + + /** + * 获取数据,设置timeout时间直到拿到数据为止 + * + *
      +     * 该方法返回的条件:
      +     *  a. 如果timeout=0,有多少取多少,不会阻塞等待
      +     *  b. 如果timeout不为0,尝试阻塞对应的超时时间,直到拿到数据就返回
      +     * 
      + * + * @throws CanalClientException + */ + List getFlatListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException; + + /** + * 消费确认。 + * + * @throws CanalClientException + */ + void ack() throws CanalClientException; + + /** + * 回滚到未进行 {@link #ack} 的地方,下次fetch的时候,可以从最后一个没有 {@link #ack} 的地方开始拿 + * + * @throws CanalClientException + */ + void rollback() throws CanalClientException; +} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java index b3f8800e..6439b1b0 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java @@ -13,16 +13,25 @@ import org.apache.kafka.common.TopicPartition; import org.apache.kafka.common.serialization.StringDeserializer; import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.client.CanalMQConnector; +import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import com.google.common.collect.Lists; /** * canal kafka 数据操作客户端 + * + *
      + * 注意点:
      + * 1. 相比于canal {@linkplain SimpleCanalConnector}, 这里get和ack操作不能有并发, 必须是一个线程执行get后,内存里执行完毕ack后再取下一个get
      + * 
      * * @author machengyuan @ 2018-6-12 - * @version 1.0.0 + * @version 1.1.1 */ -public class KafkaCanalConnector { +public class KafkaCanalConnector implements CanalMQConnector { private KafkaConsumer kafkaConsumer; private KafkaConsumer kafkaConsumer2; // 用于扁平message的数据消费 @@ -55,18 +64,6 @@ public class KafkaCanalConnector { } } - /** - * 重新设置sessionTime - * - * @param timeout - * @param unit - */ - public void setSessionTimeout(Long timeout, TimeUnit unit) { - long t = unit.toMillis(timeout); - properties.put("request.timeout.ms", String.valueOf(t + 60000)); - properties.put("session.timeout.ms", String.valueOf(t)); - } - /** * 打开连接 */ @@ -76,7 +73,6 @@ public class KafkaCanalConnector { } connected = true; - if (kafkaConsumer == null && !flatMessage) { kafkaConsumer = new KafkaConsumer(properties); } @@ -151,62 +147,61 @@ public class KafkaCanalConnector { } } - /** - * 获取数据,自动进行确认 - * - * @return - */ - public List get() { - return get(100L, TimeUnit.MILLISECONDS); - } - - public List get(Long timeout, TimeUnit unit) { + @Override + public List getList(Long timeout, TimeUnit unit) throws CanalClientException { waitClientRunning(); if (!running) { - return null; + return Lists.newArrayList(); } - List messages = getWithoutAck(timeout, unit); - this.ack(); + List messages = getListWithoutAck(timeout, unit); + if (messages != null && !messages.isEmpty()) { + this.ack(); + } return messages; } - public List getWithoutAck() { - return getWithoutAck(100L, TimeUnit.MILLISECONDS); - } - - /** - * 获取数据,不进行确认,等待处理完成手工确认 - * - * @return - */ - public List getWithoutAck(Long timeout, TimeUnit unit) { + @Override + public List getListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException { waitClientRunning(); if (!running) { - return null; + return Lists.newArrayList(); } ConsumerRecords records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据 if (!records.isEmpty()) { - // return records.iterator().next().value(); List messages = new ArrayList<>(); for (ConsumerRecord record : records) { messages.add(record.value()); } return messages; } - return null; + return Lists.newArrayList(); } - public List getFlatMessageWithoutAck(Long timeout, TimeUnit unit) { + @Override + public List getFlatList(Long timeout, TimeUnit unit) throws CanalClientException { waitClientRunning(); if (!running) { - return null; + return Lists.newArrayList(); + } + + List messages = getFlatListWithoutAck(timeout, unit); + if (messages != null && !messages.isEmpty()) { + this.ack(); + } + return messages; + } + + @Override + public List getFlatListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException { + waitClientRunning(); + if (!running) { + return Lists.newArrayList(); } ConsumerRecords records = kafkaConsumer2.poll(unit.toMillis(timeout)); - if (!records.isEmpty()) { List flatMessages = new ArrayList<>(); for (ConsumerRecord record : records) { @@ -217,7 +212,11 @@ public class KafkaCanalConnector { return flatMessages; } - return null; + return Lists.newArrayList(); + } + + @Override + public void rollback() throws CanalClientException { } /** @@ -245,4 +244,52 @@ public class KafkaCanalConnector { // } } } + + @Override + public void subscribe(String filter) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public Message get(int batchSize) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public Message getWithoutAck(int batchSize) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public void ack(long batchId) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public void rollback(long batchId) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + /** + * 重新设置sessionTime + * + * @param timeout + * @param unit + */ + public void setSessionTimeout(Long timeout, TimeUnit unit) { + long t = unit.toMillis(timeout); + properties.put("request.timeout.ms", String.valueOf(t + 60000)); + properties.put("session.timeout.ms", String.valueOf(t)); + } + } diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningData.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningData.java deleted file mode 100644 index e528f9ee..00000000 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningData.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.alibaba.otter.canal.client.kafka.running; - -/** - * client running状态信息 - * - * @author machengyuan 2018-06-20 下午04:10:12 - * @version 1.0.0 - */ -public class ClientRunningData { - - private String groupId; - private String address; - private boolean active = true; - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public String getAddress() { - return address; - } - - public void setAddress(String address) { - this.address = address; - } - - public boolean isActive() { - return active; - } - - public void setActive(boolean active) { - this.active = active; - } - -} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningListener.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningListener.java deleted file mode 100644 index 016f7f10..00000000 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningListener.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.alibaba.otter.canal.client.kafka.running; - -/** - * client running状态信息 - * - * @author machengyuan 2018-06-20 下午04:10:12 - * @version 1.0.0 - */ -public interface ClientRunningListener { - - /** - * 触发现在轮到自己做为active - */ - public void processActiveEnter(); - - /** - * 触发一下当前active模式失败 - */ - public void processActiveExit(); - -} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningMonitor.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningMonitor.java deleted file mode 100644 index 8f0e6d92..00000000 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/running/ClientRunningMonitor.java +++ /dev/null @@ -1,280 +0,0 @@ -package com.alibaba.otter.canal.client.kafka.running; - -import java.text.MessageFormat; -import java.util.Random; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import org.I0Itec.zkclient.IZkDataListener; -import org.I0Itec.zkclient.exception.ZkException; -import org.I0Itec.zkclient.exception.ZkInterruptedException; -import org.I0Itec.zkclient.exception.ZkNoNodeException; -import org.I0Itec.zkclient.exception.ZkNodeExistsException; -import org.apache.zookeeper.CreateMode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.MDC; - -import com.alibaba.otter.canal.common.AbstractCanalLifeCycle; -import com.alibaba.otter.canal.common.utils.AddressUtils; -import com.alibaba.otter.canal.common.utils.BooleanMutex; -import com.alibaba.otter.canal.common.utils.JsonUtils; -import com.alibaba.otter.canal.common.zookeeper.ZkClientx; -import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; -import com.alibaba.otter.canal.protocol.exception.CanalClientException; - -/** - * kafka client running状态信息 - * - * @author machengyuan 2018-06-20 下午04:10:12 - * @version 1.0.0 - */ -public class ClientRunningMonitor extends AbstractCanalLifeCycle { - - private static final String TOPIC_ROOT_NODE = ZookeeperPathUtils.CANAL_ROOT_NODE - + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR + "topics"; - - private static final String TOPIC_NODE = TOPIC_ROOT_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR - + "{0}"; - - private static final String TOPIC_CLIENTID_NODE = TOPIC_NODE + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR - + "{1}"; - - private static final String TOPIC_CLIENTID_RUNNING_NODE = TOPIC_CLIENTID_NODE - + ZookeeperPathUtils.ZOOKEEPER_SEPARATOR - + ZookeeperPathUtils.RUNNING_NODE; - - private static String getTopicClientRunning(String topic, String groupId) { - return MessageFormat.format(TOPIC_CLIENTID_RUNNING_NODE, topic, groupId); - } - - private static String getClientIdNodePath(String topic, String groupId) { - return MessageFormat.format(TOPIC_CLIENTID_NODE, topic, groupId); - } - - private static final Logger logger = LoggerFactory.getLogger(ClientRunningMonitor.class); - private ZkClientx zkClient; - private String topic; - private ClientRunningData clientData; - private IZkDataListener dataListener; - private BooleanMutex mutex = new BooleanMutex(false); - private volatile boolean release = false; - private volatile ClientRunningData activeData; - private ScheduledExecutorService delayExector = Executors.newScheduledThreadPool(1); - private ClientRunningListener listener; - private int delayTime = 5; - - private static Integer virtualPort; - - public ClientRunningMonitor(){ - if (virtualPort == null) { - Random rand = new Random(); - virtualPort = rand.nextInt(9000) + 1000; - } - - dataListener = new IZkDataListener() { - - public void handleDataChange(String dataPath, Object data) throws Exception { - MDC.put("kafkaTopic", topic); - ClientRunningData runningData = JsonUtils.unmarshalFromByte((byte[]) data, ClientRunningData.class); - if (!isMine(runningData.getAddress())) { - mutex.set(false); - } - - if (!runningData.isActive() && isMine(runningData.getAddress())) { // 说明出现了主动释放的操作,并且本机之前是active - release = true; - releaseRunning();// 彻底释放mainstem - } - - activeData = (ClientRunningData) runningData; - } - - public void handleDataDeleted(String dataPath) throws Exception { - MDC.put("kafkaTopic", topic); - - mutex.set(false); - // 触发一下退出,可能是人为干预的释放操作或者网络闪断引起的session expired timeout - processActiveExit(); - if (!release && activeData != null && isMine(activeData.getAddress())) { - // 如果上一次active的状态就是本机,则即时触发一下active抢占 - initRunning(); - } else { - // 否则就是等待delayTime,避免因网络瞬端或者zk异常,导致出现频繁的切换操作 - delayExector.schedule(new Runnable() { - - public void run() { - initRunning(); - } - }, delayTime, TimeUnit.SECONDS); - } - } - - }; - - } - - public void start() { - super.start(); - - String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - - zkClient.subscribeDataChanges(path, dataListener); - initRunning(); - } - - public void stop() { - super.stop(); - String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - zkClient.unsubscribeDataChanges(path, dataListener); - releaseRunning(); // 尝试一下release - // Fix issue #697 - if (delayExector != null) { - delayExector.shutdown(); - } - } - - // 改动记录: - // 1,在方法上加synchronized关键字,保证同步顺序执行; - // 2,判断Zk上已经存在的activeData是否是本机,是的话把mutex重置为true,否则会导致死锁 - // 3,增加异常处理,保证出现异常时,running节点能被删除,否则会导致死锁 - public synchronized void initRunning() { - if (!isStart()) { - return; - } - - String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - // 序列化 - byte[] bytes = JsonUtils.marshalToByte(clientData); - try { - mutex.set(false); - zkClient.create(path, bytes, CreateMode.EPHEMERAL); - processActiveEnter();// 触发一下事件 - activeData = clientData; - mutex.set(true); - } catch (ZkNodeExistsException e) { - bytes = zkClient.readData(path, true); - if (bytes == null) {// 如果不存在节点,立即尝试一次 - initRunning(); - } else { - activeData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class); - // 如果发现已经存在,判断一下是否自己,避免活锁 - if (activeData.getAddress().contains(":") && isMine(activeData.getAddress())) { - mutex.set(true); - } - } - } catch (ZkNoNodeException e) { - zkClient.createPersistent(getClientIdNodePath(this.topic, clientData.getGroupId()), true); // 尝试创建父节点 - initRunning(); - } catch (Throwable t) { - logger.error(MessageFormat.format("There is an error when execute initRunning method, with destination [{0}].", - topic), - t); - // 出现任何异常尝试release - releaseRunning(); - throw new CanalClientException("something goes wrong in initRunning method. ", t); - } - } - - /** - * 阻塞等待自己成为active,如果自己成为active,立马返回 - * - * @throws InterruptedException - */ - public void waitForActive() throws InterruptedException { - initRunning(); - mutex.get(); - } - - /** - * 检查当前的状态 - */ - public boolean check() { - String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - // ZookeeperPathUtils.getDestinationClientRunning(this.destination, - // clientData.getClientId()); - try { - byte[] bytes = zkClient.readData(path); - ClientRunningData eventData = JsonUtils.unmarshalFromByte(bytes, ClientRunningData.class); - activeData = eventData;// 更新下为最新值 - // 检查下nid是否为自己 - boolean result = isMine(activeData.getAddress()); - if (!result) { - logger.warn("canal is running in [{}] , but not in [{}]", - activeData.getAddress(), - clientData.getAddress()); - } - return result; - } catch (ZkNoNodeException e) { - logger.warn("canal is not run any in node"); - return false; - } catch (ZkInterruptedException e) { - logger.warn("canal check is interrupt"); - Thread.interrupted();// 清除interrupt标记 - return check(); - } catch (ZkException e) { - logger.warn("canal check is failed"); - return false; - } - } - - public boolean releaseRunning() { - if (check()) { - String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - zkClient.delete(path); - mutex.set(false); - processActiveExit(); - return true; - } - - return false; - } - - // ====================== helper method ====================== - - private boolean isMine(String address) { - return address.equals(clientData.getAddress()); - } - - private void processActiveEnter() { - if (listener != null) { - // 触发回调 - listener.processActiveEnter(); - this.clientData.setAddress(/* address */AddressUtils.getHostIp() + ":" + virtualPort); - - String path = getTopicClientRunning(this.topic, clientData.getGroupId()); - // 序列化 - byte[] bytes = JsonUtils.marshalToByte(clientData); - zkClient.writeData(path, bytes); - } - } - - private void processActiveExit() { - if (listener != null) { - listener.processActiveExit(); - } - } - - public void setListener(ClientRunningListener listener) { - this.listener = listener; - } - - // ===================== setter / getter ======================= - - public void setTopic(String topic) { - this.topic = topic; - } - - public void setClientData(ClientRunningData clientData) { - this.clientData = clientData; - } - - public void setDelayTime(int delayTime) { - this.delayTime = delayTime; - } - - public void setZkClient(ZkClientx zkClient) { - this.zkClient = zkClient; - } - -} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java index af156878..1def9163 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/ConsumerBatchMessage.java @@ -1,18 +1,18 @@ package com.alibaba.otter.canal.client.rocketmq; -import java.util.concurrent.BlockingQueue; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; public class ConsumerBatchMessage { - private final BlockingQueue data; + private final List data; private CountDownLatch latch; private boolean hasFailure = false; - public ConsumerBatchMessage(BlockingQueue data){ + public ConsumerBatchMessage(List data){ this.data = data; - latch = new CountDownLatch(data.size()); + latch = new CountDownLatch(1); } public boolean waitFinish(long timeout) throws InterruptedException { @@ -23,7 +23,7 @@ public class ConsumerBatchMessage { return !hasFailure; } - public BlockingQueue getData() { + public List getData() { return data; } diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java index 85a6119a..987406ba 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java @@ -1,11 +1,7 @@ package com.alibaba.otter.canal.client.rocketmq; -import com.alibaba.fastjson.JSON; -import com.alibaba.otter.canal.protocol.FlatMessage; import java.util.List; -import java.util.Map; import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; @@ -19,35 +15,47 @@ import org.apache.rocketmq.common.message.MessageExt; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.otter.canal.client.CanalConnector; +import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.CanalMessageDeserializer; +import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; +import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.exception.CanalClientException; +import com.google.common.collect.Lists; -public class RocketMQCanalConnector implements CanalConnector { +/** + * RocketMQ的连接 + * + *
      + * 注意点:
      + * 1. 相比于canal {@linkplain SimpleCanalConnector}, 这里get和ack操作不能有并发, 必须是一个线程执行get后,内存里执行完毕ack后再取下一个get
      + * 
      + * + * @since 1.1.1 + */ +public class RocketMQCanalConnector implements CanalMQConnector { - private static final Logger logger = LoggerFactory.getLogger(RocketMQCanalConnector.class); + private static final Logger logger = LoggerFactory.getLogger(RocketMQCanalConnector.class); - private String nameServer; - private String topic; - private String groupName; - private volatile boolean connected = false; - private DefaultMQPushConsumer rocketMQConsumer; + private String nameServer; + private String topic; + private String groupName; + private volatile boolean connected = false; + private DefaultMQPushConsumer rocketMQConsumer; private BlockingQueue messageBlockingQueue; - Map messageCache; - private long batchProcessTimeout = 3000; - private boolean flatMessage; + private long batchProcessTimeout = 60 * 1000; + private boolean flatMessage; + private volatile ConsumerBatchMessage lastGetBatchMessage = null; - public RocketMQCanalConnector(String nameServer, String topic, String groupName, boolean flatMessage) { + public RocketMQCanalConnector(String nameServer, String topic, String groupName, boolean flatMessage){ this.nameServer = nameServer; this.topic = topic; this.groupName = groupName; this.flatMessage = flatMessage; - messageBlockingQueue = new LinkedBlockingQueue<>(); - messageCache = new ConcurrentHashMap<>(); + this.messageBlockingQueue = new LinkedBlockingQueue<>(1024); } - @Override public void connect() throws CanalClientException { rocketMQConsumer = new DefaultMQPushConsumer(groupName); if (!StringUtils.isBlank(nameServer)) { @@ -55,17 +63,14 @@ public class RocketMQCanalConnector implements CanalConnector { } } - @Override public void disconnect() throws CanalClientException { rocketMQConsumer.shutdown(); } - @Override public boolean checkValid() throws CanalClientException { return connected; } - @Override public synchronized void subscribe(String filter) throws CanalClientException { if (connected) { return; @@ -78,8 +83,7 @@ public class RocketMQCanalConnector implements CanalConnector { rocketMQConsumer.registerMessageListener(new MessageListenerOrderly() { @Override - public ConsumeOrderlyStatus consumeMessage(List messageExts, - ConsumeOrderlyContext context) { + public ConsumeOrderlyStatus consumeMessage(List messageExts, ConsumeOrderlyContext context) { context.setAutoCommit(true); boolean isSuccess = process(messageExts); if (isSuccess) { @@ -99,23 +103,23 @@ public class RocketMQCanalConnector implements CanalConnector { private boolean process(List messageExts) { logger.info("Get Message:{}", messageExts); - BlockingQueue messageList = new LinkedBlockingQueue<>(); + List messageList = Lists.newArrayList(); for (MessageExt messageExt : messageExts) { byte[] data = messageExt.getBody(); - if (data != null){ + if (data != null) { try { if (!flatMessage) { Message message = CanalMessageDeserializer.deserializer(data); - messageList.put(message); + messageList.add(message); } else { FlatMessage flatMessage = JSON.parseObject(data, FlatMessage.class); - messageList.put(flatMessage); + messageList.add(flatMessage); } } catch (Exception ex) { logger.error("Add message error", ex); throw new CanalClientException(ex); } - }else{ + } else { logger.warn("Received message data is null"); } } @@ -142,145 +146,117 @@ public class RocketMQCanalConnector implements CanalConnector { return isCompleted && isSuccess; } - @Override public void subscribe() throws CanalClientException { this.subscribe(null); } - @Override public void unsubscribe() throws CanalClientException { this.rocketMQConsumer.unsubscribe(this.topic); } - /** - * 暂时不支持batchSize 参数 - * - * @param batchSize 暂时不支持 - * @return - * @throws CanalClientException - */ @Override - public Message get(int batchSize) throws CanalClientException { - Message message = getWithoutAck(batchSize); - if (message != null) { - ack(message.getId()); + public List getList(Long timeout, TimeUnit unit) throws CanalClientException { + List messages = getListWithoutAck(timeout, unit); + if (messages != null && !messages.isEmpty()) { + ack(); } - return message; + return messages; } @Override - public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { - Message message = getWithoutAck(batchSize, timeout, unit); - if (message != null) { - ack(message.getId()); - } - return message; - } - - private Message getMessage(ConsumerBatchMessage consumerBatchMessage) { - BlockingQueue messageList = consumerBatchMessage.getData(); - if (messageList != null & messageList.size() > 0) { - Message message = messageList.poll(); - messageCache.put(message.getId(), consumerBatchMessage); - return message; - } - return null; - } - - private FlatMessage getFlatMessage(ConsumerBatchMessage consumerBatchMessage) { - BlockingQueue messageList = consumerBatchMessage.getData(); - if (messageList != null & messageList.size() > 0) { - FlatMessage message = messageList.poll(); - messageCache.put(message.getId(), consumerBatchMessage); - return message; - } - return null; - } - - /** - * 暂时不支持该参数设置 - * - * @param batchSize - * @return - * @throws CanalClientException - */ - @Override - public Message getWithoutAck(int batchSize) throws CanalClientException { - ConsumerBatchMessage batchMessage = messageBlockingQueue.poll(); - if (batchMessage != null) { - return getMessage(batchMessage); - } - return null; - } - - @Override - public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { + public List getListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException { try { + if (this.lastGetBatchMessage != null) { + throw new CanalClientException("mq get/ack not support concurrent & async ack"); + } + ConsumerBatchMessage batchMessage = messageBlockingQueue.poll(timeout, unit); if (batchMessage != null) { - return getMessage(batchMessage); + this.lastGetBatchMessage = batchMessage; + return batchMessage.getData(); } } catch (InterruptedException ex) { logger.warn("Get message timeout", ex); throw new CanalClientException("Failed to fetch the data after: " + timeout); } - return null; + return Lists.newArrayList(); } - public FlatMessage getFlatMessageWithoutAck() { - return getFlatMessageWithoutAck(null, null); + @Override + public List getFlatList(Long timeout, TimeUnit unit) throws CanalClientException { + List messages = getFlatListWithoutAck(timeout, unit); + if (messages != null && !messages.isEmpty()) { + ack(); + } + return messages; } - public FlatMessage getFlatMessageWithoutAck(Long timeout, - TimeUnit unit) throws CanalClientException { + @Override + public List getFlatListWithoutAck(Long timeout, TimeUnit unit) throws CanalClientException { try { - ConsumerBatchMessage batchMessage = null; - if (timeout == null || timeout == 0) { - batchMessage = messageBlockingQueue.poll(); - } else { - batchMessage = messageBlockingQueue.poll(timeout, unit); + if (this.lastGetBatchMessage != null) { + throw new CanalClientException("mq get/ack not support concurrent & async ack"); } + + ConsumerBatchMessage batchMessage = messageBlockingQueue.poll(timeout, unit); if (batchMessage != null) { - return getFlatMessage(batchMessage); + this.lastGetBatchMessage = batchMessage; + return batchMessage.getData(); } } catch (InterruptedException ex) { - logger.warn("Get flat message timeout", ex); - throw new CanalClientException("Failed to fetch the flat message data after: " + timeout); + logger.warn("Get message timeout", ex); + throw new CanalClientException("Failed to fetch the data after: " + timeout); } - return null; - } - - public FlatMessage getFlatMessage() throws CanalClientException { - FlatMessage message = getFlatMessageWithoutAck(null, null); - if (message != null) { - ack(message.getId()); - } - return message; - } - - public FlatMessage getFlatMessage(Long timeout, TimeUnit unit) throws CanalClientException { - FlatMessage message = getFlatMessageWithoutAck(timeout, unit); - ack(message.getId()); - return message; + return Lists.newArrayList(); } @Override - public void ack(long batchId) throws CanalClientException { - ConsumerBatchMessage batchMessage = messageCache.get(batchId); - if (batchMessage != null) { - batchMessage.ack(); - messageCache.remove(batchId); + public void ack() throws CanalClientException { + try { + this.lastGetBatchMessage.ack(); + } catch (Throwable e) { + this.lastGetBatchMessage.fail(); + } finally { + this.lastGetBatchMessage = null; } } - @Override - public void rollback(long batchId) throws CanalClientException { - - } - @Override public void rollback() throws CanalClientException { + try { + this.lastGetBatchMessage.fail(); + } finally { + this.lastGetBatchMessage = null; + } + } + public Message get(int batchSize) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public Message get(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public Message getWithoutAck(int batchSize) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public Message getWithoutAck(int batchSize, Long timeout, TimeUnit unit) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public void ack(long batchId) throws CanalClientException { + throw new CanalClientException("mq not support this method"); + } + + @Override + public void rollback(long batchId) throws CanalClientException { + throw new CanalClientException("mq not support this method"); } @Override diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectors.java similarity index 84% rename from client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java rename to client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectors.java index 32ed7a2d..6dadd44f 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectorProvider.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnectors.java @@ -3,7 +3,7 @@ package com.alibaba.otter.canal.client.rocketmq; /** * RocketMQ connector provider. */ -public class RocketMQCanalConnectorProvider { +public class RocketMQCanalConnectors { /** * Create RocketMQ connector @@ -18,7 +18,7 @@ public class RocketMQCanalConnectorProvider { } public static RocketMQCanalConnector newRocketMQConnector(String nameServers, String topic, String groupId, - boolean flatMessage) { + boolean flatMessage) { return new RocketMQCanalConnector(nameServers, topic, groupId, flatMessage); } } diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java deleted file mode 100644 index 2c4ffaaa..00000000 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalListener.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.alibaba.otter.canal.client.rocketmq; - -import java.util.List; - -import org.apache.rocketmq.common.message.MessageExt; - -/** - * RocketMQ message listener - */ -public interface RocketMQCanalListener { - - boolean onReceive(List messageExts); -} diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 6c36cb0c..171264a7 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -1,14 +1,16 @@ package com.alibaba.otter.canal.deployer; +import java.io.FileInputStream; +import java.util.Properties; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.alibaba.otter.canal.kafka.CanalKafkaProducer; import com.alibaba.otter.canal.rocketmq.CanalRocketMQProducer; import com.alibaba.otter.canal.server.CanalMQStarter; import com.alibaba.otter.canal.spi.CanalMQProducer; -import java.io.FileInputStream; -import java.util.Properties; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * canal独立版本启动的入口类 @@ -19,7 +21,7 @@ import org.slf4j.LoggerFactory; public class CanalLauncher { private static final String CLASSPATH_URL_PREFIX = "classpath:"; - private static final Logger logger = LoggerFactory.getLogger(CanalLauncher.class); + private static final Logger logger = LoggerFactory.getLogger(CanalLauncher.class); public static void main(String[] args) throws Throwable { try { @@ -57,7 +59,6 @@ public class CanalLauncher { CanalMQProducer canalMQProducer = null; String serverMode = controller.getProperty(properties, CanalConstants.CANAL_SERVER_MODE); - serverMode = "rocketmq"; if (serverMode.equalsIgnoreCase("kafka")) { canalMQProducer = new CanalKafkaProducer(); } else if (serverMode.equalsIgnoreCase("rocketmq")) { diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index e8ae17e8..0c699d7f 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -1,13 +1,7 @@ package com.alibaba.otter.canal.rocketmq; -import com.alibaba.fastjson.JSON; -import com.alibaba.otter.canal.common.CanalMessageSerializer; -import com.alibaba.otter.canal.common.MQProperties; -import com.alibaba.otter.canal.protocol.FlatMessage; -import com.alibaba.otter.canal.server.exception.CanalServerException; -import com.alibaba.otter.canal.spi.CanalMQProducer; import java.util.List; -import org.apache.kafka.clients.producer.ProducerRecord; + import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; @@ -18,13 +12,20 @@ import org.apache.rocketmq.remoting.exception.RemotingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.common.CanalMessageSerializer; +import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.protocol.FlatMessage; +import com.alibaba.otter.canal.server.exception.CanalServerException; +import com.alibaba.otter.canal.spi.CanalMQProducer; + public class CanalRocketMQProducer implements CanalMQProducer { private static final Logger logger = LoggerFactory.getLogger(CanalRocketMQProducer.class); - private DefaultMQProducer defaultMQProducer; + private DefaultMQProducer defaultMQProducer; - private MQProperties mqProperties; + private MQProperties mqProperties; @Override public void init(MQProperties rocketMQProperties) { @@ -43,12 +44,16 @@ public class CanalRocketMQProducer implements CanalMQProducer { @Override public void send(final MQProperties.CanalDestination destination, com.alibaba.otter.canal.protocol.Message data, - Callback callback) { + Callback callback) { if (!mqProperties.getFlatMessage()) { try { Message message = new Message(destination.getTopic(), CanalMessageSerializer.serializer(data)); - logger.debug("send message:{} to destination:{}, partition: {}", message, destination.getCanalDestination(), destination.getPartition()); + logger.debug("send message:{} to destination:{}, partition: {}", + message, + destination.getCanalDestination(), + destination.getPartition()); this.defaultMQProducer.send(message, new MessageQueueSelector() { + @Override public MessageQueue select(List mqs, Message msg, Object arg) { int partition = 0; @@ -69,9 +74,14 @@ public class CanalRocketMQProducer implements CanalMQProducer { for (FlatMessage flatMessage : flatMessages) { if (destination.getPartition() != null) { try { - logger.info("send flat message: {} to topic: {} fixed partition: {}", JSON.toJSONString(flatMessage),destination.getTopic(), destination.getPartition()); - Message message = new Message(destination.getTopic(), JSON.toJSONString(flatMessage).getBytes()); + logger.info("send flat message: {} to topic: {} fixed partition: {}", + JSON.toJSONString(flatMessage), + destination.getTopic(), + destination.getPartition()); + Message message = new Message(destination.getTopic(), JSON.toJSONString(flatMessage) + .getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { + @Override public MessageQueue select(List mqs, Message msg, Object arg) { return mqs.get(destination.getPartition()); @@ -82,23 +92,28 @@ public class CanalRocketMQProducer implements CanalMQProducer { callback.rollback(); } } else { - if (destination.getPartitionHash() != null - && !destination.getPartitionHash().isEmpty()) { + if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) { FlatMessage[] partitionFlatMessage = FlatMessage.messagePartition(flatMessage, destination.getPartitionsNum(), destination.getPartitionHash()); int length = partitionFlatMessage.length; for (int i = 0; i < length; i++) { FlatMessage flatMessagePart = partitionFlatMessage[i]; - logger.debug("flatMessagePart: {}, partition: {}", JSON.toJSONString(flatMessagePart), i); + logger.debug("flatMessagePart: {}, partition: {}", + JSON.toJSONString(flatMessagePart), + i); final int index = i; try { - Message message = new Message(destination.getTopic(), JSON.toJSONString(flatMessagePart).getBytes()); + Message message = new Message(destination.getTopic(), + JSON.toJSONString(flatMessagePart).getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { + @Override public MessageQueue select(List mqs, Message msg, Object arg) { if (index > mqs.size()) { - throw new CanalServerException("partition number is error,config num:" + destination.getPartitionsNum() + ", mq num: " + mqs.size()); + throw new CanalServerException("partition number is error,config num:" + + destination.getPartitionsNum() + + ", mq num: " + mqs.size()); } return mqs.get(index); } From 232bbaf6d419462486029699861050532203ea0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Sun, 28 Oct 2018 19:58:07 +0800 Subject: [PATCH 218/360] fixed group name --- client-adapter/launcher/src/main/resources/application.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 9182e43f..250d6a60 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -21,7 +21,7 @@ canal.conf: flatMessage: true canalInstances: - instance: example - adapterGroups: + groups: - outAdapters: - name: logger # - name: hbase From c36a51b8e3fd426cc5406b8cd0f6e0a4f1774be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Sun, 28 Oct 2018 20:29:00 +0800 Subject: [PATCH 219/360] fixed client-adpater logger test --- .../canal/client/adapter/support/MessageUtil.java | 10 ++++++++-- .../launcher/loader/AbstractCanalAdapterWorker.java | 1 - 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index ed5a4358..847b9328 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -1,6 +1,11 @@ package com.alibaba.otter.canal.client.adapter.support; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -102,8 +107,9 @@ public class MessageUtil { if (!old.isEmpty()) { dml.setOld(old); } - consumer.accept(dml); } + + consumer.accept(dml); } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index 0486045c..f8438ae4 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -51,7 +51,6 @@ public abstract class AbstractCanalAdapterWorker { adapters.forEach(adapter -> { long begin = System.currentTimeMillis(); MessageUtil.parse4Dml(canalDestination, message, adapter::sync); - if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", adapter.getClass().getName(), From 071545987b8637e9db748795d0cc66398d819622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Sun, 28 Oct 2018 21:15:57 +0800 Subject: [PATCH 220/360] fixed mq config --- .../kafka/CanalKafkaClientExample.java | 3 +-- .../rocketmq/CanalRocketMQClientExample.java | 25 +++++++++++-------- .../otter/canal/deployer/CanalController.java | 2 +- .../otter/canal/deployer/CanalLauncher.java | 21 ++++++++++------ deployer/src/main/resources/mq.yml | 4 +-- .../otter/canal/protocol/FlatMessage.java | 1 - .../otter/canal/common/MQProperties.java | 4 +-- .../otter/canal/server/CanalMQStarter.java | 11 ++++---- 8 files changed, 39 insertions(+), 32 deletions(-) diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java index e9889370..db090c34 100644 --- a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java @@ -108,8 +108,7 @@ public class CanalKafkaClientExample { connector.subscribe(); while (running) { try { - List messages = connector.getWithoutAck(1L, TimeUnit.SECONDS); // 获取message - + List messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS); // 获取message if (messages == null) { continue; } diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java b/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java index b04f5b24..f50dedcf 100644 --- a/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java @@ -1,12 +1,15 @@ package com.alibaba.otter.canal.client.running.rocketmq; +import java.util.List; +import java.util.concurrent.TimeUnit; + import org.apache.kafka.common.errors.WakeupException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector; -import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectorProvider; +import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnectors; import com.alibaba.otter.canal.client.running.kafka.AbstractKafkaTest; import com.alibaba.otter.canal.protocol.Message; @@ -34,7 +37,7 @@ public class CanalRocketMQClientExample extends AbstractRocektMQTest { }; public CanalRocketMQClientExample(String nameServers, String topic, String groupId){ - connector = RocketMQCanalConnectorProvider.newRocketMQConnector(nameServers, topic, groupId); + connector = RocketMQCanalConnectors.newRocketMQConnector(nameServers, topic, groupId); } public static void main(String[] args) { @@ -103,21 +106,23 @@ public class CanalRocketMQClientExample extends AbstractRocektMQTest { connector.connect(); connector.subscribe(); while (running) { - Message message = connector.getWithoutAck(1); // 获取message - try { - if (message == null) { - continue; - } + List messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS); // 获取message + for (Message message : messages) { long batchId = message.getId(); int size = message.getEntries().size(); if (batchId == -1 || size == 0) { + // try { + // Thread.sleep(1000); + // } catch (InterruptedException e) { + // } } else { + // printSummary(message, batchId, size); + // printEntry(message.getEntries()); logger.info(message.toString()); } - } catch (Exception e) { - logger.error(e.getMessage(), e); } - connector.ack(message.getId()); // 提交确认 + + connector.ack(); // 提交确认 } } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 42821405..a15921de 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -387,7 +387,7 @@ public class CanalController { return config; } - public String getProperty(Properties properties, String key) { + public static String getProperty(Properties properties, String key) { key = StringUtils.trim(key); String value = System.getProperty(key); diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 171264a7..3277c8c1 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -38,6 +38,19 @@ public class CanalLauncher { properties.load(new FileInputStream(conf)); } + CanalMQProducer canalMQProducer = null; + String serverMode = CanalController.getProperty(properties, CanalConstants.CANAL_SERVER_MODE); + if (serverMode.equalsIgnoreCase("kafka")) { + canalMQProducer = new CanalKafkaProducer(); + } else if (serverMode.equalsIgnoreCase("rocketmq")) { + canalMQProducer = new CanalRocketMQProducer(); + } + + if (canalMQProducer != null) { + // disable netty + System.setProperty(CanalConstants.CANAL_WITHOUT_NETTY, "true"); + } + logger.info("## start the canal server."); final CanalController controller = new CanalController(properties); controller.start(); @@ -57,19 +70,11 @@ public class CanalLauncher { }); - CanalMQProducer canalMQProducer = null; - String serverMode = controller.getProperty(properties, CanalConstants.CANAL_SERVER_MODE); - if (serverMode.equalsIgnoreCase("kafka")) { - canalMQProducer = new CanalKafkaProducer(); - } else if (serverMode.equalsIgnoreCase("rocketmq")) { - canalMQProducer = new CanalRocketMQProducer(); - } if (canalMQProducer != null) { CanalMQStarter canalServerStarter = new CanalMQStarter(canalMQProducer); if (canalServerStarter != null) { canalServerStarter.init(); } - } } catch (Throwable e) { logger.error("## Something goes wrong when starting up the canal Server:", e); diff --git a/deployer/src/main/resources/mq.yml b/deployer/src/main/resources/mq.yml index 2b217fb6..019d2123 100644 --- a/deployer/src/main/resources/mq.yml +++ b/deployer/src/main/resources/mq.yml @@ -6,9 +6,9 @@ bufferMemory: 33554432 # Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下) canalBatchSize: 50 -# Canal get数据的超时时间, 单位: 毫秒, 空为不限超时 +# Canal get数据的超时时间, 单位: 毫秒, 0为不限超时 canalGetTimeout: 100 -flatMessage: false +flatMessage: true canalDestinations: - canalDestination: example diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index d8821f46..7a529f28 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -277,7 +277,6 @@ public class FlatMessage implements Serializable { partitionsNum = 1; } FlatMessage[] partitionMessages = new FlatMessage[partitionsNum]; - String pk = pkHashConfig.get(flatMessage.getDatabase() + "." + flatMessage.getTable()); if (pk == null || flatMessage.getIsDdl()) { partitionMessages[0] = flatMessage; diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index 9fce04bf..14db1deb 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -12,7 +12,7 @@ import java.util.Map; */ public class MQProperties { - private String servers = "localhost:6667"; + private String servers = "127.0.0.1:6667"; private int retries = 0; private int batchSize = 16384; private int lingerMs = 1; @@ -20,7 +20,7 @@ public class MQProperties { private boolean filterTransactionEntry = true; private String producerGroup = "Canal-Producer"; private int canalBatchSize = 50; - private Long canalGetTimeout; + private Long canalGetTimeout = 100L; private boolean flatMessage = true; private List canalDestinations = new ArrayList(); diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index 413c42b2..2e861d3f 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -121,15 +121,14 @@ public class CanalMQStarter { server.subscribe(clientIdentity); logger.info("## the canal consumer {} is running now ......", destination.getCanalDestination()); + Long getTimeout = properties.getCanalGetTimeout(); + int getBatchSize = properties.getCanalBatchSize(); while (running) { Message message; - if (properties.getCanalGetTimeout() != null) { - message = server.getWithoutAck(clientIdentity, - properties.getCanalBatchSize(), - properties.getCanalGetTimeout(), - TimeUnit.MILLISECONDS); + if (getTimeout != null && getTimeout > 0) { + message = server.getWithoutAck(clientIdentity, getBatchSize, getTimeout, TimeUnit.MILLISECONDS); } else { - message = server.getWithoutAck(clientIdentity, properties.getCanalBatchSize()); + message = server.getWithoutAck(clientIdentity, getBatchSize); } final long batchId = message.getId(); From 3b4d28eec36e880a476de8a6217f34a2575da8e3 Mon Sep 17 00:00:00 2001 From: mcy Date: Sun, 28 Oct 2018 21:30:54 +0800 Subject: [PATCH 221/360] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/running/kafka/KafkaClientRunningTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java index fcdc0bf6..1b2afec4 100644 --- a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/KafkaClientRunningTest.java @@ -40,7 +40,7 @@ public class KafkaClientRunningTest extends AbstractKafkaTest { connector.subscribe(); while (running) { try { - List messages = connector.getWithoutAck(3L, TimeUnit.SECONDS); + List messages = connector.getList(3L, TimeUnit.SECONDS); if (messages != null) { System.out.println(messages); } From cdb34571abc7ba6e006465a300d5f5a9ee64f395 Mon Sep 17 00:00:00 2001 From: mcy Date: Sun, 28 Oct 2018 21:31:57 +0800 Subject: [PATCH 222/360] =?UTF-8?q?adapter=20readme=20=20=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-adapter/README.md b/client-adapter/README.md index 31d6b5a1..b3d92c77 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -164,7 +164,7 @@ adapter.conf: 修改 config/hbase/mytest_person.yml文件: ``` dataSourceKey: defaultDS # 对应application.yml中的datasourceConfigs下的配置 -hbaseOrm: # mysql--HBase的单表映射配置 +hbaseMapping: # mysql--HBase的单表映射配置 mode: STRING # HBase中的存储类型, 默认统一存为String, 可选: #PHOENIX #NATIVE #STRING # NATIVE: 以java类型为主, PHOENIX: 将类型转换为Phoenix对应的类型 destination: example # 对应 canal destination/MQ topic 名称 From 00fce300725479c5079cd82611d02222f27c8e4d Mon Sep 17 00:00:00 2001 From: mcy Date: Sun, 28 Oct 2018 21:50:19 +0800 Subject: [PATCH 223/360] =?UTF-8?q?group=20=E7=BA=BF=E7=A8=8B=E6=B1=A0?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E5=88=9D=E5=A7=8B=E5=8C=96=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/launcher/loader/AbstractCanalAdapterWorker.java | 5 ++++- .../adapter/launcher/loader/CanalAdapterKafkaWorker.java | 2 +- .../adapter/launcher/loader/CanalAdapterRocketMQWorker.java | 4 ++-- .../canal/adapter/launcher/loader/CanalAdapterWorker.java | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index f8438ae4..333b0805 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import org.slf4j.Logger; @@ -36,7 +37,9 @@ public abstract class AbstractCanalAdapterWorker { protected SyncSwitch syncSwitch; - public AbstractCanalAdapterWorker(){ + public AbstractCanalAdapterWorker(List> canalOuterAdapters){ + this.canalOuterAdapters = canalOuterAdapters; + this.groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); syncSwitch = (SyncSwitch) SpringContext.getBean(SyncSwitch.class); } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 1ff58e9a..e74641ca 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -26,7 +26,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { public CanalAdapterKafkaWorker(String bootstrapServers, String topic, String groupId, List> canalOuterAdapters, boolean flatMessage){ - this.canalOuterAdapters = canalOuterAdapters; + super(canalOuterAdapters); this.topic = topic; this.canalDestination = topic; this.flatMessage = flatMessage; diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index fce63946..db80d602 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -24,12 +24,12 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { public CanalAdapterRocketMQWorker(String nameServers, String topic, String groupId, List> canalOuterAdapters, boolean flatMessage){ - logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); - this.canalOuterAdapters = canalOuterAdapters; + super(canalOuterAdapters); this.topic = topic; this.flatMessage = flatMessage; this.canalDestination = topic; this.connector = RocketMQCanalConnectors.newRocketMQConnector(nameServers, topic, groupId, flatMessage); + logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); } @Override diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 2a6dc1f7..826e9ccd 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -34,7 +34,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { */ public CanalAdapterWorker(String canalDestination, SocketAddress address, List> canalOuterAdapters){ - this.canalOuterAdapters = canalOuterAdapters; + super(canalOuterAdapters); this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); connector = CanalConnectors.newSingleConnector(address, canalDestination, "", ""); @@ -49,7 +49,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { */ public CanalAdapterWorker(String canalDestination, String zookeeperHosts, List> canalOuterAdapters){ - this.canalOuterAdapters = canalOuterAdapters; + super(canalOuterAdapters); this.canalDestination = canalDestination; groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", ""); From 167858cd958c380db75915848261b6eaaf7f6221 Mon Sep 17 00:00:00 2001 From: mcy Date: Sun, 28 Oct 2018 22:35:32 +0800 Subject: [PATCH 224/360] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=88=86=E5=B8=83?= =?UTF-8?q?=E5=BC=8F=E5=90=8C=E6=AD=A5=E5=BC=80=E5=85=B3=E5=85=B3=E9=97=AD?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E7=8A=B6=E6=80=81=E4=B8=8B,=20=E9=87=8D?= =?UTF-8?q?=E5=90=AF=20kafka=20canal-adapter=E5=90=8E=E5=9C=A8=E5=BC=80?= =?UTF-8?q?=E5=90=AF=E5=90=8C=E6=AD=A5,=E4=B8=A2=E5=A4=B1=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/example/.gitignore | 1 - .../loader/CanalAdapterKafkaWorker.java | 9 +++++-- .../loader/CanalAdapterRocketMQWorker.java | 7 ++++++ .../src/main/resources/canal-client.yml | 24 ------------------- .../client/kafka/KafkaCanalConnector.java | 2 ++ 5 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 client-adapter/example/.gitignore delete mode 100644 client-launcher/src/main/resources/canal-client.yml diff --git a/client-adapter/example/.gitignore b/client-adapter/example/.gitignore deleted file mode 100644 index ae3c1726..00000000 --- a/client-adapter/example/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/bin/ diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index e74641ca..41664c7c 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -35,7 +35,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { } @Override - protected void closeConnection(){ + protected void closeConnection() { connector.stopRunning(); } @@ -45,6 +45,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { ; while (running) { try { + syncSwitch.get(canalDestination); logger.info("=============> Start to connect topic: {} <=============", this.topic); connector.connect(); logger.info("=============> Start to subscribe topic: {} <=============", this.topic); @@ -52,7 +53,11 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { logger.info("=============> Subscribe topic: {} succeed <=============", this.topic); while (running) { try { - syncSwitch.get(canalDestination); + Boolean status = syncSwitch.status(canalDestination); + if (status != null && !status) { + connector.disconnect(); + break; + } List messages; if (!flatMessage) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index db80d602..5f5b058c 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -43,6 +43,7 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { ; while (running) { try { + syncSwitch.get(canalDestination); logger.info("=============> Start to connect topic: {} <=============", this.topic); connector.connect(); logger.info("=============> Start to subscribe topic: {}<=============", this.topic); @@ -50,6 +51,12 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { logger.info("=============> Subscribe topic: {} succeed<=============", this.topic); while (running) { try { + Boolean status = syncSwitch.status(canalDestination); + if (status != null && !status) { + connector.disconnect(); + break; + } + List messages; if (!flatMessage) { messages = connector.getListWithoutAck(100L, TimeUnit.MILLISECONDS); diff --git a/client-launcher/src/main/resources/canal-client.yml b/client-launcher/src/main/resources/canal-client.yml deleted file mode 100644 index 211296b5..00000000 --- a/client-launcher/src/main/resources/canal-client.yml +++ /dev/null @@ -1,24 +0,0 @@ -#canalServerHost: 127.0.0.1:11111 -#zookeeperHosts: slave1:2181 -bootstrapServers: slave1:6667,slave2:6667 #or rocketmq nameservers:host1:9876;host2:9876 -flatMessage: false - -#canalInstances: -#- instance: example -# adapterGroups: -# - outAdapters: -# - name: logger -# - name: hbase -# hosts: slave1:2181 -# properties: {znodeParent: "/hbase-unsecure"} - -mqTopics: -- mqMode: rocketmq - topic: example - groups: - - groupId: example - outAdapters: - - name: logger -# - name: hbase -# hosts: slave1:2181 -# properties: {znodeParent: "/hbase-unsecure"} diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java index 6439b1b0..e9282498 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java @@ -87,9 +87,11 @@ public class KafkaCanalConnector implements CanalMQConnector { public void disconnect() { if (kafkaConsumer != null) { kafkaConsumer.close(); + kafkaConsumer = null; } if (kafkaConsumer2 != null) { kafkaConsumer2.close(); + kafkaConsumer2 = null; } connected = false; From ddcf619deb331ab6d413e3c7c65c44977c65cf18 Mon Sep 17 00:00:00 2001 From: agapple Date: Sun, 28 Oct 2018 23:39:11 +0800 Subject: [PATCH 225/360] Update README.md --- client-adapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-adapter/README.md b/client-adapter/README.md index b3d92c77..0ca50b9b 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -41,7 +41,7 @@ canal.conf: flatMessage: true # 扁平message开关, 是否以json字符串形式投递数据, 仅在kafka/rocketMQ模式下有效 canalInstances: # canal实例组, 如果是tcp模式可配置此项 - instance: example # 对应canal destination - adapterGroups: # 对应适配器分组, 分组间的适配器并行运行 + groups: # 对应适配器分组, 分组间的适配器并行运行 - outAdapters: # 适配器列表, 分组内的适配串行运行 - name: logger # 适配器SPI名 - name: hbase From 2e1d0e36c598a3746056627b74327a57a0218ab9 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 29 Oct 2018 01:05:12 +0800 Subject: [PATCH 226/360] =?UTF-8?q?group=20=E7=BA=BF=E7=A8=8B=E6=B1=A0?= =?UTF-8?q?=E5=9C=A8=E7=88=B6=E7=B1=BB=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/adapter/launcher/loader/CanalAdapterWorker.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 826e9ccd..198ebaae 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -36,7 +36,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { List> canalOuterAdapters){ super(canalOuterAdapters); this.canalDestination = canalDestination; - groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); connector = CanalConnectors.newSingleConnector(address, canalDestination, "", ""); } @@ -51,7 +50,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { List> canalOuterAdapters){ super(canalOuterAdapters); this.canalDestination = canalDestination; - groupInnerExecutorService = Executors.newFixedThreadPool(canalOuterAdapters.size()); connector = CanalConnectors.newClusterConnector(zookeeperHosts, canalDestination, "", ""); ((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT); } From b131e8cfa4c212497b075d5175aa3425aa6fd8c3 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 29 Oct 2018 01:40:34 +0800 Subject: [PATCH 227/360] =?UTF-8?q?CanalConnector=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=8E=BB=E6=8E=89stopRunning=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../loader/AbstractCanalAdapterWorker.java | 3 -- .../loader/CanalAdapterKafkaWorker.java | 5 --- .../loader/CanalAdapterRocketMQWorker.java | 7 +--- .../launcher/loader/CanalAdapterWorker.java | 41 ++++++++++++++++--- .../otter/canal/client/CanalConnector.java | 7 ---- .../client/kafka/KafkaCanalConnector.java | 9 ---- .../rocketmq/RocketMQCanalConnector.java | 7 +--- .../kafka/CanalKafkaClientExample.java | 1 - .../rocketmq/CanalRocketMQClientExample.java | 3 +- .../example/AbstractCanalClientTest.java | 1 - 10 files changed, 38 insertions(+), 46 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index 333b0805..a5a7a730 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -134,7 +134,6 @@ public abstract class AbstractCanalAdapterWorker { return; } - closeConnection(); running = false; syncSwitch.release(canalDestination); @@ -156,6 +155,4 @@ public abstract class AbstractCanalAdapterWorker { } } - protected abstract void closeConnection(); - } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 41664c7c..4aa5aee4 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -34,11 +34,6 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { // connector.setSessionTimeout(1L, TimeUnit.MINUTES); } - @Override - protected void closeConnection() { - connector.stopRunning(); - } - @Override protected void process() { while (!running) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index 5f5b058c..69862f88 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -32,11 +32,6 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); } - @Override - protected void closeConnection() { - connector.stopRunning(); - } - @Override protected void process() { while (!running) @@ -88,7 +83,7 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { } catch (WakeupException e) { // No-op. Continue process } - connector.stopRunning(); + connector.disconnect(); logger.info("=============> Disconnect topic: {} <=============", this.topic); } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 198ebaae..ec09da6a 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -2,7 +2,6 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.net.SocketAddress; import java.util.List; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; @@ -10,6 +9,7 @@ import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.impl.ClusterCanalConnector; +import com.alibaba.otter.canal.client.impl.SimpleCanalConnector; import com.alibaba.otter.canal.protocol.Message; /** @@ -54,11 +54,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { ((ClusterCanalConnector) connector).setSoTimeout(SO_TIMEOUT); } - @Override - protected void closeConnection() { - connector.stopRunning(); - } - @Override protected void process() { while (!running) @@ -130,4 +125,38 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { } } } + + @Override + public void stop() { + try { + if (!running) { + return; + } + + if (connector instanceof ClusterCanalConnector) { + ((ClusterCanalConnector) connector).stopRunning(); + } else if (connector instanceof SimpleCanalConnector) { + ((SimpleCanalConnector) connector).stopRunning(); + } + + running = false; + + syncSwitch.release(canalDestination); + + logger.info("destination {} is waiting for adapters' worker thread die!", canalDestination); + if (thread != null) { + try { + thread.join(); + } catch (InterruptedException e) { + // ignore + } + } + groupInnerExecutorService.shutdown(); + logger.info("destination {} adapters worker thread dead!", canalDestination); + canalOuterAdapters.forEach(outerAdapters -> outerAdapters.forEach(OuterAdapter::destroy)); + logger.info("destination {} all adapters destroyed!", canalDestination); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } } diff --git a/client/src/main/java/com/alibaba/otter/canal/client/CanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/CanalConnector.java index 8e52333d..7f3eeb80 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/CanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/CanalConnector.java @@ -149,11 +149,4 @@ public interface CanalConnector { * @throws CanalClientException */ void rollback() throws CanalClientException; - - /** - * 中断的阻塞,用于优雅停止client - * - * @throws CanalClientException - */ - void stopRunning() throws CanalClientException; } diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java index e9282498..bf652ecf 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java @@ -238,15 +238,6 @@ public class KafkaCanalConnector implements CanalMQConnector { } } - public void stopRunning() { - if (running) { - running = false; // 设置为非running状态 - // if (!mutex.state()) { - // mutex.set(true); // 中断阻塞 - // } - } - } - @Override public void subscribe(String filter) throws CanalClientException { throw new CanalClientException("mq not support this method"); diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java index 987406ba..da21ead8 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java @@ -65,6 +65,7 @@ public class RocketMQCanalConnector implements CanalMQConnector { public void disconnect() throws CanalClientException { rocketMQConsumer.shutdown(); + connected = false; } public boolean checkValid() throws CanalClientException { @@ -259,10 +260,4 @@ public class RocketMQCanalConnector implements CanalMQConnector { throw new CanalClientException("mq not support this method"); } - @Override - public void stopRunning() throws CanalClientException { - this.rocketMQConsumer.shutdown(); - connected = false; - } - } diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java index db090c34..25fefe38 100644 --- a/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/kafka/CanalKafkaClientExample.java @@ -88,7 +88,6 @@ public class CanalKafkaClientExample { if (!running) { return; } - connector.stopRunning(); running = false; if (thread != null) { try { diff --git a/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java b/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java index f50dedcf..404fa8a0 100644 --- a/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java +++ b/client/src/test/java/com/alibaba/otter/canal/client/running/rocketmq/CanalRocketMQClientExample.java @@ -87,7 +87,6 @@ public class CanalRocketMQClientExample extends AbstractRocektMQTest { if (!running) { return; } - connector.stopRunning(); running = false; if (thread != null) { try { @@ -134,6 +133,6 @@ public class CanalRocketMQClientExample extends AbstractRocektMQTest { } catch (WakeupException e) { // No-op. Continue process } - connector.stopRunning(); +// connector.stopRunning(); } } diff --git a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java index 9cb5eb00..552f19da 100644 --- a/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java +++ b/example/src/main/java/com/alibaba/otter/canal/example/AbstractCanalClientTest.java @@ -96,7 +96,6 @@ public class AbstractCanalClientTest { if (!running) { return; } - connector.stopRunning(); running = false; if (thread != null) { try { From 56ed158814f07293114b08ce2111bb0bb39d907a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 29 Oct 2018 12:46:20 +0800 Subject: [PATCH 228/360] fixed issue #850 , support alter lock/algorithm --- .../otter/canal/parse/inbound/mysql/tsdb/FastsqlSchemaTest.java | 2 ++ pom.xml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/FastsqlSchemaTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/FastsqlSchemaTest.java index 70eab24c..bf0deafe 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/FastsqlSchemaTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/FastsqlSchemaTest.java @@ -19,6 +19,8 @@ public class FastsqlSchemaTest { public void testSimple() throws FileNotFoundException, IOException { SchemaRepository repository = new SchemaRepository(JdbcConstants.MYSQL); String sql = "create table yushitai_test.card_record ( id bigint auto_increment, name varchar(32) DEFAULT NULL) auto_increment=256 " + + "ALTER TABLE yushitai_test.card_record ADD COLUMN remark2 varchar(255) DEFAULT NULL , ALGORITHM=inplace,LOCK=NONE;" + + "ALTER TABLE yushitai_test.card_record modify COLUMN name varchar(64) DEFAULT NULL , ALGORITHM=copy,LOCK=SHARED; " + "alter table yushitai_test.card_record add index index_name(name) ;" + "alter table yushitai_test.card_record add index index_name(name) ;" + "alter table yushitai_test.card_record add Constraint pk_id PRIMARY KEY (id);" diff --git a/pom.xml b/pom.xml index 27038086..02ff2756 100644 --- a/pom.xml +++ b/pom.xml @@ -246,7 +246,7 @@ com.alibaba.fastsql fastsql - 2.0.0_preview_630 + 2.0.0_preview_644 com.alibaba From d03e7e2b1580232a38c753d931d8f5e54fcf32fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 29 Oct 2018 13:49:24 +0800 Subject: [PATCH 229/360] fixed canal_version release --- client-adapter/common/pom.xml | 2 +- client-adapter/launcher/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml index eb3a21a3..82042053 100644 --- a/client-adapter/common/pom.xml +++ b/client-adapter/common/pom.xml @@ -16,7 +16,7 @@ com.alibaba.otter canal.protocol - ${canal_version} + 1.1.1-SNAPSHOT joda-time diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index 8159bb66..0b7325ad 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -34,7 +34,7 @@ com.alibaba.otter canal.client - ${canal_version} + 1.1.1-SNAPSHOT org.yaml From f28efce11eae59aa3c3a5d5573a1aea770192db9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= Date: Mon, 29 Oct 2018 14:54:17 +0800 Subject: [PATCH 230/360] fixed javadoc for release --- common/pom.xml | 7 ++++++- .../com/alibaba/otter/canal/common/utils/BooleanMutex.java | 2 +- pom.xml | 6 +++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/common/pom.xml b/common/pom.xml index 1f2ece8b..545905c2 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -1,4 +1,5 @@ - + 4.0.0 com.alibaba.otter @@ -83,6 +84,10 @@ org.springframework spring-orm + + com.google.code.findbugs + jsr305 + org.springframework spring-test diff --git a/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java b/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java index d729ad01..660181ce 100644 --- a/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java +++ b/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java @@ -5,7 +5,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** - * 实现一个互斥实现,基于Cocurrent中的{@linkplain AbstractQueuedSynchronizer}实现了自己的sync
      + * 实现一个互斥实现,基于Cocurrent中的AbstractQueuedSynchronizer实现了自己的sync
      * 应用场景:系统初始化/授权控制,没权限时阻塞等待。有权限时所有线程都可以快速通过 * *
      diff --git a/pom.xml b/pom.xml
      index 02ff2756..edda070d 100644
      --- a/pom.xml
      +++ b/pom.xml
      @@ -297,7 +297,11 @@
                       5.1.40
                       
                   
      -
      +            
      +                com.google.code.findbugs
      +                jsr305
      +                3.0.2
      +            
               
           
       
      
      From 774aeeea68a1eae6b9e52d6005e4eb866c891522 Mon Sep 17 00:00:00 2001
      From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= 
      Date: Mon, 29 Oct 2018 14:57:25 +0800
      Subject: [PATCH 231/360] [maven-release-plugin] prepare release canal-1.1.1
      
      ---
       client-adapter/common/pom.xml   |  8 +++-----
       client-adapter/hbase/pom.xml    |  6 ++----
       client-adapter/launcher/pom.xml | 16 +++++++---------
       client-adapter/logger/pom.xml   |  6 ++----
       client-adapter/pom.xml          |  6 ++----
       client/pom.xml                  |  5 ++---
       common/pom.xml                  |  5 ++---
       dbsync/pom.xml                  |  2 +-
       deployer/pom.xml                |  2 +-
       driver/pom.xml                  |  2 +-
       example/pom.xml                 |  2 +-
       filter/pom.xml                  |  2 +-
       instance/core/pom.xml           |  2 +-
       instance/manager/pom.xml        |  2 +-
       instance/pom.xml                |  2 +-
       instance/spring/pom.xml         |  2 +-
       meta/pom.xml                    |  2 +-
       parse/pom.xml                   |  2 +-
       pom.xml                         | 20 +++++++++++++++++++-
       prometheus/pom.xml              |  4 ++--
       protocol/pom.xml                |  2 +-
       server/pom.xml                  |  2 +-
       sink/pom.xml                    |  2 +-
       store/pom.xml                   |  2 +-
       24 files changed, 56 insertions(+), 50 deletions(-)
      
      diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml
      index 82042053..347dae44 100644
      --- a/client-adapter/common/pom.xml
      +++ b/client-adapter/common/pom.xml
      @@ -1,11 +1,9 @@
       
      -
      +
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.1-SNAPSHOT
      +        1.1.1
           
           4.0.0
           com.alibaba.otter
      @@ -16,7 +14,7 @@
               
                   com.alibaba.otter
                   canal.protocol
      -            1.1.1-SNAPSHOT
      +            1.1.1
               
               
                   joda-time
      diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml
      index b3d274e4..94acbbe2 100644
      --- a/client-adapter/hbase/pom.xml
      +++ b/client-adapter/hbase/pom.xml
      @@ -1,11 +1,9 @@
       
      -
      +
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.1-SNAPSHOT
      +        1.1.1
           
           4.0.0
           com.alibaba.otter
      diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml
      index 0b7325ad..8cc80d81 100644
      --- a/client-adapter/launcher/pom.xml
      +++ b/client-adapter/launcher/pom.xml
      @@ -1,11 +1,9 @@
       
      -
      +
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.1-SNAPSHOT
      +        1.1.1
           
           4.0.0
           com.alibaba.otter
      @@ -34,7 +32,7 @@
               
                   com.alibaba.otter
                   canal.client
      -            1.1.1-SNAPSHOT
      +            1.1.1
               
               
                   org.yaml
      @@ -134,14 +132,14 @@
                               
                               
                                   
      -                                
      +                                
                                           
      -                                        
      +                                        
                                           
                                       
      -                                
      +                                
                                           
      -                                        
      +                                        
                                           
                                       
                                   
      diff --git a/client-adapter/logger/pom.xml b/client-adapter/logger/pom.xml
      index af7ec379..bfbe39f8 100644
      --- a/client-adapter/logger/pom.xml
      +++ b/client-adapter/logger/pom.xml
      @@ -1,11 +1,9 @@
       
      -
      +
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.1-SNAPSHOT
      +        1.1.1
           
           4.0.0
           com.alibaba.otter
      diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml
      index aafe6395..e5ad637b 100644
      --- a/client-adapter/pom.xml
      +++ b/client-adapter/pom.xml
      @@ -1,11 +1,9 @@
       
      -
      +
           4.0.0
           com.alibaba.otter
           canal.client-adapter
      -    1.1.1-SNAPSHOT
      +    1.1.1
           pom
           canal client adapter module for otter ${project.version}
       
      diff --git a/client/pom.xml b/client/pom.xml
      index 370d1854..3f7d6ed5 100644
      --- a/client/pom.xml
      +++ b/client/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      @@ -180,8 +180,7 @@
       								
       							
       							
      -								
      +								
       							
       						
       					
      diff --git a/common/pom.xml b/common/pom.xml
      index 545905c2..0b85b882 100644
      --- a/common/pom.xml
      +++ b/common/pom.xml
      @@ -1,10 +1,9 @@
      -
      +
       	4.0.0
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	canal.common
      diff --git a/dbsync/pom.xml b/dbsync/pom.xml
      index ac606af4..959d9594 100644
      --- a/dbsync/pom.xml
      +++ b/dbsync/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/deployer/pom.xml b/deployer/pom.xml
      index 690b8c91..cc4226a4 100644
      --- a/deployer/pom.xml
      +++ b/deployer/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/driver/pom.xml b/driver/pom.xml
      index 692475db..04c9d3e4 100644
      --- a/driver/pom.xml
      +++ b/driver/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/example/pom.xml b/example/pom.xml
      index 679bfb1d..c125ddda 100644
      --- a/example/pom.xml
      +++ b/example/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/filter/pom.xml b/filter/pom.xml
      index 95c77f8f..40a36b51 100644
      --- a/filter/pom.xml
      +++ b/filter/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/core/pom.xml b/instance/core/pom.xml
      index 1a73fc8a..554c9a62 100644
      --- a/instance/core/pom.xml
      +++ b/instance/core/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../../pom.xml
       	
       	canal.instance.core
      diff --git a/instance/manager/pom.xml b/instance/manager/pom.xml
      index ec85fa2c..3dd0a1d7 100644
      --- a/instance/manager/pom.xml
      +++ b/instance/manager/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/pom.xml b/instance/pom.xml
      index a73b43fe..8723337d 100644
      --- a/instance/pom.xml
      +++ b/instance/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/spring/pom.xml b/instance/spring/pom.xml
      index 929fc273..b885754d 100644
      --- a/instance/spring/pom.xml
      +++ b/instance/spring/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../../pom.xml
       	
       	com.alibaba.otter
      diff --git a/meta/pom.xml b/meta/pom.xml
      index 98788116..d27e41d2 100644
      --- a/meta/pom.xml
      +++ b/meta/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/parse/pom.xml b/parse/pom.xml
      index 8e47215b..8e90cb88 100644
      --- a/parse/pom.xml
      +++ b/parse/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.1-SNAPSHOT
      +		1.1.1
       		../pom.xml
       	
       	canal.parse
      diff --git a/pom.xml b/pom.xml
      index edda070d..88a9b62d 100644
      --- a/pom.xml
      +++ b/pom.xml
      @@ -4,7 +4,7 @@
           canal
           pom
           canal module for otter ${project.version}
      -    1.1.1-SNAPSHOT
      +    1.1.1
           https://github.com/alibaba/canal
           
               org.sonatype.oss
      @@ -371,6 +371,24 @@
                       
                   
                   
      +            
      +                org.apache.maven.plugins
      +                maven-javadoc-plugin
      +                2.9.1
      +                
      +                    
      +                            attach-javadocs
      +                            
      +                                jar
      +                            
      +                        
      +                    
      +                
      +                  ${file_encoding}
      +                  ${file_encoding}
      +                  -Xdoclint:none
      +                
      +            
                    index
      +        for (int i = 0; i < 2048; i++) {
      +            String charset = INDEX_TO_CHARSET[i];
      +            if (charset != null && CHARSET_TO_INDEX.get(charset) == null) {
      +                CHARSET_TO_INDEX.put(charset, i);
      +            }
      +        }
      +        CHARSET_TO_INDEX.put("iso-8859-1", 14);
      +        CHARSET_TO_INDEX.put("iso_8859_1", 14);
      +        CHARSET_TO_INDEX.put("utf-8", 33);
      +        CHARSET_TO_INDEX.put("utf8mb4", 45);
      +    }
      +
      +    public static final String getCharset(int index) {
      +        return INDEX_TO_CHARSET[index];
      +    }
      +
      +    public static final int getIndex(String charset) {
      +        if (charset == null || charset.length() == 0) {
      +            return 0;
      +        } else {
      +            Integer i = CHARSET_TO_INDEX.get(charset.toLowerCase());
      +            return (i == null) ? 0 : i.intValue();
      +        }
      +    }
      +
      +    /**
      +     * 'utf8' COLLATE 'utf8_general_ci'
      +     * 
      +     * @param charset
      +     * @return
      +     */
      +    public static final String collateCharset(String charset) {
      +        String[] output = StringUtils.split(charset, "COLLATE");
      +        return output[0].replace('\'', ' ').trim();
      +    }
      +
      +    public static String getJavaCharset(String charset) {
      +        if ("utf8".equals(charset)) {
      +            return charset;
      +        }
      +
      +        if (StringUtils.endsWithIgnoreCase(charset, "utf8mb4")) {
      +            return "utf-8";
      +        }
      +
      +        if (StringUtils.endsWithIgnoreCase(charset, "binary")) {
      +            return "iso_8859_1";
      +        }
      +
      +        return charset;
      +    }
      +}
      diff --git a/driver/src/test/java/com/alibaba/otter/canal/parse/driver/mysql/CharsetUtilTest.java b/driver/src/test/java/com/alibaba/otter/canal/parse/driver/mysql/CharsetUtilTest.java
      new file mode 100644
      index 00000000..09837d09
      --- /dev/null
      +++ b/driver/src/test/java/com/alibaba/otter/canal/parse/driver/mysql/CharsetUtilTest.java
      @@ -0,0 +1,51 @@
      +package com.alibaba.otter.canal.parse.driver.mysql;
      +
      +import org.junit.Assert;
      +import org.junit.Test;
      +
      +import com.alibaba.otter.canal.parse.driver.mysql.utils.CharsetUtil;
      +
      +public class CharsetUtilTest {
      +
      +    @Test
      +    public void testLatin1() {
      +        int charsetIndex = 5;
      +        String charset = "latin1";
      +        Assert.assertTrue(charset.equals(CharsetUtil.getCharset(charsetIndex)));
      +    }
      +
      +    @Test
      +    public void testGbk() {
      +        int charsetIndex = 87;
      +        String charset = "gbk";
      +        Assert.assertTrue(charset.equals(CharsetUtil.getCharset(charsetIndex)));
      +    }
      +
      +    @Test
      +    public void testGb2312() {
      +        int charsetIndex = 24;
      +        String charset = "gb2312";
      +        Assert.assertTrue(charset.equals(CharsetUtil.getCharset(charsetIndex)));
      +    }
      +
      +    @Test
      +    public void testUtf8() {
      +        int charsetIndex = 213;
      +        String charset = "utf8";
      +        Assert.assertTrue(charset.equals(CharsetUtil.getCharset(charsetIndex)));
      +    }
      +
      +    @Test
      +    public void testUtf8mb4() {
      +        int charsetIndex = 235;
      +        String charset = "utf8mb4";
      +        Assert.assertTrue(charset.equals(CharsetUtil.getCharset(charsetIndex)));
      +    }
      +
      +    @Test
      +    public void testBinary() {
      +        int charsetIndex = 63;
      +        String charset = "binary";
      +        Assert.assertTrue(charset.equals(CharsetUtil.getCharset(charsetIndex)));
      +    }
      +}
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java
      index 8b682f5d..703de818 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlMultiStageCoprocessor.java
      @@ -275,6 +275,7 @@ public class MysqlMultiStageCoprocessor extends AbstractCanalLifeCycle implement
                               needDmlParse = true;
                               break;
                           case LogEvent.UPDATE_ROWS_EVENT_V1:
      +                    case LogEvent.PARTIAL_UPDATE_ROWS_EVENT:
                           case LogEvent.UPDATE_ROWS_EVENT:
                               tableMeta = logEventConvert.parseRowsEventForTableMeta((UpdateRowsLogEvent) logEvent);
                               needDmlParse = true;
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      index 0b88af14..5c5432c6 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java
      @@ -120,6 +120,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                   case LogEvent.WRITE_ROWS_EVENT:
                       return parseRowsEvent((WriteRowsLogEvent) logEvent);
                   case LogEvent.UPDATE_ROWS_EVENT_V1:
      +            case LogEvent.PARTIAL_UPDATE_ROWS_EVENT:
                   case LogEvent.UPDATE_ROWS_EVENT:
                       return parseRowsEvent((UpdateRowsLogEvent) logEvent);
                   case LogEvent.DELETE_ROWS_EVENT_V1:
      @@ -261,7 +262,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                   }
       
                   boolean isDml = (type == EventType.INSERT || type == EventType.UPDATE || type == EventType.DELETE);
      -            
      +
                   if (!isSeek && !isDml) {
                       // 使用新的表结构元数据管理方式
                       EntryPosition position = createPosition(event.getHeader());
      @@ -504,7 +505,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                   int type = event.getHeader().getType();
                   if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) {
                       eventType = EventType.INSERT;
      -            } else if (LogEvent.UPDATE_ROWS_EVENT_V1 == type || LogEvent.UPDATE_ROWS_EVENT == type) {
      +            } else if (LogEvent.UPDATE_ROWS_EVENT_V1 == type || LogEvent.UPDATE_ROWS_EVENT == type
      +                       || LogEvent.PARTIAL_UPDATE_ROWS_EVENT == type) {
                       eventType = EventType.UPDATE;
                   } else if (LogEvent.DELETE_ROWS_EVENT_V1 == type || LogEvent.DELETE_ROWS_EVENT == type) {
                       eventType = EventType.DELETE;
      @@ -523,7 +525,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
       
                   boolean tableError = false;
                   int rowsCount = 0;
      -            while (buffer.nextOneRow(columns)) {
      +            while (buffer.nextOneRow(columns, false)) {
                       // 处理row记录
                       RowData.Builder rowDataBuilder = RowData.newBuilder();
                       if (EventType.INSERT == eventType) {
      @@ -535,7 +537,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                       } else {
                           // update需要处理before/after
                           tableError |= parseOneRow(rowDataBuilder, event, buffer, columns, false, tableMeta);
      -                    if (!buffer.nextOneRow(changeColumns)) {
      +                    if (!buffer.nextOneRow(changeColumns, true)) {
                               rowChangeBuider.addRowDatas(rowDataBuilder.build());
                               break;
                           }
      @@ -577,7 +579,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                                       boolean isAfter, TableMeta tableMeta) throws UnsupportedEncodingException {
               int columnCnt = event.getTable().getColumnCnt();
               ColumnInfo[] columnInfo = event.getTable().getColumnInfo();
      -
      +        // mysql8.0针对set @@global.binlog_row_metadata='FULL' 可以记录部分的metadata信息
      +        boolean existOptionalMetaData = event.getTable().isExistOptionalMetaData();
               boolean tableError = false;
               // check table fileds count,只能处理加字段
               boolean existRDSNoPrimaryKey = false;
      @@ -631,22 +634,43 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                       continue;
                   }
       
      -            if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
      -                // 不解析最后一列
      -                buffer.nextValue(info.type, info.meta, false);
      -                continue;
      -            }
      -
      -            Column.Builder columnBuilder = Column.newBuilder();
      -
                   FieldMeta fieldMeta = null;
                   if (tableMeta != null && !tableError) {
                       // 处理file meta
                       fieldMeta = tableMeta.getFields().get(i);
      +            }
      +
      +            if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
      +                // 不解析最后一列
      +                buffer.nextValue(fieldMeta.getColumnName(), i, info.type, info.meta, false);
      +                continue;
      +            }
      +
      +            if (fieldMeta != null && existOptionalMetaData) {
      +                // check column info
      +                boolean check = StringUtils.equalsIgnoreCase(fieldMeta.getColumnName(), info.name);
      +                check &= (fieldMeta.isUnsigned() == info.unsigned);
      +                check &= (fieldMeta.isNullable() == info.nullable);
      +
      +                if (!check) {
      +                    throw new CanalParseException("MySQL8.0 unmatch column metadata & pls submit issue , db : "
      +                                                  + fieldMeta.toString() + " , binlog : " + info.toString()
      +                                                  + " , on : " + event.getHeader().getLogFileName() + ":"
      +                                                  + event.getHeader().getLogPos());
      +                }
      +            }
      +
      +            Column.Builder columnBuilder = Column.newBuilder();
      +            if (fieldMeta != null) {
                       columnBuilder.setName(fieldMeta.getColumnName());
                       columnBuilder.setIsKey(fieldMeta.isKey());
                       // 增加mysql type类型,issue 73
                       columnBuilder.setMysqlType(fieldMeta.getColumnType());
      +            } else if (existOptionalMetaData) {
      +                columnBuilder.setName(info.name);
      +                columnBuilder.setIsKey(info.pk);
      +                // mysql8.0里没有mysql type类型
      +                // columnBuilder.setMysqlType(fieldMeta.getColumnType());
                   }
                   columnBuilder.setIndex(i);
                   columnBuilder.setIsNull(false);
      @@ -664,12 +688,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                           isSingleBit = true;
                       }
                   }
      -            buffer.nextValue(info.type, info.meta, isBinary);
      -            if (existRDSNoPrimaryKey && i == columnCnt - 1 && info.type == LogEvent.MYSQL_TYPE_LONGLONG) {
      -                // 不解析最后一列
      -                continue;
      -            }
       
      +            buffer.nextValue(fieldMeta.getColumnName(), i, info.type, info.meta, isBinary);
                   int javaType = buffer.getJavaType();
                   if (buffer.isNull()) {
                       columnBuilder.setIsNull(true);
      @@ -683,7 +703,8 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                           case Types.BIGINT:
                               // 处理unsigned类型
                               Number number = (Number) value;
      -                        if (fieldMeta != null && fieldMeta.isUnsigned() && number.longValue() < 0) {
      +                        boolean isUnsigned = (fieldMeta != null ? fieldMeta.isUnsigned() : (existOptionalMetaData ? info.unsigned : false));
      +                        if (isUnsigned && number.longValue() < 0) {
                                   switch (buffer.getLength()) {
                                       case 1: /* MYSQL_TYPE_TINY */
                                           columnBuilder.setValue(String.valueOf(Integer.valueOf(TINYINT_MAX_VALUE
      @@ -772,7 +793,6 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar
                           default:
                               columnBuilder.setValue(value.toString());
                       }
      -
                   }
       
                   columnBuilder.setSqlType(javaType);
      diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java
      index 2cb6ed8b..fd01e6a8 100644
      --- a/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java
      +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java
      @@ -1,7 +1,11 @@
       package com.alibaba.otter.canal.parse;
       
       import java.io.IOException;
      +import java.io.Serializable;
      +import java.io.UnsupportedEncodingException;
       import java.net.InetSocketAddress;
      +import java.nio.charset.Charset;
      +import java.util.BitSet;
       import java.util.List;
       
       import org.apache.commons.lang.StringUtils;
      @@ -24,12 +28,25 @@ import com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher;
       import com.taobao.tddl.dbsync.binlog.LogContext;
       import com.taobao.tddl.dbsync.binlog.LogDecoder;
       import com.taobao.tddl.dbsync.binlog.LogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.DeleteRowsLogEvent;
       import com.taobao.tddl.dbsync.binlog.event.FormatDescriptionLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.QueryLogEvent;
       import com.taobao.tddl.dbsync.binlog.event.RotateLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.RowsLogBuffer;
      +import com.taobao.tddl.dbsync.binlog.event.RowsLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.RowsQueryLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.TableMapLogEvent.ColumnInfo;
      +import com.taobao.tddl.dbsync.binlog.event.UpdateRowsLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.WriteRowsLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.XidLogEvent;
      +import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent;
       
       public class DirectLogFetcherTest {
       
           protected final Logger logger = LoggerFactory.getLogger(this.getClass());
      +    protected String       binlogFileName = "mysql-bin.000001";
      +    protected Charset      charset        = Charset.forName("utf-8");
           private boolean        isMariaDB;
           private int            binlogChecksum;
       
      @@ -37,7 +54,7 @@ public class DirectLogFetcherTest {
           public void testSimple() {
               DirectLogFetcher fetcher = new DirectLogFetcher();
               try {
      -            MysqlConnector connector = new MysqlConnector(new InetSocketAddress("127.0.0.1", 3306), "xxxx", "xxxx");
      +            MysqlConnector connector = new MysqlConnector(new InetSocketAddress("127.0.0.1", 3306), "root", "hello");
                   connector.connect();
                   updateSettings(connector);
                   loadBinlogChecksum(connector);
      @@ -67,21 +84,22 @@ public class DirectLogFetcherTest {
                               break;
                           case LogEvent.WRITE_ROWS_EVENT_V1:
                           case LogEvent.WRITE_ROWS_EVENT:
      -                        // parseRowsEvent((WriteRowsLogEvent) event);
      +                        parseRowsEvent((WriteRowsLogEvent) event);
                               break;
                           case LogEvent.UPDATE_ROWS_EVENT_V1:
      +                    case LogEvent.PARTIAL_UPDATE_ROWS_EVENT:
                           case LogEvent.UPDATE_ROWS_EVENT:
      -                        // parseRowsEvent((UpdateRowsLogEvent) event);
      +                        parseRowsEvent((UpdateRowsLogEvent) event);
                               break;
                           case LogEvent.DELETE_ROWS_EVENT_V1:
                           case LogEvent.DELETE_ROWS_EVENT:
      -                        // parseRowsEvent((DeleteRowsLogEvent) event);
      +                        parseRowsEvent((DeleteRowsLogEvent) event);
                               break;
                           case LogEvent.QUERY_EVENT:
      -                        // parseQueryEvent((QueryLogEvent) event);
      +                        parseQueryEvent((QueryLogEvent) event);
                               break;
                           case LogEvent.ROWS_QUERY_LOG_EVENT:
      -                        // parseRowsQueryEvent((RowsQueryLogEvent) event);
      +                        parseRowsQueryEvent((RowsQueryLogEvent) event);
                               break;
                           case LogEvent.ANNOTATE_ROWS_EVENT:
                               break;
      @@ -241,4 +259,98 @@ public class DirectLogFetcherTest {
               exector.update(cmd);
           }
       
      +    protected void parseQueryEvent(QueryLogEvent event) {
      +        System.out.println(String.format("================> binlog[%s:%s] , name[%s]",
      +            binlogFileName,
      +            event.getHeader().getLogPos() - event.getHeader().getEventLen(),
      +            event.getCatalog()));
      +        System.out.println("sql : " + event.getQuery());
      +    }
      +
      +    protected void parseRowsQueryEvent(RowsQueryLogEvent event) throws Exception {
      +        System.out.println(String.format("================> binlog[%s:%s]", binlogFileName, event.getHeader()
      +            .getLogPos() - event.getHeader().getEventLen()));
      +        System.out.println("sql : " + new String(event.getRowsQuery().getBytes("ISO-8859-1"), charset.name()));
      +    }
      +
      +    protected void parseAnnotateRowsEvent(AnnotateRowsEvent event) throws Exception {
      +        System.out.println(String.format("================> binlog[%s:%s]", binlogFileName, event.getHeader()
      +            .getLogPos() - event.getHeader().getEventLen()));
      +        System.out.println("sql : " + new String(event.getRowsQuery().getBytes("ISO-8859-1"), charset.name()));
      +    }
      +
      +    protected void parseXidEvent(XidLogEvent event) throws Exception {
      +        System.out.println(String.format("================> binlog[%s:%s]", binlogFileName, event.getHeader()
      +            .getLogPos() - event.getHeader().getEventLen()));
      +        System.out.println("xid : " + event.getXid());
      +    }
      +
      +    protected void parseRowsEvent(RowsLogEvent event) {
      +        try {
      +            System.out.println(String.format("================> binlog[%s:%s] , name[%s,%s]",
      +                binlogFileName,
      +                event.getHeader().getLogPos() - event.getHeader().getEventLen(),
      +                event.getTable().getDbName(),
      +                event.getTable().getTableName()));
      +            RowsLogBuffer buffer = event.getRowsBuf(charset.name());
      +            BitSet columns = event.getColumns();
      +            BitSet changeColumns = event.getChangeColumns();
      +            while (buffer.nextOneRow(columns)) {
      +                // 处理row记录
      +                int type = event.getHeader().getType();
      +                if (LogEvent.WRITE_ROWS_EVENT_V1 == type || LogEvent.WRITE_ROWS_EVENT == type) {
      +                    // insert的记录放在before字段中
      +                    parseOneRow(event, buffer, columns, true);
      +                } else if (LogEvent.DELETE_ROWS_EVENT_V1 == type || LogEvent.DELETE_ROWS_EVENT == type) {
      +                    // delete的记录放在before字段中
      +                    parseOneRow(event, buffer, columns, false);
      +                } else {
      +                    // update需要处理before/after
      +                    System.out.println("-------> before");
      +                    parseOneRow(event, buffer, columns, false);
      +                    if (!buffer.nextOneRow(changeColumns, true)) {
      +                        break;
      +                    }
      +                    System.out.println("-------> after");
      +                    parseOneRow(event, buffer, changeColumns, true);
      +                }
      +
      +            }
      +        } catch (Exception e) {
      +            throw new RuntimeException("parse row data failed.", e);
      +        }
      +    }
      +
      +    protected void parseOneRow(RowsLogEvent event, RowsLogBuffer buffer, BitSet cols, boolean isAfter)
      +                                                                                                      throws UnsupportedEncodingException {
      +        TableMapLogEvent map = event.getTable();
      +        if (map == null) {
      +            throw new RuntimeException("not found TableMap with tid=" + event.getTableId());
      +        }
      +
      +        final int columnCnt = map.getColumnCnt();
      +        final ColumnInfo[] columnInfo = map.getColumnInfo();
      +
      +        for (int i = 0; i < columnCnt; i++) {
      +            if (!cols.get(i)) {
      +                continue;
      +            }
      +
      +            ColumnInfo info = columnInfo[i];
      +            buffer.nextValue(null, i, info.type, info.meta);
      +
      +            if (buffer.isNull()) {
      +                //
      +            } else {
      +                final Serializable value = buffer.getValue();
      +                if (value instanceof byte[]) {
      +                    System.out.println(new String((byte[]) value));
      +                } else {
      +                    System.out.println(value);
      +                }
      +            }
      +        }
      +
      +    }
      +
       }
      diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java
      index 79b88375..70b2dbd7 100644
      --- a/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java
      +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/MysqlBinlogParsePerformanceTest.java
      @@ -89,6 +89,7 @@ public class MysqlBinlogParsePerformanceTest {
                           parseRowsEvent((WriteRowsLogEvent) event, sum);
                           break;
                       case LogEvent.UPDATE_ROWS_EVENT_V1:
      +                case LogEvent.PARTIAL_UPDATE_ROWS_EVENT:
                       case LogEvent.UPDATE_ROWS_EVENT:
                           parseRowsEvent((UpdateRowsLogEvent) event, sum);
                           break;
      @@ -154,7 +155,7 @@ public class MysqlBinlogParsePerformanceTest {
                           parseOneRow(event, buffer, columns, false);
                       } else {
                           parseOneRow(event, buffer, columns, false);
      -                    if (!buffer.nextOneRow(changeColumns)) {
      +                    if (!buffer.nextOneRow(changeColumns, true)) {
                               break;
                           }
                           parseOneRow(event, buffer, changeColumns, true);
      @@ -182,7 +183,7 @@ public class MysqlBinlogParsePerformanceTest {
                   }
       
                   ColumnInfo info = columnInfo[i];
      -            buffer.nextValue(info.type, info.meta);
      +            buffer.nextValue(null, i, info.type, info.meta);
                   if (buffer.isNull()) {
                   } else {
                       buffer.getValue();
      
      From 80cf533b050b714ea4a96bef0e7453ab59dd99ee Mon Sep 17 00:00:00 2001
      From: mcy 
      Date: Mon, 5 Nov 2018 15:24:32 +0800
      Subject: [PATCH 273/360] =?UTF-8?q?=E6=95=B4=E7=90=86=E6=B3=A8=E9=87=8A?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../canal/client/adapter/es/ESAdapter.java    |  4 -
       .../adapter/es/config/ESSyncConfig.java       |  6 ++
       .../adapter/es/config/ESSyncConfigLoader.java |  6 ++
       .../client/adapter/es/config/SchemaItem.java  | 10 ++-
       .../client/adapter/es/config/SqlParser.java   | 21 +++--
       .../adapter/es/service/ESEtlService.java      |  6 ++
       .../adapter/es/service/ESSyncService.java     | 26 +++---
       .../client/adapter/es/support/ESSyncUtil.java | 24 +++++
       .../client/adapter/es/support/ESTemplate.java | 90 ++++++++++++++-----
       .../client/adapter/hbase/HbaseAdapter.java    | 49 +++++-----
       .../src/main/resources/application.yml        | 38 ++++----
       11 files changed, 189 insertions(+), 91 deletions(-)
      
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java
      index 3f11035a..7d10d702 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java
      @@ -12,8 +12,6 @@ import org.elasticsearch.client.transport.TransportClient;
       import org.elasticsearch.common.settings.Settings;
       import org.elasticsearch.common.transport.TransportAddress;
       import org.elasticsearch.transport.client.PreBuiltTransportClient;
      -import org.slf4j.Logger;
      -import org.slf4j.LoggerFactory;
       
       import com.alibaba.otter.canal.client.adapter.OuterAdapter;
       import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
      @@ -33,8 +31,6 @@ import com.alibaba.otter.canal.client.adapter.support.*;
       @SPI("es")
       public class ESAdapter implements OuterAdapter {
       
      -    private static Logger   logger = LoggerFactory.getLogger(ESAdapter.class);
      -
           private TransportClient transportClient;
       
           private ESSyncService   esSyncService;
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java
      index e327b93f..99d425a6 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java
      @@ -3,6 +3,12 @@ package com.alibaba.otter.canal.client.adapter.es.config;
       import java.util.ArrayList;
       import java.util.List;
       
      +/**
      + * ES 映射配置
      + *
      + * @author rewerma 2018-11-01
      + * @version 1.0.0
      + */
       public class ESSyncConfig {
       
           private String    dataSourceKey; // 数据源key
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java
      index 7fef12c0..cb1cc274 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java
      @@ -17,6 +17,12 @@ import com.alibaba.druid.pool.DruidDataSource;
       import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs;
       import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
       
      +/**
      + * ES 配置装载器
      + *
      + * @author rewerma 2018-11-01
      + * @version 1.0.0
      + */
       public class ESSyncConfigLoader {
       
           private static Logger                                   logger              = LoggerFactory
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SchemaItem.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SchemaItem.java
      index f092bf78..fc6e076c 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SchemaItem.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SchemaItem.java
      @@ -7,10 +7,16 @@ import java.util.Map;
       
       import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
       
      +/**
      + * ES 映射配置视图
      + *
      + * @author rewerma 2018-11-01
      + * @version 1.0.0
      + */
       public class SchemaItem {
       
      -    private Map                aliasTableItems = new LinkedHashMap<>();
      -    private Map                selectFields    = new LinkedHashMap<>();
      +    private Map                aliasTableItems = new LinkedHashMap<>(); // 别名对应表名
      +    private Map                selectFields    = new LinkedHashMap<>(); // 查询字段
           private String                                sql;
       
           private volatile Map> tableItemAliases;
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SqlParser.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SqlParser.java
      index 78fb93ec..0c2d7b2c 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SqlParser.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/SqlParser.java
      @@ -34,8 +34,8 @@ public class SqlParser {
           /**
            * 解析sql
            * 
      -     * @param sql
      -     * @return
      +     * @param sql sql
      +     * @return 视图对象
            */
           public static SchemaItem parse(String sql) {
               try {
      @@ -67,8 +67,8 @@ public class SqlParser {
           /**
            * 归集字段
            * 
      -     * @param sqlSelectQueryBlock
      -     * @return
      +     * @param sqlSelectQueryBlock sqlSelectQueryBlock
      +     * @return 字段属性列表
            */
           private static List collectSelectQueryFields(MySqlSelectQueryBlock sqlSelectQueryBlock) {
               return sqlSelectQueryBlock.getSelectList().stream().map(selectItem -> {
      @@ -82,8 +82,8 @@ public class SqlParser {
           /**
            * 解析字段
            * 
      -     * @param expr
      -     * @param fieldItem
      +     * @param expr sql expr
      +     * @param fieldItem 字段属性
            */
           private static void visitColumn(SQLExpr expr, FieldItem fieldItem) {
               if (expr instanceof SQLIdentifierExpr) {
      @@ -123,6 +123,11 @@ public class SqlParser {
       
           /**
            * 解析表
      +     * 
      +     * @param schemaItem 视图对象
      +     * @param sqlTableSource sqlTableSource
      +     * @param tableItems 表对象列表
      +     * @param tableItemTmp 表对象(临时)
            */
           private static void visitSelectTable(SchemaItem schemaItem, SQLTableSource sqlTableSource,
                                                List tableItems, TableItem tableItemTmp) {
      @@ -174,8 +179,8 @@ public class SqlParser {
           /**
            * 解析on条件
            * 
      -     * @param expr
      -     * @param tableItem
      +     * @param expr sql expr
      +     * @param tableItem 表对象
            */
           private static void visitOnCondition(SQLExpr expr, TableItem tableItem) {
               if (!(expr instanceof SQLBinaryOpExpr)) {
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESEtlService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESEtlService.java
      index d7045e47..51f0a924 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESEtlService.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESEtlService.java
      @@ -31,6 +31,12 @@ import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
       import com.alibaba.otter.canal.client.adapter.support.EtlResult;
       import com.google.common.base.Joiner;
       
      +/**
      + * ES ETL Service
      + *
      + * @author rewerma 2018-11-01
      + * @version 1.0.0
      + */
       public class ESEtlService {
       
           private static Logger   logger = LoggerFactory.getLogger(ESEtlService.class);
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java
      index a77aaeef..3927c6ab 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java
      @@ -6,8 +6,6 @@ import java.util.Map;
       
       import javax.sql.DataSource;
       
      -import org.elasticsearch.index.query.BoolQueryBuilder;
      -import org.elasticsearch.index.query.QueryBuilders;
       import org.slf4j.Logger;
       import org.slf4j.LoggerFactory;
       
      @@ -23,6 +21,12 @@ import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate;
       import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
       import com.alibaba.otter.canal.client.adapter.support.Dml;
       
      +/**
      + * ES 同步 Service
      + *
      + * @author rewerma 2018-11-01
      + * @version 1.0.0
      + */
       public class ESSyncService {
       
           private static Logger logger = LoggerFactory.getLogger(ESSyncService.class);
      @@ -530,7 +534,7 @@ public class ESSyncService {
                                                      TableItem tableItem, Map esFieldData) {
               ESMapping mapping = config.getEsMapping();
       
      -        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
      +        Map paramsTmp = new LinkedHashMap<>();
               for (Map.Entry> entry : tableItem.getRelationTableFields().entrySet()) {
                   for (FieldItem fieldItem : entry.getValue()) {
                       if (fieldItem.getColumnItems().size() == 1) {
      @@ -544,7 +548,7 @@ public class ESSyncService {
                           if (fieldName.equals(mapping.get_id())) {
                               fieldName = "_id";
                           }
      -                    queryBuilder.must(QueryBuilders.termsQuery(fieldName, value));
      +                    paramsTmp.put(fieldName, value);
                       }
                   }
               }
      @@ -555,7 +559,7 @@ public class ESSyncService {
                       dml.getTable(),
                       mapping.get_index());
               }
      -        boolean result = esTemplate.updateByQuery(mapping, queryBuilder, esFieldData);
      +        boolean result = esTemplate.updateByQuery(config, paramsTmp, esFieldData);
               if (!result) {
                   logger.error("Join table update es index by foreign key error, destination:{}, table: {}, index: {}",
                       config.getDestination(),
      @@ -625,7 +629,7 @@ public class ESSyncService {
                               }
                           }
       
      -                    BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
      +                    Map paramsTmp = new LinkedHashMap<>();
                           for (Map.Entry> entry : tableItem.getRelationTableFields().entrySet()) {
                               for (FieldItem fieldItem : entry.getValue()) {
                                   if (fieldItem.getColumnItems().size() == 1) {
      @@ -638,7 +642,7 @@ public class ESSyncService {
                                       if (fieldName.equals(mapping.get_id())) {
                                           fieldName = "_id";
                                       }
      -                                queryBuilder.must(QueryBuilders.termsQuery(fieldName, value));
      +                                paramsTmp.put(fieldName, value);
                                   }
                               }
                           }
      @@ -649,7 +653,7 @@ public class ESSyncService {
                                   dml.getTable(),
                                   mapping.get_index());
                           }
      -                    boolean result = esTemplate.updateByQuery(mapping, queryBuilder, esFieldData);
      +                    boolean result = esTemplate.updateByQuery(config, paramsTmp, esFieldData);
                           if (!result) {
                               logger.error(
                                   "Join table update es index by query sql error, destination:{}, table: {}, index: {}",
      @@ -737,7 +741,7 @@ public class ESSyncService {
                               }
                           }
       
      -                    BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
      +                    Map paramsTmp = new LinkedHashMap<>();
                           for (Map.Entry> entry : tableItem.getRelationTableFields().entrySet()) {
                               for (FieldItem fieldItem : entry.getValue()) {
                                   Object value = esTemplate
      @@ -747,7 +751,7 @@ public class ESSyncService {
                                   if (fieldName.equals(mapping.get_id())) {
                                       fieldName = "_id";
                                   }
      -                            queryBuilder.must(QueryBuilders.termsQuery(fieldName, value));
      +                            paramsTmp.put(fieldName, value);
                               }
                           }
       
      @@ -758,7 +762,7 @@ public class ESSyncService {
                                   dml.getTable(),
                                   mapping.get_index());
                           }
      -                    boolean result = esTemplate.updateByQuery(mapping, queryBuilder, esFieldData);
      +                    boolean result = esTemplate.updateByQuery(config, paramsTmp, esFieldData);
                           if (!result) {
                               logger.error(
                                   "Join table update es index by query whole sql error, destination:{}, table: {}, index: {}",
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java
      index 71331873..af58595b 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java
      @@ -21,6 +21,12 @@ import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
       import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
       import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.TableItem;
       
      +/**
      + * ES 同步工具同类
      + *
      + * @author rewerma 2018-11-01
      + * @version 1.0.0
      + */
       public class ESSyncUtil {
       
           private static Logger logger = LoggerFactory.getLogger(ESSyncUtil.class);
      @@ -169,6 +175,24 @@ public class ESSyncUtil {
                   location.put("lat", Double.valueOf(point[0].trim()));
                   location.put("lon", Double.valueOf(point[1].trim()));
                   return location;
      +        } else if ("array".equals(esType)) {
      +            if ("".equals(val.toString().trim())) {
      +                res = new ArrayList<>();
      +            } else {
      +                String value = val.toString();
      +                String separator = ",";
      +                if (!value.contains(",")) {
      +                    if (value.contains(";")) {
      +                        separator = ";";
      +                    } else if (value.contains("|")) {
      +                        separator = "|";
      +                    } else if (value.contains("-")) {
      +                        separator = "-";
      +                    }
      +                }
      +                String[] values = value.split(separator);
      +                return Arrays.asList(values);
      +            }
               } else if ("object".equals(esType)) {
                   if ("".equals(val.toString().trim())) {
                       res = new HashMap<>();
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
      index 9ec95d54..1d53c873 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
      @@ -9,7 +9,8 @@ import java.util.Map;
       import java.util.concurrent.ConcurrentHashMap;
       import java.util.concurrent.ConcurrentMap;
       import java.util.concurrent.TimeUnit;
      -import java.util.function.Consumer;
      +
      +import javax.sql.DataSource;
       
       import org.elasticsearch.action.bulk.BulkItemResponse;
       import org.elasticsearch.action.bulk.BulkRequestBuilder;
      @@ -18,6 +19,7 @@ import org.elasticsearch.action.search.SearchResponse;
       import org.elasticsearch.client.transport.TransportClient;
       import org.elasticsearch.cluster.metadata.MappingMetaData;
       import org.elasticsearch.common.collect.ImmutableOpenMap;
      +import org.elasticsearch.index.query.BoolQueryBuilder;
       import org.elasticsearch.index.query.QueryBuilder;
       import org.elasticsearch.index.query.QueryBuilders;
       import org.elasticsearch.index.reindex.BulkByScrollResponse;
      @@ -31,16 +33,24 @@ import org.slf4j.Logger;
       import org.slf4j.LoggerFactory;
       import org.springframework.util.CollectionUtils;
       
      +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig;
       import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping;
       import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem;
       import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem;
       import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem;
      +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig;
       
      +/**
      + * ES 操作模板
      + *
      + * @author rewerma 2018-11-01
      + * @version 1.0.0
      + */
       public class ESTemplate {
       
           private static final Logger logger         = LoggerFactory.getLogger(ESTemplate.class);
       
      -    private static final int    MAX_BATCH_SIZE = 10000;
      +    private static final int    MAX_BATCH_SIZE = 1000;
       
           private TransportClient     transportClient;
       
      @@ -48,10 +58,6 @@ public class ESTemplate {
               this.transportClient = transportClient;
           }
       
      -    public void setTransportClient(TransportClient transportClient) {
      -        this.transportClient = transportClient;
      -    }
      -
           /**
            * 插入数据
            * 
      @@ -96,18 +102,6 @@ public class ESTemplate {
               return commitBulkRequest(bulkRequestBuilder);
           }
       
      -    /**
      -     * 结合 addBulkRequest4Update 批量更新
      -     * 
      -     * @param consumer
      -     * @return
      -     */
      -    public boolean updateBatch(Consumer consumer) {
      -        BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
      -        consumer.accept(bulkRequestBuilder);
      -        return commitBulkRequest(bulkRequestBuilder);
      -    }
      -
           public void append4Update(BulkRequestBuilder bulkRequestBuilder, ESMapping mapping, Object pkVal,
                                     Map esFieldData) {
               if (mapping.get_id() != null) {
      @@ -120,7 +114,7 @@ public class ESTemplate {
                       .setQuery(QueryBuilders.termQuery(mapping.getPk(), pkVal))
                       .setSize(MAX_BATCH_SIZE)
                       .get();
      -            for (SearchHit hit : response.getHits()) { // 理论上只有一条
      +            for (SearchHit hit : response.getHits()) {
                       bulkRequestBuilder
                           .add(transportClient.prepareUpdate(mapping.get_index(), mapping.get_type(), hit.getId())
                               .setDoc(esFieldData));
      @@ -131,13 +125,63 @@ public class ESTemplate {
           /**
            * update by query
            *
      -     * @param mapping
      -     * @param queryBuilder
      +     * @param config
      +     * @param paramsTmp
            * @param esFieldData
            * @return
            */
      -    public boolean updateByQuery(ESMapping mapping, QueryBuilder queryBuilder, Map esFieldData) {
      -        return updateByQuery(mapping, queryBuilder, esFieldData, 1);
      +    public boolean updateByQuery(ESSyncConfig config, Map paramsTmp, Map esFieldData) {
      +        if (paramsTmp.isEmpty()) {
      +            return false;
      +        }
      +        ESMapping mapping = config.getEsMapping();
      +        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
      +        paramsTmp.forEach((fieldName, value) -> queryBuilder.must(QueryBuilders.termsQuery(fieldName, value)));
      +
      +        SearchResponse response = transportClient.prepareSearch(mapping.get_index())
      +            .setTypes(mapping.get_type())
      +            .setSize(0)
      +            .setQuery(queryBuilder)
      +            .get();
      +        long count = response.getHits().getTotalHits();
      +        // 如果更新量大于Max, 查询sql批量更新
      +        if (count > MAX_BATCH_SIZE) {
      +            BulkRequestBuilder bulkRequestBuilder = transportClient.prepareBulk();
      +
      +            DataSource ds = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey());
      +            // 查询sql更新
      +            StringBuilder sql = new StringBuilder("SELECT * FROM (" + mapping.getSql() + ") _v WHERE ");
      +            paramsTmp.forEach(
      +                (fieldName, value) -> sql.append("_v.").append(fieldName).append("=").append(value).append(" AND "));
      +            int len = sql.length();
      +            sql.delete(len - 4, len);
      +            ESSyncUtil.sqlRS(ds, sql.toString(), rs -> {
      +                int exeCount = 1;
      +                try {
      +                    BulkRequestBuilder bulkRequestBuilderTmp = bulkRequestBuilder;
      +                    while (rs.next()) {
      +                        Object idVal = getIdValFromRS(mapping, rs);
      +                        append4Update(bulkRequestBuilderTmp, mapping, idVal, esFieldData);
      +
      +                        if (exeCount % mapping.getCommitBatch() == 0 && bulkRequestBuilderTmp.numberOfActions() > 0) {
      +                            commitBulkRequest(bulkRequestBuilderTmp);
      +                            bulkRequestBuilderTmp = transportClient.prepareBulk();
      +                        }
      +                        exeCount++;
      +                    }
      +
      +                    if (bulkRequestBuilder.numberOfActions() > 0) {
      +                        commitBulkRequest(bulkRequestBuilderTmp);
      +                    }
      +                } catch (Exception e) {
      +                    throw new RuntimeException(e);
      +                }
      +                return 0;
      +            });
      +            return true;
      +        } else {
      +            return updateByQuery(mapping, queryBuilder, esFieldData, 1);
      +        }
           }
       
           private boolean updateByQuery(ESMapping mapping, QueryBuilder queryBuilder, Map esFieldData,
      diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java
      index 2b2d59ff..8e4c6d36 100644
      --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java
      +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java
      @@ -53,8 +53,8 @@ public class HbaseAdapter implements OuterAdapter {
                               hbaseMapping = MappingConfigLoader.load();
                               mappingConfigCache = new HashMap<>();
                               for (MappingConfig mappingConfig : hbaseMapping.values()) {
      -                            mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination())
      -                                                   + "." + mappingConfig.getHbaseMapping().getDatabase() + "."
      +                            mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "."
      +                                                   + mappingConfig.getHbaseMapping().getDatabase() + "."
                                                          + mappingConfig.getHbaseMapping().getTable(),
                                       mappingConfig);
                               }
      @@ -100,33 +100,34 @@ public class HbaseAdapter implements OuterAdapter {
                       return etlResult;
                   }
               } else {
      -            DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(task);
      -            if (dataSource != null) {
      -                StringBuilder resultMsg = new StringBuilder();
      -                boolean resSucc = true;
      -                // ds不为空说明传入的是datasourceKey
      -                for (MappingConfig configTmp : hbaseMapping.values()) {
      -                    // 取所有的datasourceKey为task的配置
      -                    if (configTmp.getDataSourceKey().equals(task)) {
      -                        EtlResult etlRes = HbaseEtlService.importData(dataSource, hbaseTemplate, configTmp, params);
      -                        if (!etlRes.getSucceeded()) {
      -                            resSucc = false;
      -                            resultMsg.append(etlRes.getErrorMessage()).append("\n");
      -                        } else {
      -                            resultMsg.append(etlRes.getResultMessage()).append("\n");
      -                        }
      +            StringBuilder resultMsg = new StringBuilder();
      +            boolean resSucc = true;
      +            // ds不为空说明传入的是datasourceKey
      +            for (MappingConfig configTmp : hbaseMapping.values()) {
      +                // 取所有的destination为task的配置
      +                if (configTmp.getDestination().equals(task)) {
      +                    DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(configTmp.getDataSourceKey());
      +                    if (dataSource == null) {
      +                        continue;
                           }
      -                }
      -                if (resultMsg.length() > 0) {
      -                    etlResult.setSucceeded(resSucc);
      -                    if (resSucc) {
      -                        etlResult.setResultMessage(resultMsg.toString());
      +                    EtlResult etlRes = HbaseEtlService.importData(dataSource, hbaseTemplate, configTmp, params);
      +                    if (!etlRes.getSucceeded()) {
      +                        resSucc = false;
      +                        resultMsg.append(etlRes.getErrorMessage()).append("\n");
                           } else {
      -                        etlResult.setErrorMessage(resultMsg.toString());
      +                        resultMsg.append(etlRes.getResultMessage()).append("\n");
                           }
      -                    return etlResult;
                       }
                   }
      +            if (resultMsg.length() > 0) {
      +                etlResult.setSucceeded(resSucc);
      +                if (resSucc) {
      +                    etlResult.setResultMessage(resultMsg.toString());
      +                } else {
      +                    etlResult.setErrorMessage(resultMsg.toString());
      +                }
      +                return etlResult;
      +            }
               }
               etlResult.setSucceeded(false);
               etlResult.setErrorMessage("Task not found");
      diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml
      index bd70c5e0..f0b4c6a2 100644
      --- a/client-adapter/launcher/src/main/resources/application.yml
      +++ b/client-adapter/launcher/src/main/resources/application.yml
      @@ -10,25 +10,25 @@ spring:
           time-zone: GMT+8
           default-property-inclusion: non_null
       
      -canal.conf:
      -  canalServerHost: 127.0.0.1:11111
      +#canal.conf:
      +#  canalServerHost: 127.0.0.1:11111
       #  zookeeperHosts: slave1:2181
       #  bootstrapServers: slave1:6667 #or rocketmq
       #  flatMessage: true
      -  canalInstances:
      -  - instance: example
      -    groups:
      -    - outAdapters:
      +#  canalInstances:
      +#  - instance: example
      +#    groups:
      +#    - outAdapters:
       #      - name: logger
       #      - name: hbase
       #        properties:
       #          hbase.zookeeper.quorum: 127.0.0.1
       #          hbase.zookeeper.property.clientPort: 2181
       #          zookeeper.znode.parent: /hbase
      -      - name: es
      -        hosts: 127.0.0.1:9300
      -        properties:
      -          cluster.name: elasticsearch
      +#      - name: es
      +#        hosts: 127.0.0.1:9300
      +#        properties:
      +#          cluster.name: elasticsearch
       #  mqTopics:
       #  - mqMode: kafka
       #    topic: example
      @@ -44,12 +44,12 @@ canal.conf:
       #      outAdapters:
       #      - name: logger
       
      -adapter.conf:
      -  datasourceConfigs:
      -    defaultDS:
      -      url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
      -      username: root
      -      password: 121212
      -  adapterConfigs:
      -  - hbase/mytest_person2.yml
      -  - es/mytest_user.yml
      +#adapter.conf:
      +#  datasourceConfigs:
      +#    defaultDS:
      +#      url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true
      +#      username: root
      +#      password: 121212
      +#  adapterConfigs:
      +#  - hbase/mytest_person2.yml
      +#  - es/mytest_user.yml
      
      From beed10901d210d5fc6ad45cebeca2c0df3f27285 Mon Sep 17 00:00:00 2001
      From: wuwo 
      Date: Mon, 5 Nov 2018 16:22:15 +0800
      Subject: [PATCH 274/360] destory TableMetaTSDB when stop EventParser
      
      ---
       .../main/resources/spring/tsdb/h2-tsdb.xml    |  2 +-
       .../main/resources/spring/tsdb/mysql-tsdb.xml |  2 +-
       .../inbound/mysql/tsdb/DatabaseTableMeta.java | 22 ++++++++++++++++++-
       .../inbound/mysql/tsdb/MemoryTableMeta.java   |  5 +++++
       .../inbound/mysql/tsdb/TableMetaTSDB.java     |  5 +++++
       parse/src/test/resources/tsdb/h2-tsdb.xml     |  2 +-
       parse/src/test/resources/tsdb/mysql-tsdb.xml  |  2 +-
       7 files changed, 35 insertions(+), 5 deletions(-)
      
      diff --git a/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml b/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
      index beb61236..c05b5200 100644
      --- a/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
      +++ b/deployer/src/main/resources/spring/tsdb/h2-tsdb.xml
      @@ -23,7 +23,7 @@
       	
       	
       	
      -	
      +	
       		
       		
       	
      diff --git a/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml b/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
      index 58cab638..87f407b3 100644
      --- a/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
      +++ b/deployer/src/main/resources/spring/tsdb/mysql-tsdb.xml
      @@ -23,7 +23,7 @@
       	
       	
       	
      -	
      +	
       		
       		
       	
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
      index b0dd89ce..73a5ead8 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
      @@ -72,7 +72,7 @@ public class DatabaseTableMeta implements TableMetaTSDB {
       
                   @Override
                   public Thread newThread(Runnable r) {
      -                Thread thread = new Thread(r, "[scheduler-table-meta-snapshot]");
      +                Thread thread = new Thread(r, String.format("[scheduler-table-meta-snapshot-%s]", destination));
                       thread.setDaemon(true);
                       return thread;
                   }
      @@ -105,6 +105,26 @@ public class DatabaseTableMeta implements TableMetaTSDB {
               }
               return true;
           }
      +    
      +    @Override
      +    public void destory() {
      +        if (memoryTableMeta != null) {
      +            memoryTableMeta.destory();
      +        }
      +        
      +        if (connection != null) {
      +            try {
      +                connection.disconnect();
      +            } catch (IOException e) {
      +                logger.error("ERROR # disconnect meta connection for address:{}", connection.getConnector()
      +                    .getAddress(), e);
      +            }
      +        }
      +        
      +        if (scheduler != null) {
      +            scheduler.shutdown();
      +        }
      +    }
       
           @Override
           public TableMeta find(String schema, String table) {
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java
      index 904b55a9..c53e1a08 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java
      @@ -58,6 +58,11 @@ public class MemoryTableMeta implements TableMetaTSDB {
           public boolean init(String destination) {
               return true;
           }
      +    
      +    @Override
      +    public void destory() {
      +        tableMetas.clear();
      +    }
       
           public boolean apply(EntryPosition position, String schema, String ddl, String extra) {
               tableMetas.clear();
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDB.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDB.java
      index d712adbe..f968ae12 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDB.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDB.java
      @@ -18,6 +18,11 @@ public interface TableMetaTSDB {
            */
           public boolean init(String destination);
       
      +    /**
      +     * 销毁资源
      +     */
      +    public void destory();
      +
           /**
            * 获取当前的表结构
            */
      diff --git a/parse/src/test/resources/tsdb/h2-tsdb.xml b/parse/src/test/resources/tsdb/h2-tsdb.xml
      index c0a3d385..dcb6bc47 100644
      --- a/parse/src/test/resources/tsdb/h2-tsdb.xml
      +++ b/parse/src/test/resources/tsdb/h2-tsdb.xml
      @@ -7,7 +7,7 @@
              default-autowire="byName">
       	
       	
      -	
      +	
       		
       		
       	
      diff --git a/parse/src/test/resources/tsdb/mysql-tsdb.xml b/parse/src/test/resources/tsdb/mysql-tsdb.xml
      index 099c034d..44f97203 100644
      --- a/parse/src/test/resources/tsdb/mysql-tsdb.xml
      +++ b/parse/src/test/resources/tsdb/mysql-tsdb.xml
      @@ -7,7 +7,7 @@
              default-autowire="byName">
       	
       	
      -	
      +	
       		
       		
       	
      
      From e608f09b53e1d0d0964fda73693320f3f66148dc Mon Sep 17 00:00:00 2001
      From: wuwo 
      Date: Mon, 5 Nov 2018 16:37:42 +0800
      Subject: [PATCH 275/360] improve DatabaseTableMeta
      
      ---
       .../inbound/mysql/tsdb/DatabaseTableMeta.java | 28 +++++++++----------
       1 file changed, 14 insertions(+), 14 deletions(-)
      
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
      index 73a5ead8..8896e2f5 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java
      @@ -7,6 +7,7 @@ import java.util.List;
       import java.util.Map;
       import java.util.concurrent.Executors;
       import java.util.concurrent.ScheduledExecutorService;
      +import java.util.concurrent.ScheduledFuture;
       import java.util.concurrent.ThreadFactory;
       import java.util.concurrent.TimeUnit;
       import java.util.regex.Pattern;
      @@ -48,18 +49,26 @@ public class DatabaseTableMeta implements TableMetaTSDB {
           private static Logger             logger           = LoggerFactory.getLogger(DatabaseTableMeta.class);
           private static Pattern            pattern          = Pattern.compile("Duplicate entry '.*' for key '*'");
           private static Pattern            h2Pattern        = Pattern.compile("Unique index or primary key violation");
      +    private static ScheduledExecutorService  scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
      +        @Override
      +        public Thread newThread(Runnable r) {
      +            Thread thread = new Thread(r, "[scheduler-table-meta-snapshot]");
      +            thread.setDaemon(true);
      +            return thread;
      +        }
      +    });
           private String                    destination;
           private MemoryTableMeta           memoryTableMeta;
           private MysqlConnection           connection;                                                                 // 查询meta信息的链接
           private CanalEventFilter          filter;
           private CanalEventFilter          blackFilter;
           private EntryPosition             lastPosition;
      -    private ScheduledExecutorService  scheduler;
           private MetaHistoryDAO            metaHistoryDAO;
           private MetaSnapshotDAO           metaSnapshotDAO;
           private int                       snapshotInterval = 24;
           private int                       snapshotExpire   = 360;
      -
      +    private ScheduledFuture        scheduleSnapshotFuture;
      +    
           public DatabaseTableMeta(){
       
           }
      @@ -68,19 +77,10 @@ public class DatabaseTableMeta implements TableMetaTSDB {
           public boolean init(final String destination) {
               this.destination = destination;
               this.memoryTableMeta = new MemoryTableMeta();
      -        this.scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
      -
      -            @Override
      -            public Thread newThread(Runnable r) {
      -                Thread thread = new Thread(r, String.format("[scheduler-table-meta-snapshot-%s]", destination));
      -                thread.setDaemon(true);
      -                return thread;
      -            }
      -        });
       
               // 24小时生成一份snapshot
               if (snapshotInterval > 0) {
      -            scheduler.scheduleWithFixedDelay(new Runnable() {
      +            scheduleSnapshotFuture = scheduler.scheduleWithFixedDelay(new Runnable() {
       
                       @Override
                       public void run() {
      @@ -121,8 +121,8 @@ public class DatabaseTableMeta implements TableMetaTSDB {
                   }
               }
               
      -        if (scheduler != null) {
      -            scheduler.shutdown();
      +        if (scheduleSnapshotFuture != null) {
      +            scheduleSnapshotFuture.cancel(false);
               }
           }
       
      
      From 359aa1a0740d2b47d6ebdc70377b9fbdfa42c5df Mon Sep 17 00:00:00 2001
      From: =?UTF-8?q?=E4=B8=83=E9=94=8B?= 
      Date: Mon, 5 Nov 2018 16:53:15 +0800
      Subject: [PATCH 276/360] fixed compiler failed
      
      ---
       .../dbsync/binlog/BaseLogFetcherTest.java     |  4 +-
       pom.xml                                       | 57 +------------------
       2 files changed, 4 insertions(+), 57 deletions(-)
      
      diff --git a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java
      index d0c6746d..27ecf3db 100644
      --- a/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java
      +++ b/dbsync/src/test/java/com/taobao/tddl/dbsync/binlog/BaseLogFetcherTest.java
      @@ -68,7 +68,7 @@ public class BaseLogFetcherTest {
                           // update需要处理before/after
                           System.out.println("-------> before");
                           parseOneRow(event, buffer, columns, false);
      -                    if (!buffer.nextOneRow(changeColumns)) {
      +                    if (!buffer.nextOneRow(changeColumns, true)) {
                               break;
                           }
                           System.out.println("-------> after");
      @@ -97,7 +97,7 @@ public class BaseLogFetcherTest {
                   }
       
                   ColumnInfo info = columnInfo[i];
      -            buffer.nextValue(info.type, info.meta);
      +            buffer.nextValue(null , i ,info.type, info.meta);
       
                   if (buffer.isNull()) {
                       //
      diff --git a/pom.xml b/pom.xml
      index 5cb51eac..0bb4bc11 100644
      --- a/pom.xml
      +++ b/pom.xml
      @@ -371,6 +371,7 @@
                       
                   
                   
      +            
      +            -->
               
               src/main/java
               src/test/java
      
      From 0d9695c3a54647a593c4b5f9476e974d36675b33 Mon Sep 17 00:00:00 2001
      From: mcy 
      Date: Mon, 5 Nov 2018 17:29:11 +0800
      Subject: [PATCH 277/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AF=B9array?=
       =?UTF-8?q?=E5=92=8Cobject=E7=B1=BB=E5=9E=8B=E7=9A=84=E6=94=AF=E6=8C=81?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../adapter/es/config/ESSyncConfig.java       | 37 +++++++++++++------
       .../client/adapter/es/support/ESSyncUtil.java | 14 +++++++
       .../client/adapter/es/support/ESTemplate.java | 30 +++++++++++----
       .../src/main/resources/es/mytest_user.yml     |  2 +
       4 files changed, 64 insertions(+), 19 deletions(-)
      
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java
      index 99d425a6..9d4a9da2 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java
      @@ -1,7 +1,9 @@
       package com.alibaba.otter.canal.client.adapter.es.config;
       
       import java.util.ArrayList;
      +import java.util.LinkedHashMap;
       import java.util.List;
      +import java.util.Map;
       
       /**
        * ES 映射配置
      @@ -58,19 +60,22 @@ public class ESSyncConfig {
       
           public static class ESMapping {
       
      -        private String       _index;
      -        private String       _type;
      -        private String       _id;
      -        private String       pk;
      -        private String       parent;
      -        private String       sql;
      -        private List skips           = new ArrayList<>();
      -        private int          commitBatch     = 1000;
      -        private String       etlCondition;
      -        private boolean      syncByTimestamp = false;            // 是否按时间戳定时同步
      -        private Long         syncInterval;                       // 同步时间间隔
      +        private String              _index;
      +        private String              _type;
      +        private String              _id;
      +        private String              pk;
      +        private String              parent;
      +        private String              sql;
      +        // 对象字段, 例: objFields:
      +        //              - _labels: array:;
      +        private Map objFields     = new LinkedHashMap<>();
      +        private List        skips           = new ArrayList<>();
      +        private int                 commitBatch     = 1000;
      +        private String              etlCondition;
      +        private boolean             syncByTimestamp = false;                 // 是否按时间戳定时同步
      +        private Long                syncInterval;                            // 同步时间间隔
       
      -        private SchemaItem   schemaItem;                         // sql解析结果模型
      +        private SchemaItem          schemaItem;                              // sql解析结果模型
       
               public String get_index() {
                   return _index;
      @@ -112,6 +117,14 @@ public class ESSyncConfig {
                   this.parent = parent;
               }
       
      +        public Map getObjFields() {
      +            return objFields;
      +        }
      +
      +        public void setObjFields(Map objFields) {
      +            this.objFields = objFields;
      +        }
      +
               public List getSkips() {
                   return skips;
               }
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java
      index af58595b..cf8f8fd4 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESSyncUtil.java
      @@ -31,6 +31,20 @@ public class ESSyncUtil {
       
           private static Logger logger = LoggerFactory.getLogger(ESSyncUtil.class);
       
      +    public static Object convertToEsObj(Object val, String fieldInfo) {
      +        if (val == null) {
      +            return null;
      +        }
      +        if (fieldInfo.startsWith("array:")) {
      +            String separator = fieldInfo.substring("array:".length()).trim();
      +            String[] values = val.toString().split(separator);
      +            return Arrays.asList(values);
      +        } else if (fieldInfo.startsWith("object")) {
      +            return JSON.parse(val.toString());
      +        }
      +        return null;
      +    }
      +
           /**
            * 类型转换为Mapping中对应的类型
            */
      diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
      index 1d53c873..e57b89b7 100644
      --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
      +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/support/ESTemplate.java
      @@ -2,16 +2,14 @@ package com.alibaba.otter.canal.client.adapter.es.support;
       
       import java.sql.ResultSet;
       import java.sql.SQLException;
      -import java.util.Collections;
      -import java.util.HashMap;
      -import java.util.LinkedHashMap;
      -import java.util.Map;
      +import java.util.*;
       import java.util.concurrent.ConcurrentHashMap;
       import java.util.concurrent.ConcurrentMap;
       import java.util.concurrent.TimeUnit;
       
       import javax.sql.DataSource;
       
      +import com.alibaba.fastjson.JSON;
       import org.elasticsearch.action.bulk.BulkItemResponse;
       import org.elasticsearch.action.bulk.BulkRequestBuilder;
       import org.elasticsearch.action.bulk.BulkResponse;
      @@ -205,8 +203,14 @@ public class ESTemplate {
                               .append(mapValue.get("lat"))
                               .append("];");
                       } else {
      -                    logger.warn("Unsupported object type for script_update");
      +                    sb.append("ctx._source").append("[\"").append(key).append("\"]").append(" = ");
      +                    sb.append(JSON.toJSONString(value));
      +                    sb.append(";");
                       }
      +            } else if (value instanceof List) {
      +                sb.append("ctx._source").append("[\"").append(key).append("\"]").append(" = ");
      +                sb.append(JSON.toJSONString(value));
      +                sb.append(";");
                   } else if (value instanceof String) {
                       sb.append("ctx._source")
                           .append("['")
      @@ -325,7 +329,13 @@ public class ESTemplate {
                       value = resultSet.getByte(columnName);
                   }
               }
      -        return ESSyncUtil.typeConvert(value, esType);
      +
      +        // 如果是对象类型
      +        if (mapping.getObjFields().containsKey(fieldName)) {
      +            return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
      +        } else {
      +            return ESSyncUtil.typeConvert(value, esType);
      +        }
           }
       
           public Object getESDataFromRS(ESMapping mapping, ResultSet resultSet,
      @@ -393,7 +403,13 @@ public class ESTemplate {
                       value = ((Byte) value).intValue() != 0;
                   }
               }
      -        return ESSyncUtil.typeConvert(value, esType);
      +
      +        // 如果是对象类型
      +        if (mapping.getObjFields().containsKey(fieldName)) {
      +            return ESSyncUtil.convertToEsObj(value, mapping.getObjFields().get(fieldName));
      +        } else {
      +            return ESSyncUtil.typeConvert(value, esType);
      +        }
           }
       
           /**
      diff --git a/client-adapter/elasticsearch/src/main/resources/es/mytest_user.yml b/client-adapter/elasticsearch/src/main/resources/es/mytest_user.yml
      index 7b9effde..849420a3 100644
      --- a/client-adapter/elasticsearch/src/main/resources/es/mytest_user.yml
      +++ b/client-adapter/elasticsearch/src/main/resources/es/mytest_user.yml
      @@ -10,5 +10,7 @@ esMapping:
               left join role b on b.id=a.role_id
               left join (select user_id, group_concat(label order by id desc separator ';') as labels from label
               group by user_id) c on c.user_id=a.id"
      +#  objFields:
      +#    _labels: array:;
         etlCondition: "where a.c_time>='{0}'"
         commitBatch: 3000
      
      From d8e411462ec26ee8644445280fd88734b53a09d0 Mon Sep 17 00:00:00 2001
      From: winger 
      Date: Mon, 5 Nov 2018 18:38:44 +0800
      Subject: [PATCH 278/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0alter=20rename=20?=
       =?UTF-8?q?=E7=9A=84=E7=B1=BB=E5=9E=8B=E4=B8=BArename?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java      | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java
      index d82e5eff..68f1ff5a 100644
      --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java
      +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/ddl/DruidDdlParser.java
      @@ -71,6 +71,7 @@ public class DruidDdlParser {
                               DdlResult ddlResult = new DdlResult();
                               processName(ddlResult, schmeaName, alterTable.getName(), true);
                               processName(ddlResult, schmeaName, ((SQLAlterTableRename) item).getToName(), false);
      +                        ddlResult.setType(EventType.RENAME);
                               ddlResults.add(ddlResult);
                           } else if (item instanceof SQLAlterTableAddIndex) {
                               DdlResult ddlResult = new DdlResult();
      
      From edb5f294ef45174fbd9b7392e16ca17c9a15ebef Mon Sep 17 00:00:00 2001
      From: jiacheo 
      Date: Mon, 5 Nov 2018 22:00:56 +0800
      Subject: [PATCH 279/360] =?UTF-8?q?fix=EF=BC=9Arollback=E4=B9=8B=E5=90=8E?=
       =?UTF-8?q?=E7=BB=A7=E7=BB=ADcommit=EF=BC=8C=E4=BC=9A=E4=BA=A7=E7=94=9F=20?=
       =?UTF-8?q?CanalServerException:=20ack=20error=20,=20clientId:1001=20batch?=
       =?UTF-8?q?Id:12=20is=20not=20exist=20,=20please=20check=20=E7=9A=84?=
       =?UTF-8?q?=E9=94=99=E8=AF=AF=E3=80=82?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../otter/canal/kafka/CanalKafkaProducer.java | 23 +++++++++++--------
       .../canal/rocketmq/CanalRocketMQProducer.java | 18 ++++++++-------
       2 files changed, 23 insertions(+), 18 deletions(-)
      
      diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      index 654a4b6b..8576835f 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java
      @@ -1,21 +1,20 @@
       package com.alibaba.otter.canal.kafka;
       
      -import java.util.List;
      -import java.util.Properties;
      -
      -import org.apache.kafka.clients.producer.KafkaProducer;
      -import org.apache.kafka.clients.producer.Producer;
      -import org.apache.kafka.clients.producer.ProducerRecord;
      -import org.apache.kafka.common.serialization.StringSerializer;
      -import org.slf4j.Logger;
      -import org.slf4j.LoggerFactory;
      -
       import com.alibaba.fastjson.JSON;
       import com.alibaba.fastjson.serializer.SerializerFeature;
       import com.alibaba.otter.canal.common.MQProperties;
       import com.alibaba.otter.canal.protocol.FlatMessage;
       import com.alibaba.otter.canal.protocol.Message;
       import com.alibaba.otter.canal.spi.CanalMQProducer;
      +import org.apache.kafka.clients.producer.KafkaProducer;
      +import org.apache.kafka.clients.producer.Producer;
      +import org.apache.kafka.clients.producer.ProducerRecord;
      +import org.apache.kafka.common.serialization.StringSerializer;
      +import org.slf4j.Logger;
      +import org.slf4j.LoggerFactory;
      +
      +import java.util.List;
      +import java.util.Properties;
       
       /**
        * kafka producer 主操作类
      @@ -93,6 +92,7 @@ public class CanalKafkaProducer implements CanalMQProducer {
                       logger.error(e.getMessage(), e);
                       // producer.abortTransaction();
                       callback.rollback();
      +                return;
                   }
               } else {
                   // 发送扁平数据json
      @@ -110,6 +110,7 @@ public class CanalKafkaProducer implements CanalMQProducer {
                                   logger.error(e.getMessage(), e);
                                   // producer.abortTransaction();
                                   callback.rollback();
      +                            return;
                               }
                           } else {
                               if (canalDestination.getPartitionHash() != null
      @@ -131,6 +132,7 @@ public class CanalKafkaProducer implements CanalMQProducer {
                                               logger.error(e.getMessage(), e);
                                               // producer.abortTransaction();
                                               callback.rollback();
      +                                        return;
                                           }
                                       }
                                   }
      @@ -145,6 +147,7 @@ public class CanalKafkaProducer implements CanalMQProducer {
                                       logger.error(e.getMessage(), e);
                                       // producer.abortTransaction();
                                       callback.rollback();
      +                                return;
                                   }
                               }
                           }
      diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java
      index 6173cebd..d8bd45e4 100644
      --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java
      +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java
      @@ -1,7 +1,11 @@
       package com.alibaba.otter.canal.rocketmq;
       
      -import java.util.List;
      -
      +import com.alibaba.fastjson.JSON;
      +import com.alibaba.otter.canal.common.CanalMessageSerializer;
      +import com.alibaba.otter.canal.common.MQProperties;
      +import com.alibaba.otter.canal.protocol.FlatMessage;
      +import com.alibaba.otter.canal.server.exception.CanalServerException;
      +import com.alibaba.otter.canal.spi.CanalMQProducer;
       import org.apache.rocketmq.client.exception.MQBrokerException;
       import org.apache.rocketmq.client.exception.MQClientException;
       import org.apache.rocketmq.client.producer.DefaultMQProducer;
      @@ -12,12 +16,7 @@ import org.apache.rocketmq.remoting.exception.RemotingException;
       import org.slf4j.Logger;
       import org.slf4j.LoggerFactory;
       
      -import com.alibaba.fastjson.JSON;
      -import com.alibaba.otter.canal.common.CanalMessageSerializer;
      -import com.alibaba.otter.canal.common.MQProperties;
      -import com.alibaba.otter.canal.protocol.FlatMessage;
      -import com.alibaba.otter.canal.server.exception.CanalServerException;
      -import com.alibaba.otter.canal.spi.CanalMQProducer;
      +import java.util.List;
       
       public class CanalRocketMQProducer implements CanalMQProducer {
       
      @@ -67,6 +66,7 @@ public class CanalRocketMQProducer implements CanalMQProducer {
                   } catch (MQClientException | RemotingException | MQBrokerException | InterruptedException e) {
                       logger.error("Send message error!", e);
                       callback.rollback();
      +                return;
                   }
               } else {
                   List flatMessages = FlatMessage.messageConverter(data);
      @@ -90,6 +90,7 @@ public class CanalRocketMQProducer implements CanalMQProducer {
                               } catch (Exception e) {
                                   logger.error("send flat message to fixed partition error", e);
                                   callback.rollback();
      +                            return;
                               }
                           } else {
                               if (destination.getPartitionHash() != null && !destination.getPartitionHash().isEmpty()) {
      @@ -124,6 +125,7 @@ public class CanalRocketMQProducer implements CanalMQProducer {
                                           } catch (Exception e) {
                                               logger.error("send flat message to hashed partition error", e);
                                               callback.rollback();
      +                                        return;
                                           }
                                       }
                                   }
      
      From 8ae192f0a91f331e3343986cd91028c391132426 Mon Sep 17 00:00:00 2001
      From: mcy 
      Date: Tue, 6 Nov 2018 09:56:16 +0800
      Subject: [PATCH 280/360] =?UTF-8?q?adapter=20canal-client=20=E7=89=88?=
       =?UTF-8?q?=E6=9C=AC?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       client-adapter/common/pom.xml   | 2 +-
       client-adapter/launcher/pom.xml | 2 +-
       client-adapter/pom.xml          | 2 --
       3 files changed, 2 insertions(+), 4 deletions(-)
      
      diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml
      index f018c490..e62fc386 100644
      --- a/client-adapter/common/pom.xml
      +++ b/client-adapter/common/pom.xml
      @@ -14,7 +14,7 @@
               
                   com.alibaba.otter
                   canal.protocol
      -            ${canal_version}
      +            1.1.2-SNAPSHOT
               
               
                   joda-time
      diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml
      index c24674bd..c8969223 100644
      --- a/client-adapter/launcher/pom.xml
      +++ b/client-adapter/launcher/pom.xml
      @@ -33,7 +33,7 @@
               
                   com.alibaba.otter
                   canal.client
      -            ${canal_version}
      +            1.1.2-SNAPSHOT
               
               
                   org.yaml
      diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml
      index d18d1c19..4cc7a80d 100644
      --- a/client-adapter/pom.xml
      +++ b/client-adapter/pom.xml
      @@ -14,8 +14,6 @@
               1.8
               1.8
               UTF-8
      -
      -        1.1.2-SNAPSHOT
           
       
           
      
      From 5b94c69d100ac545a59054dd04e78e4983348fff Mon Sep 17 00:00:00 2001
      From: mcy 
      Date: Tue, 6 Nov 2018 14:52:17 +0800
      Subject: [PATCH 281/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9adapter=E6=89=93?=
       =?UTF-8?q?=E5=8C=85,=E5=90=AF=E5=8A=A8=E6=96=B9=E5=BC=8F?=
      MIME-Version: 1.0
      Content-Type: text/plain; charset=UTF-8
      Content-Transfer-Encoding: 8bit
      
      ---
       .../adapter/support/ExtensionLoader.java      | 153 ++---------------
       .../adapter/es/config/ESSyncConfigLoader.java |   4 +-
       .../adapter/es/service/ESSyncService.java     |   7 +-
       .../hbase/config/MappingConfigLoader.java     |   2 +-
       .../hbase/service/HbaseSyncService.java       |   3 +-
       client-adapter/launcher/pom.xml               | 154 ++++++++----------
       .../launcher/src/main/assembly/dev.xml        |  62 +++++++
       .../launcher/src/main/assembly/release.xml    |  65 ++++++--
       .../launcher/src/main/bin/restart.sh          |   5 +
       .../launcher/src/main/bin/startup.bat         |  22 +++
       .../launcher/src/main/bin/startup.sh          |  68 ++++++++
       client-adapter/launcher/src/main/bin/stop.sh  |  65 ++++++++
       .../src/main/resources/application.yml        |   2 +-
       .../example/db/CanalConnectorClient.java      |   4 +-
       14 files changed, 366 insertions(+), 250 deletions(-)
       create mode 100644 client-adapter/launcher/src/main/assembly/dev.xml
       create mode 100644 client-adapter/launcher/src/main/bin/restart.sh
       create mode 100755 client-adapter/launcher/src/main/bin/startup.bat
       create mode 100644 client-adapter/launcher/src/main/bin/startup.sh
       create mode 100644 client-adapter/launcher/src/main/bin/stop.sh
      
      diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java
      index 24d43a0f..cf69fde5 100644
      --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java
      +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java
      @@ -1,6 +1,9 @@
       package com.alibaba.otter.canal.client.adapter.support;
       
      -import java.io.*;
      +import java.io.BufferedReader;
      +import java.io.File;
      +import java.io.IOException;
      +import java.io.InputStreamReader;
       import java.net.MalformedURLException;
       import java.net.URL;
       import java.net.URLClassLoader;
      @@ -83,47 +86,6 @@ public class ExtensionLoader {
               this.classLoaderPolicy = classLoaderPolicy;
           }
       
      -    public String getExtensionName(T extensionInstance) {
      -        return getExtensionName(extensionInstance.getClass());
      -    }
      -
      -    public String getExtensionName(Class extensionClass) {
      -        return cachedNames.get(extensionClass);
      -    }
      -
      -    public ConcurrentHashMap getExceptions() {
      -        return exceptions;
      -    }
      -
      -    /**
      -     * 返回扩展点实例,如果没有指定的扩展点或是还没加载(即实例化)则返回null注意:此方法不会触发扩展点的加载
      -     * 

      - * 一般应该调用{@link #getExtension(String)}方法获得扩展,这个方法会触发扩展点加载 - * - * @see #getExtension(String) - */ - @SuppressWarnings("unchecked") - public T getLoadedExtension(String name) { - if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); - Holder holder = cachedInstances.get(name); - if (holder == null) { - cachedInstances.putIfAbsent(name, new Holder<>()); - holder = cachedInstances.get(name); - } - return (T) holder.get(); - } - - /** - * 返回已经加载的扩展点的名字 - *

      - * 一般应该调用{@link #getSupportedExtensions()}方法获得扩展,这个方法会返回所有的扩展点 - * - * @see #getSupportedExtensions() - */ - public Set getLoadedExtensions() { - return Collections.unmodifiableSet(new TreeSet<>(cachedInstances.keySet())); - } - /** * 返回指定名字的扩展 * @@ -165,87 +127,6 @@ public class ExtensionLoader { return getExtension(cachedDefaultName); } - public boolean hasExtension(String name) { - if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); - try { - return getExtensionClass(name) != null; - } catch (Throwable t) { - return false; - } - } - - public Set getSupportedExtensions() { - Map> clazzes = getExtensionClasses(); - return Collections.unmodifiableSet(new TreeSet(clazzes.keySet())); - } - - /** - * 返回缺省的扩展点名,如果没有设置缺省则返回null - */ - public String getDefaultExtensionName() { - getExtensionClasses(); - return cachedDefaultName; - } - - /** - * 编程方式添加新扩展点 - * - * @param name 扩展点名 - * @param clazz 扩展点类 - * @throws IllegalStateException 要添加扩展点名已经存在 - */ - public void addExtension(String name, Class clazz) { - getExtensionClasses(); // load classes - - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type); - } - if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + clazz + "can not be interface!"); - } - - if (name == null || "".equals(name)) { - throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); - } - if (cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + name + " already existed(Extension " + type + ")!"); - } - - cachedNames.put(clazz, name); - cachedClasses.get().put(name, clazz); - } - - /** - * 编程方式添加替换已有扩展点 - * - * @param name 扩展点名 - * @param clazz 扩展点类 - * @throws IllegalStateException 要添加扩展点名已经存在 - * @deprecated 不推荐应用使用,一般只在测试时可以使用 - */ - @Deprecated - public void replaceExtension(String name, Class clazz) { - getExtensionClasses(); // load classes - - if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException("Input type " + clazz + "not implement Extension " + type); - } - if (clazz.isInterface()) { - throw new IllegalStateException("Input type " + clazz + "can not be interface!"); - } - - if (name == null || "".equals(name)) { - throw new IllegalStateException("Extension name is blank (Extension " + type + ")!"); - } - if (!cachedClasses.get().containsKey(name)) { - throw new IllegalStateException("Extension name " + name + " not existed(Extension " + type + ")!"); - } - - cachedNames.put(clazz, name); - cachedClasses.get().put(name, clazz); - cachedInstances.remove(name); - } - @SuppressWarnings("unchecked") private T createExtension(String name) { Class clazz = getExtensionClasses().get(name); @@ -267,15 +148,6 @@ public class ExtensionLoader { } } - private Class getExtensionClass(String name) { - if (type == null) throw new IllegalArgumentException("Extension type == null"); - if (name == null) throw new IllegalArgumentException("Extension name == null"); - Class clazz = getExtensionClasses().get(name); - if (clazz == null) - throw new IllegalStateException("No such extension \"" + name + "\" for " + type.getName() + "!"); - return clazz; - } - private Map> getExtensionClasses() { Map> classes = cachedClasses.get(); if (classes == null) { @@ -334,23 +206,18 @@ public class ExtensionLoader { // 1. lib folder,customized extension classLoader (jar_dir/lib) String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib"; - logger.info("extension classpath dir: " + dir); + File externalLibDir = new File(dir); if (!externalLibDir.exists()) { - externalLibDir = new File( - File.separator + this.getJarDirectoryPath() + File.separator + "canal_client" + File.separator + "lib"); + externalLibDir = new File(File.separator + this.getJarDirectoryPath() + File.separator + "canal-adapter" + + File.separator + "lib"); } + logger.info("extension classpath dir: " + externalLibDir.getAbsolutePath()); if (externalLibDir.exists()) { - File[] files = externalLibDir.listFiles(new FilenameFilter() { - - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".jar"); - } - }); + File[] files = externalLibDir.listFiles((dir1, name) -> name.endsWith(".jar")); if (files != null) { for (File f : files) { - URL url = null; + URL url; try { url = f.toURI().toURL(); } catch (MalformedURLException e) { diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java index cb1cc274..d9b09161 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java @@ -99,11 +99,11 @@ public class ESSyncConfigLoader { InputStream in = null; try { // 先取本地文件,再取类路径 - File configFile = new File("config/" + config); + File configFile = new File("../config/" + config); if (configFile.exists()) { in = new FileInputStream(configFile); } else { - in = ESSyncConfigLoader.class.getClassLoader().getResourceAsStream(config); + in = ESSyncConfigLoader.class.getClassLoader().getResourceAsStream(config); } if (in == null) { throw new RuntimeException("Config file: " + config + " not found."); diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java index 3927c6ab..272ef1a9 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java @@ -9,6 +9,7 @@ import javax.sql.DataSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSON; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader; @@ -38,9 +39,6 @@ public class ESSyncService { } public void sync(Dml dml) { - if (logger.isDebugEnabled()) { - logger.debug("DML: {}", dml.toString()); - } long begin = System.currentTimeMillis(); String database = dml.getDatabase(); String table = dml.getTable(); @@ -74,6 +72,9 @@ public class ESSyncService { esSyncConfigs.size(), dml.getDestination()); } + if (logger.isDebugEnabled()) { + logger.debug("DML: {}", JSON.toJSONString(dml)); + } } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index cd5407d8..5b6cf2bd 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -124,7 +124,7 @@ public class MappingConfigLoader { InputStream in = null; try { // 先取本地文件,再取类路径 - File configFile = new File("config/" + config); + File configFile = new File("../config/" + config); if (configFile.exists()) { in = new FileInputStream(configFile); } else { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java index 9606ee97..420640af 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -6,6 +6,7 @@ import org.apache.hadoop.hbase.util.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSON; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.support.*; import com.alibaba.otter.canal.client.adapter.support.Dml; @@ -38,7 +39,7 @@ public class HbaseSyncService { delete(config, dml); } if (logger.isDebugEnabled()) { - logger.debug("DML: {}", dml.toString()); + logger.debug("DML: {}", JSON.toJSONString(dml)); } } } catch (Exception e) { diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index c24674bd..6b5f71dd 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -47,7 +47,7 @@ org.springframework.boot spring-boot-configuration-processor - true + provided org.apache.curator @@ -79,7 +79,7 @@ jar-with-dependencies - true + provided com.alibaba.otter @@ -92,7 +92,7 @@ jar-with-dependencies - true + provided com.alibaba.otter @@ -105,72 +105,12 @@ jar-with-dependencies - true + provided - canal-adapter-launcher - - org.springframework.boot - spring-boot-maven-plugin - 2.0.1.RELEASE - - - - com.alibaba.otter - client-adapter.logger - - - com.alibaba.otter - client-adapter.hbase - - - com.alibaba.otter - client-adapter.elasticsearch - - - - - - - repackage - - - - - - maven-antrun-plugin - - - package - - run - - - - - - - - - - - - - - - - - - - - - - - org.apache.maven.plugins maven-dependency-plugin @@ -184,15 +124,81 @@ jar-with-dependencies - ${project.basedir}/target/lib + ${project.basedir}/target/canal-adapter/lib + + org.apache.maven.plugins + maven-assembly-plugin + 2.2.1 + + + assemble + + single + + package + + + + false + false + + + + org.apache.maven.plugins + maven-jar-plugin + 3.0.2 + + + + true + lib/ + com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication + + + + **/*.properties + **/*.xml + **/*.yml + static/** + templates/** + + + + + dev + + true + + env + !release + + + + + + + maven-assembly-plugin + + + ${basedir}/src/main/assembly/dev.xml + + canal-adapter + ${project.build.directory} + + + + + + + release @@ -207,32 +213,14 @@ maven-assembly-plugin - false ${basedir}/src/main/assembly/release.xml ${project.artifactId}-${project.version} ${project.basedir}/../../target - - - make-assembly - package - - single - - - - - - src/main/resources - - application.yml - - - diff --git a/client-adapter/launcher/src/main/assembly/dev.xml b/client-adapter/launcher/src/main/assembly/dev.xml new file mode 100644 index 00000000..1be787b5 --- /dev/null +++ b/client-adapter/launcher/src/main/assembly/dev.xml @@ -0,0 +1,62 @@ + + dist + + dir + + false + + + . + / + + README* + + + + ./src/main/bin + bin + + **/* + + 0755 + + + ./src/main/resources + /config + + **/* + + + + + ../elasticsearch/src/main/resources/es + /config/es + + **/* + + + + ../hbase/src/main/resources/hbase + /config/hbase + + **/* + + + + target + logs + + **/* + + + + + + lib + + junit:junit + + + + diff --git a/client-adapter/launcher/src/main/assembly/release.xml b/client-adapter/launcher/src/main/assembly/release.xml index 7bb12314..57a815c8 100644 --- a/client-adapter/launcher/src/main/assembly/release.xml +++ b/client-adapter/launcher/src/main/assembly/release.xml @@ -1,34 +1,71 @@ - canal-adapter + dist tar.gz + false - ${project.build.directory} + . / - *.jar + README* + + ./src/main/bin + bin + + **/* + + 0755 + + + ./src/main/resources + /config + + **/* + + + + + ../elasticsearch/src/main/resources/es + /config/es + + **/* + + + + ../hbase/src/main/resources/hbase + /config/hbase + + **/* + + + + target + logs + + **/* + + - ${project.build.directory}/lib + ${project.basedir}/target/canal-adapter/lib /lib/ - *.jar - - - - - ${project.build.directory}/config - /config/ - - ** + *-jar-with-dependencies.jar - / + + + lib + + junit:junit + + + \ No newline at end of file diff --git a/client-adapter/launcher/src/main/bin/restart.sh b/client-adapter/launcher/src/main/bin/restart.sh new file mode 100644 index 00000000..63749ce5 --- /dev/null +++ b/client-adapter/launcher/src/main/bin/restart.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +sh stop.sh + +sh startup.sh diff --git a/client-adapter/launcher/src/main/bin/startup.bat b/client-adapter/launcher/src/main/bin/startup.bat new file mode 100755 index 00000000..00555432 --- /dev/null +++ b/client-adapter/launcher/src/main/bin/startup.bat @@ -0,0 +1,22 @@ +@echo off +@if not "%ECHO%" == "" echo %ECHO% +@if "%OS%" == "Windows_NT" setlocal + +set ENV_PATH=.\ +if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% + +set conf_dir=%ENV_PATH%\..\config + +set CLASSPATH=%conf_dir% +set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% + +set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m +set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 +set ADAPTER_OPTS= -DappName=canal-adapter + +set JAVA_OPTS= %JAVA_MEM_OPTS% %JAVA_OPTS_EXT% %ADAPTER_OPTS% + +set CMD_STR= java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication +echo start cmd : %CMD_STR% + +java %JAVA_OPTS% -classpath "%CLASSPATH%" com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication \ No newline at end of file diff --git a/client-adapter/launcher/src/main/bin/startup.sh b/client-adapter/launcher/src/main/bin/startup.sh new file mode 100644 index 00000000..001f7fb9 --- /dev/null +++ b/client-adapter/launcher/src/main/bin/startup.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +current_path=`pwd` +case "`uname`" in + Linux) + bin_abs_path=$(readlink -f $(dirname $0)) + ;; + *) + bin_abs_path=`cd $(dirname $0); pwd` + ;; +esac +base=${bin_abs_path}/.. +#canal_conf=$base/config/canal.properties +#logback_configurationFile=$base/conf/logback.xml +export LANG=en_US.UTF-8 +export BASE=$base + +if [ -f $base/bin/adapter.pid ] ; then + echo "found adapter.pid , Please run stop.sh first ,then startup.sh" 2>&2 + exit 1 +fi + +if [ ! -d $base/logs ] ; then + mkdir -p $base/logs +fi + +## set java path +if [ -z "$JAVA" ] ; then + JAVA=$(which java) +fi + +ALIBABA_JAVA="/usr/alibaba/java/bin/java" +TAOBAO_JAVA="/opt/taobao/java/bin/java" +if [ -z "$JAVA" ]; then + if [ -f $ALIBABA_JAVA ] ; then + JAVA=$ALIBABA_JAVA + elif [ -f $TAOBAO_JAVA ] ; then + JAVA=$TAOBAO_JAVA + else + echo "Cannot find a Java JDK. Please set either set JAVA or put java (>=1.5) in your PATH." 2>&2 + exit 1 + fi +fi + +str=`file -L $JAVA | grep 64-bit` +if [ -n "$str" ]; then + JAVA_OPTS="-server -Xms2048m -Xmx3072m -Xmn1024m -XX:SurvivorRatio=2 -XX:PermSize=96m -XX:MaxPermSize=256m -Xss256k -XX:-UseAdaptiveSizePolicy -XX:MaxTenuringThreshold=15 -XX:+DisableExplicitGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:+HeapDumpOnOutOfMemoryError" +else + JAVA_OPTS="-server -Xms1024m -Xmx1024m -XX:NewSize=256m -XX:MaxNewSize=256m -XX:MaxPermSize=128m " +fi + +JAVA_OPTS=" $JAVA_OPTS -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dfile.encoding=UTF-8" +ADAPTER_OPTS="-DappName=canal-adapter" + +for i in $base/lib/*; + do CLASSPATH=$i:"$CLASSPATH"; +done +CLASSPATH="$base/config:$CLASSPATH"; + +echo "cd to $bin_abs_path for workaround relative path" +cd $bin_abs_path + +echo CLASSPATH :$CLASSPATH +$JAVA $JAVA_OPTS $ADAPTER_OPTS -classpath .:$CLASSPATH com.alibaba.otter.canal.adapter.launcher.CanalAdapterApplication 1>>$base/logs/adapter.log 2>&1 & +echo $! > $base/bin/adapter.pid + +echo "cd to $current_path for continue" +cd $current_path \ No newline at end of file diff --git a/client-adapter/launcher/src/main/bin/stop.sh b/client-adapter/launcher/src/main/bin/stop.sh new file mode 100644 index 00000000..fbdd5317 --- /dev/null +++ b/client-adapter/launcher/src/main/bin/stop.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +cygwin=false; +linux=false; +case "`uname`" in + CYGWIN*) + cygwin=true + ;; + Linux*) + linux=true + ;; +esac + +get_pid() { + STR=$1 + PID=$2 + if $cygwin; then + JAVA_CMD="$JAVA_HOME\bin\java" + JAVA_CMD=`cygpath --path --unix $JAVA_CMD` + JAVA_PID=`ps |grep $JAVA_CMD |awk '{print $1}'` + else + if $linux; then + if [ ! -z "$PID" ]; then + JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` + else + JAVA_PID=`ps -C java -f --width 1000|grep "$STR"|grep -v grep|awk '{print $2}'` + fi + else + if [ ! -z "$PID" ]; then + JAVA_PID=`ps aux |grep "$STR"|grep "$PID"|grep -v grep|awk '{print $2}'` + else + JAVA_PID=`ps aux |grep "$STR"|grep -v grep|awk '{print $2}'` + fi + fi + fi + echo $JAVA_PID; +} + +base=`dirname $0`/.. +pidfile=$base/bin/adapter.pid +if [ ! -f "$pidfile" ];then + echo "canal-adapter is not running. exists" + exit +fi + +pid=`cat $pidfile` +if [ "$pid" == "" ] ; then + pid=`get_pid "appName=canal-adapter"` +fi + +echo -e "`hostname`: stopping canal $pid ... " +kill $pid + +LOOPS=0 +while (true); +do + gpid=`get_pid "appName=canal-adapter" "$pid"` + if [ "$gpid" == "" ] ; then + echo "Oook! cost:$LOOPS" + `rm $pidfile` + break; + fi + let LOOPS=LOOPS+1 + sleep 1 +done \ No newline at end of file diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index f0b4c6a2..894d123e 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -3,7 +3,7 @@ server: logging: level: com.alibaba.otter.canal.client.adapter.hbase: DEBUG - com.alibaba.otter.canal.client.adapter.es: TRACE + com.alibaba.otter.canal.client.adapter.es: DEBUG spring: jackson: date-format: yyyy-MM-dd HH:mm:ss diff --git a/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java b/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java index 844f60db..247f9e1f 100644 --- a/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java +++ b/example/src/main/java/com/alibaba/otter/canal/example/db/CanalConnectorClient.java @@ -359,7 +359,7 @@ public abstract class CanalConnectorClient extends AbstractCanalLifeCycle implem logger.warn("parse delete index event, table: {}, sql: {}", table, sql); return; default: - logger.warn("parse unknown event: {}, table: {}, sql: {}", new String[]{eventType.name(), table, sql}); + logger.warn("parse unknown event: {}, table: {}, sql: {}", eventType.name(), table, sql); break; } } @@ -375,7 +375,7 @@ public abstract class CanalConnectorClient extends AbstractCanalLifeCycle implem public void whenOthers(CanalEntry.Header header, String sql) { String schema = header.getSchemaName(); String table = header.getTableName(); - logger.error("ignore event,schema: {},table: {},SQL: {}", new String[]{schema, table, sql}); + logger.error("ignore event,schema: {},table: {},SQL: {}", schema, table, sql); } public enum ExceptionStrategy { From f2b8d000f766f644f2332f45daabc84f158aae73 Mon Sep 17 00:00:00 2001 From: wuwo Date: Tue, 6 Nov 2018 16:58:50 +0800 Subject: [PATCH 282/360] skip tsdb snapshot apply if has not receive new ddl change from last snapshot time --- .../canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index 8896e2f5..7f4ba057 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -63,6 +63,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { private CanalEventFilter filter; private CanalEventFilter blackFilter; private EntryPosition lastPosition; + private boolean hasNewDdl; private MetaHistoryDAO metaHistoryDAO; private MetaSnapshotDAO metaSnapshotDAO; private int snapshotInterval = 24; @@ -139,6 +140,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { synchronized (memoryTableMeta) { if (memoryTableMeta.apply(position, schema, ddl, extra)) { this.lastPosition = position; + this.hasNewDdl = true; // 同步每次变更给远程做历史记录 return applyHistoryToDB(position, schema, ddl, extra); } else { @@ -270,10 +272,11 @@ public class DatabaseTableMeta implements TableMetaTSDB { MemoryTableMeta tmpMemoryTableMeta = new MemoryTableMeta(); Map schemaDdls = null; synchronized (memoryTableMeta) { - if (!init && position == null) { + if (!init && !hasNewDdl) { // 如果是持续构建,则识别一下是否有DDL变更过,如果没有就忽略了 return false; } + this.hasNewDdl = false; schemaDdls = memoryTableMeta.snapshot(); for (Map.Entry entry : schemaDdls.entrySet()) { tmpMemoryTableMeta.apply(position, entry.getKey(), entry.getValue(), null); From 6de9f7efa730afd9cf0f5607376773b9e453318c Mon Sep 17 00:00:00 2001 From: machey Date: Tue, 6 Nov 2018 22:49:34 +0800 Subject: [PATCH 283/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20adapter=20release?= =?UTF-8?q?=20=E6=89=93=E5=8C=85=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/launcher/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index 6b5f71dd..6c39f828 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -216,7 +216,7 @@ ${basedir}/src/main/assembly/release.xml - ${project.artifactId}-${project.version} + canal.adapter-${project.version} ${project.basedir}/../../target From 3090557eeb8dbc9712bb8f5aa237ec249e957cc3 Mon Sep 17 00:00:00 2001 From: machey Date: Tue, 6 Nov 2018 23:16:11 +0800 Subject: [PATCH 284/360] =?UTF-8?q?client=20=E6=8E=92=E9=99=A4jsr305?= =?UTF-8?q?=E4=B8=8D=E6=89=93=E8=BF=9Bfat=20jar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/client/pom.xml b/client/pom.xml index c12447c9..aa31390a 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -149,6 +149,7 @@ commons-logging:commons-logging aopalliance:aopalliance com.google.protobuf:protobuf-java + com.google.code.findbugs:jsr305 io.netty:* junit:junit From c27d5fa16274207176f9a638ea92e3afca118189 Mon Sep 17 00:00:00 2001 From: machey Date: Wed, 7 Nov 2018 02:04:08 +0800 Subject: [PATCH 285/360] =?UTF-8?q?ETL=E6=95=B4=E4=B8=AAdestination?= =?UTF-8?q?=EF=BC=8C=E5=90=8C=E6=AD=A5=E5=BC=80=E5=85=B3=E6=97=A0=E6=95=88?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/launcher/rest/CommonRest.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index d2c163fb..6f09c33b 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -67,10 +67,18 @@ public class CommonRest { try { OuterAdapter adapter = loader.getExtension(type); String destination = adapter.getDestination(task); - Boolean oriSwithcStatus = null; + Boolean oriSwitchStatus; if (destination != null) { - oriSwithcStatus = syncSwitch.status(destination); - syncSwitch.off(destination); + oriSwitchStatus = syncSwitch.status(destination); + if (oriSwitchStatus != null && oriSwitchStatus) { + syncSwitch.off(destination); + } + } else { + // task可能为destination,直接锁task + oriSwitchStatus = syncSwitch.status(task); + if (oriSwitchStatus != null && oriSwitchStatus) { + syncSwitch.off(task); + } } try { List paramArr = null; @@ -80,8 +88,10 @@ public class CommonRest { } return adapter.etl(task, paramArr); } finally { - if (destination != null && oriSwithcStatus != null && oriSwithcStatus) { + if (destination != null && oriSwitchStatus != null && oriSwitchStatus) { syncSwitch.on(destination); + } else if (destination == null && oriSwitchStatus != null && oriSwitchStatus) { + syncSwitch.on(task); } } } finally { From 202557630c027ffb23bac383bd87d409b5e751b4 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 7 Nov 2018 09:05:47 +0800 Subject: [PATCH 286/360] =?UTF-8?q?=E8=B0=83=E6=95=B4etl=E9=94=81=E7=9A=84?= =?UTF-8?q?=E7=B2=92=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/adapter/launcher/rest/CommonRest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index 6f09c33b..0a6d00f4 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -56,8 +56,11 @@ public class CommonRest { @PostMapping("/etl/{type}/{task}") public EtlResult etl(@PathVariable String type, @PathVariable String task, @RequestParam(name = "params", required = false) String params) { + OuterAdapter adapter = loader.getExtension(type); + String destination = adapter.getDestination(task); + String lockKey = destination == null ? task : destination; - boolean locked = etlLock.tryLock(ETL_LOCK_ZK_NODE + type + "-" + task); + boolean locked = etlLock.tryLock(ETL_LOCK_ZK_NODE + type + "-" + lockKey); if (!locked) { EtlResult result = new EtlResult(); result.setSucceeded(false); @@ -65,8 +68,7 @@ public class CommonRest { return result; } try { - OuterAdapter adapter = loader.getExtension(type); - String destination = adapter.getDestination(task); + Boolean oriSwitchStatus; if (destination != null) { oriSwitchStatus = syncSwitch.status(destination); @@ -95,7 +97,7 @@ public class CommonRest { } } } finally { - etlLock.unlock(ETL_LOCK_ZK_NODE + type + "-" + task); + etlLock.unlock(ETL_LOCK_ZK_NODE + type + "-" + lockKey); } } From bc298bedd533bde6247cba6b59207d70f266a4b5 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 7 Nov 2018 09:32:52 +0800 Subject: [PATCH 287/360] =?UTF-8?q?=E6=94=AF=E6=8C=81hbase=20rowkey?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=95=B0=E5=AD=97=E9=95=BF=E5=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/hbase/config/MappingConfig.java | 20 +++++- .../hbase/service/HbaseEtlService.java | 67 +++++++++++++++---- .../hbase/service/HbaseSyncService.java | 43 ++++++++++-- .../main/resources/hbase/mytest_person2.yml | 14 ++-- 4 files changed, 118 insertions(+), 26 deletions(-) diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java index c380b65c..326971ac 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -76,6 +76,7 @@ public class MappingConfig { public static class ColumnItem { private boolean isRowKey = false; + private Integer rowKeyLen; private String column; private String family; private String qualifier; @@ -89,6 +90,14 @@ public class MappingConfig { isRowKey = rowKey; } + public Integer getRowKeyLen() { + return rowKeyLen; + } + + public void setRowKeyLen(Integer rowKeyLen) { + this.rowKeyLen = rowKeyLen; + } + public String getColumn() { return column; } @@ -264,7 +273,16 @@ public class MappingConfig { ColumnItem columnItem = new ColumnItem(); columnItem.setColumn(columnField.getKey()); columnItem.setType(type); - if ("rowKey".equalsIgnoreCase(field)) { + if (field != null && field.toUpperCase().startsWith("ROWKEY")) { + int idx = field.toUpperCase().indexOf("LEN:"); + if (idx > -1) { + String len = field.substring(idx + 4); + try { + columnItem.setRowKeyLen(Integer.parseInt(len)); + } catch (Exception e) { + // ignore + } + } columnItem.setRowKey(true); rowKeyColumn = columnItem; } else { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index 7c57a9ab..912baf08 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -199,8 +199,8 @@ public class HbaseEtlService { } else { sqlFinal = sql + " LIMIT " + offset + "," + cnt; } - Future future = executor - .submit(() -> executeSqlImport(ds, sqlFinal, hbaseMapping, hbaseTemplate, successCount, errMsg)); + Future future = executor.submit( + () -> executeSqlImport(ds, sqlFinal, hbaseMapping, hbaseTemplate, successCount, errMsg)); futures.add(future); } @@ -213,8 +213,8 @@ public class HbaseEtlService { executeSqlImport(ds, sql, hbaseMapping, hbaseTemplate, successCount, errMsg); } - logger.info( - hbaseMapping.getHbaseTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); + logger.info(hbaseMapping.getHbaseTable() + " etl completed in: " + + (System.currentTimeMillis() - start) / 1000 + "s!"); etlResult.setResultMessage("导入HBase表 " + hbaseMapping.getHbaseTable() + " 数据:" + successCount.get() + " 条"); } catch (Exception e) { @@ -332,18 +332,49 @@ public class HbaseEtlService { byte[] valBytes = Bytes.toBytes(val.toString()); if (columnItem.isRowKey()) { - row.setRowKey(valBytes); + if (columnItem.getRowKeyLen() != null) { + valBytes = Bytes.toBytes(limitLenNum(columnItem.getRowKeyLen(), val)); + row.setRowKey(valBytes); + } else { + row.setRowKey(valBytes); + } } else { row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes); } } else { - PhType phType = PhType.getType(columnItem.getType()); - if (columnItem.isRowKey()) { - row.setRowKey(PhTypeUtil.toBytes(val, phType)); - } else { - row.addCell(columnItem.getFamily(), - columnItem.getQualifier(), - PhTypeUtil.toBytes(val, phType)); + if (MappingConfig.Mode.STRING == hbaseMapping.getMode()) { + byte[] valBytes = Bytes.toBytes(val.toString()); + if (columnItem.isRowKey()) { + if (columnItem.getRowKeyLen() != null) { + valBytes = Bytes.toBytes(limitLenNum(columnItem.getRowKeyLen(), val)); + } + row.setRowKey(valBytes); + } else { + row.addCell(columnItem.getFamily(), columnItem.getQualifier(), valBytes); + } + } else if (MappingConfig.Mode.NATIVE == hbaseMapping.getMode()) { + Type type = Type.getType(columnItem.getType()); + if (columnItem.isRowKey()) { + if (columnItem.getRowKeyLen() != null) { + String v = limitLenNum(columnItem.getRowKeyLen(), val); + row.setRowKey(Bytes.toBytes(v)); + } else { + row.setRowKey(TypeUtil.toBytes(val, type)); + } + } else { + row.addCell(columnItem.getFamily(), + columnItem.getQualifier(), + TypeUtil.toBytes(val, type)); + } + } else if (MappingConfig.Mode.PHOENIX == hbaseMapping.getMode()) { + PhType phType = PhType.getType(columnItem.getType()); + if (columnItem.isRowKey()) { + row.setRowKey(PhTypeUtil.toBytes(val, phType)); + } else { + row.addCell(columnItem.getFamily(), + columnItem.getQualifier(), + PhTypeUtil.toBytes(val, phType)); + } } } } @@ -382,4 +413,16 @@ public class HbaseEtlService { return false; } } + + private static String limitLenNum(int len, Object val) { + if (val == null) { + return null; + } + if (val instanceof Number) { + return String.format("%0" + len + "d", (Number) ((Number) val).longValue()); + } else if (val instanceof String) { + return String.format("%0" + len + "d", Long.parseLong((String) val)); + } + return null; + } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java index 420640af..2a242093 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -132,7 +132,21 @@ public class HbaseSyncService { } } else { if (columnItem.isRowKey()) { - // row.put("rowKey", bytes); + if (columnItem.getRowKeyLen() != null && entry.getValue() != null) { + if (entry.getValue() instanceof Number) { + String v = String.format("%0" + columnItem.getRowKeyLen() + "d", + ((Number) entry.getValue()).longValue()); + bytes = Bytes.toBytes(v); + } else { + try { + String v = String.format("%0" + columnItem.getRowKeyLen() + "d", + Integer.parseInt((String) entry.getValue())); + bytes = Bytes.toBytes(v); + } catch (Exception e) { + // ignore + } + } + } hRow.setRowKey(bytes); } else { hRow.addCell(columnItem.getFamily(), columnItem.getQualifier(), bytes); @@ -146,8 +160,8 @@ public class HbaseSyncService { /** * 更新操作 * - * @param config - * @param dml + * @param config 配置对象 + * @param dml dml对象 */ private void update(MappingConfig config, Dml dml) { List> data = dml.getData(); @@ -192,7 +206,7 @@ public class HbaseSyncService { Map rowKey = data.get(0); rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next()); } else { - rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, r.get(rowKeyColumn.getColumn())); + rowKeyBytes = getRowKeyBytes(hbaseMapping, rowKeyColumn, r); } if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空"); @@ -277,8 +291,7 @@ public class HbaseSyncService { Map rowKey = data.get(0); rowKeyBytes = typeConvert(null, hbaseMapping, rowKey.values().iterator().next()); } else { - Object val = r.get(rowKeyColumn.getColumn()); - rowKeyBytes = typeConvert(rowKeyColumn, hbaseMapping, val); + rowKeyBytes = getRowKeyBytes(hbaseMapping, rowKeyColumn, r); } if (rowKeyBytes == null) throw new RuntimeException("rowKey值为空"); rowKeys.add(rowKeyBytes); @@ -424,4 +437,22 @@ public class HbaseSyncService { return rowKeyValue.toString(); } + private static byte[] getRowKeyBytes(MappingConfig.HbaseMapping hbaseMapping, MappingConfig.ColumnItem rowKeyColumn, + Map rowData) { + Object val = rowData.get(rowKeyColumn.getColumn()); + String v = null; + if (rowKeyColumn.getRowKeyLen() != null) { + if (val instanceof Number) { + v = String.format("%0" + rowKeyColumn.getRowKeyLen() + "d", (Number) ((Number) val).longValue()); + } else if (val instanceof String) { + v = String.format("%0" + rowKeyColumn.getRowKeyLen() + "d", Long.parseLong((String) val)); + } + } + if (v != null) { + return Bytes.toBytes(v); + } else { + return typeConvert(rowKeyColumn, hbaseMapping, val); + } + } + } diff --git a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml index cbfe6bb1..0eebad47 100644 --- a/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml +++ b/client-adapter/hbase/src/main/resources/hbase/mytest_person2.yml @@ -1,7 +1,7 @@ dataSourceKey: defaultDS destination: example hbaseMapping: - mode: PHOENIX #NATIVE #STRING + mode: STRING #NATIVE #PHOENIX database: mytest # 数据库名 table: person2 # 数据库表名 hbaseTable: MYTEST.PERSON2 # HBase表名 @@ -11,14 +11,14 @@ hbaseMapping: #rowKey: id,type # 复合字段rowKey不能和columns中的rowKey重复 columns: # 数据库字段:HBase对应字段 - id: ROWKEY$UNSIGNED_LONG + id: ROWKEY LEN:15 name: NAME email: EMAIL - type: $DECIMAL - c_time: C_TIME$UNSIGNED_TIMESTAMP - birthday: BIRTHDAY$DATE - excludeColumns: - - lat # 忽略字段 + type: + c_time: C_TIME + birthday: BIRTHDAY +# excludeColumns: +# - lat # 忽略字段 # -- NATIVE类型 # $DEFAULT From b71e2991610febd5d19d9e3a303d3c9fe667ce32 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 7 Nov 2018 11:37:16 +0800 Subject: [PATCH 288/360] =?UTF-8?q?kafka=E5=8D=95=E4=B8=AApartition?= =?UTF-8?q?=E7=9A=84=E5=8F=91=E9=80=81=E6=9C=AA=E6=94=B9=E4=B8=BA=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=8F=91=E9=80=81=E7=9A=84bug=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index 8576835f..a4c9eaa8 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -105,7 +105,7 @@ public class CanalKafkaProducer implements CanalMQProducer { canalDestination.getPartition(), null, JSON.toJSONString(flatMessage)); - producer2.send(record); + producer2.send(record).get(); } catch (Exception e) { logger.error(e.getMessage(), e); // producer.abortTransaction(); From 87abaee8a7e50ee6a4c7b5f9b3f70ced217e6ce2 Mon Sep 17 00:00:00 2001 From: wuwo Date: Wed, 7 Nov 2018 15:50:33 +0800 Subject: [PATCH 289/360] a little improvement for DatabaseTableMeta --- .../inbound/mysql/tsdb/DatabaseTableMeta.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index 7f4ba057..a958dbbe 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -10,6 +10,8 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; import org.apache.commons.beanutils.BeanUtils; @@ -57,6 +59,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { return thread; } }); + private ReadWriteLock lock = new ReentrantReadWriteLock(); private String destination; private MemoryTableMeta memoryTableMeta; private MysqlConnection connection; // 查询meta信息的链接 @@ -129,15 +132,19 @@ public class DatabaseTableMeta implements TableMetaTSDB { @Override public TableMeta find(String schema, String table) { - synchronized (memoryTableMeta) { + lock.readLock().lock(); + try{ return memoryTableMeta.find(schema, table); + }finally { + lock.readLock().unlock(); } } @Override public boolean apply(EntryPosition position, String schema, String ddl, String extra) { // 首先记录到内存结构 - synchronized (memoryTableMeta) { + lock.writeLock().lock(); + try{ if (memoryTableMeta.apply(position, schema, ddl, extra)) { this.lastPosition = position; this.hasNewDdl = true; @@ -146,6 +153,8 @@ public class DatabaseTableMeta implements TableMetaTSDB { } else { throw new RuntimeException("apply to memory is failed"); } + }finally { + lock.writeLock().unlock(); } } @@ -269,18 +278,22 @@ public class DatabaseTableMeta implements TableMetaTSDB { */ private boolean applySnapshotToDB(EntryPosition position, boolean init) { // 获取一份快照 - MemoryTableMeta tmpMemoryTableMeta = new MemoryTableMeta(); Map schemaDdls = null; - synchronized (memoryTableMeta) { + lock.readLock().lock(); + try{ if (!init && !hasNewDdl) { // 如果是持续构建,则识别一下是否有DDL变更过,如果没有就忽略了 return false; } this.hasNewDdl = false; schemaDdls = memoryTableMeta.snapshot(); - for (Map.Entry entry : schemaDdls.entrySet()) { - tmpMemoryTableMeta.apply(position, entry.getKey(), entry.getValue(), null); - } + }finally{ + lock.readLock().unlock(); + } + + MemoryTableMeta tmpMemoryTableMeta = new MemoryTableMeta(); + for (Map.Entry entry : schemaDdls.entrySet()) { + tmpMemoryTableMeta.apply(position, entry.getKey(), entry.getValue(), null); } // 基于临时内存对象进行对比 From 2278340c7b8453e2521bad6bed0b4a82017d47b0 Mon Sep 17 00:00:00 2001 From: littleneko Date: Wed, 7 Nov 2018 20:24:38 +0800 Subject: [PATCH 290/360] =?UTF-8?q?fix:=20buildLastTransactionPosition=20?= =?UTF-8?q?=E7=9A=84=20for=20=E5=BE=AA=E7=8E=AF=E5=B0=91=E4=BA=86=20=3D0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alibaba/otter/canal/parse/inbound/AbstractEventParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index 8902c05e..4d5cf7ec 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -417,7 +417,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle } protected LogPosition buildLastTransactionPosition(List entries) { // 初始化一下 - for (int i = entries.size() - 1; i > 0; i--) { + for (int i = entries.size() - 1; i >= 0; i--) { CanalEntry.Entry entry = entries.get(i); if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {// 尽量记录一个事务做为position return buildLastPosition(entry); From 5ae71121927887b27d42bc9a8d8d10b730932da5 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 7 Nov 2018 21:01:50 +0800 Subject: [PATCH 291/360] init rdb sync --- .../adapter/es/config/ESSyncConfigLoader.java | 6 +- .../hbase/config/MappingConfigLoader.java | 9 +- .../launcher/config/AdapterConfig.java | 2 - client-adapter/pom.xml | 1 + client-adapter/rdb/pom.xml | 85 ++++ .../canal/client/adapter/rdb/RdbAdapter.java | 86 ++++ .../adapter/rdb/config/MappingConfig.java | 142 +++++++ .../rdb/config/MappingConfigLoader.java | 102 +++++ .../adapter/rdb/service/RdbSyncService.java | 368 ++++++++++++++++++ ...ba.otter.canal.client.adapter.OuterAdapter | 1 + .../src/main/resources/rdb/mytest_user.yml | 12 + .../adapter/rdb/test/ConfigLoadTest.java | 29 ++ .../canal/client/adapter/rdb/test/DBTest.java | 82 ++++ .../client/adapter/rdb/test/TestConstant.java | 37 ++ .../client/adapter/rdb/test/sync/Common.java | 28 ++ .../adapter/rdb/test/sync/OracleSyncTest.java | 47 +++ .../rdb/src/test/resources/log4j2-test.xml | 13 + .../rdb/src/test/resources/logback-test.xml | 13 + .../src/test/resources/rdb/mytest_user.yml | 14 + 19 files changed, 1068 insertions(+), 9 deletions(-) create mode 100644 client-adapter/rdb/pom.xml create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java create mode 100644 client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter create mode 100644 client-adapter/rdb/src/main/resources/rdb/mytest_user.yml create mode 100644 client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java create mode 100644 client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java create mode 100644 client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/TestConstant.java create mode 100644 client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java create mode 100644 client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java create mode 100644 client-adapter/rdb/src/test/resources/log4j2-test.xml create mode 100644 client-adapter/rdb/src/test/resources/logback-test.xml create mode 100644 client-adapter/rdb/src/test/resources/rdb/mytest_user.yml diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java index d9b09161..681537cf 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java @@ -42,7 +42,7 @@ public class ESSyncConfigLoader { } public static synchronized void load() { - logger.info("## Start loading mapping config ... "); + logger.info("## Start loading es mapping config ... "); Collection configs = AdapterConfigs.get("es"); if (configs == null) { return; @@ -92,7 +92,7 @@ public class ESSyncConfigLoader { esSyncConfig.put(c, config); } - logger.info("## Mapping config loaded"); + logger.info("## ES mapping config loaded"); } private static String readConfigContent(String config) { @@ -113,7 +113,7 @@ public class ESSyncConfigLoader { in.read(bytes); return new String(bytes, StandardCharsets.UTF_8); } catch (IOException e) { - throw new RuntimeException("Read yml config error ", e); + throw new RuntimeException("Read es mapping config error ", e); } finally { try { if (in != null) { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 5b6cf2bd..f2ee6994 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -4,6 +4,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; @@ -33,7 +34,7 @@ public class MappingConfigLoader { * @return 配置名/配置文件名--对象 */ public static Map load() { - logger.info("## Start loading mapping config ... "); + logger.info("## Start loading hbase mapping config ... "); Map result = new LinkedHashMap<>(); @@ -116,7 +117,7 @@ public class MappingConfigLoader { result.put(c, config); } - logger.info("## Mapping config loaded"); + logger.info("## Hbase mapping config loaded"); return result; } @@ -136,9 +137,9 @@ public class MappingConfigLoader { byte[] bytes = new byte[in.available()]; in.read(bytes); - return new String(bytes, "UTF-8"); + return new String(bytes, StandardCharsets.UTF_8); } catch (IOException e) { - throw new RuntimeException("Read ds-config.yml or hbase-mappings.conf error. ", e); + throw new RuntimeException("Read hbase mapping config error. ", e); } finally { try { if (in != null) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java index f3e265f8..0f18c996 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java @@ -55,8 +55,6 @@ public class AdapterConfig { ds.setMaxWait(60000); ds.setTimeBetweenEvictionRunsMillis(60000); ds.setMinEvictableIdleTimeMillis(300000); - ds.setPoolPreparedStatements(false); - ds.setMaxPoolPreparedStatementPerConnectionSize(20); ds.setValidationQuery("select 1"); try { ds.init(); diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml index 4cc7a80d..3c1dcb55 100644 --- a/client-adapter/pom.xml +++ b/client-adapter/pom.xml @@ -22,6 +22,7 @@ hbase elasticsearch launcher + rdb diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml new file mode 100644 index 00000000..951579a9 --- /dev/null +++ b/client-adapter/rdb/pom.xml @@ -0,0 +1,85 @@ + + + + canal.client-adapter + com.alibaba.otter + 1.1.2-SNAPSHOT + + 4.0.0 + com.alibaba.otter + client-adapter.rdb + jar + canal client adapter rdb module for otter ${project.version} + + + + com.alibaba.otter + client-adapter.common + ${project.version} + provided + + + org.yaml + snakeyaml + 1.19 + provided + + + + mysql + mysql-connector-java + 5.1.40 + test + + + org.postgresql + postgresql + 42.1.4 + test + + + com.oracle + ojdbc6 + 11.2.0.4.0-atlassian-hosted + test + + + com.microsoft.sqlserver + mssql-jdbc + 7.0.0.jre8 + test + + + junit + junit + 4.12 + test + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + \ No newline at end of file diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java new file mode 100644 index 00000000..d8fb9bcb --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -0,0 +1,86 @@ +package com.alibaba.otter.canal.client.adapter.rdb; + +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfigLoader; +import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; +import com.alibaba.otter.canal.client.adapter.support.SPI; + +@SPI("rdb") +public class RdbAdapter implements OuterAdapter { + + private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); + + private static volatile Map rdbMapping = null; // 文件名对应配置 + private static volatile Map mappingConfigCache = null; // 库名-表名对应配置 + + private DruidDataSource dataSource; + + private RdbSyncService rdbSyncService; + + @Override + public void init(OuterAdapterConfig configuration) { + if (mappingConfigCache == null) { + synchronized (MappingConfig.class) { + if (mappingConfigCache == null) { + rdbMapping = MappingConfigLoader.load(); + mappingConfigCache = new HashMap<>(); + for (MappingConfig mappingConfig : rdbMapping.values()) { + mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getDbMapping().getDatabase() + "." + + mappingConfig.getDbMapping().getTable(), + mappingConfig); + } + } + } + } + + Map properties = configuration.getProperties(); + dataSource = new DruidDataSource(); + dataSource.setDriverClassName(properties.get("jdbc.driverClassName")); + dataSource.setUrl(properties.get("jdbc.url")); + dataSource.setUsername(properties.get("jdbc.username")); + dataSource.setPassword(properties.get("jdbc.password")); + dataSource.setInitialSize(1); + dataSource.setMinIdle(1); + dataSource.setMaxActive(2); + dataSource.setMaxWait(60000); + dataSource.setTimeBetweenEvictionRunsMillis(60000); + dataSource.setMinEvictableIdleTimeMillis(300000); + + try { + dataSource.init(); + } catch (SQLException e) { + logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e); + } + + rdbSyncService = new RdbSyncService(dataSource); + } + + @Override + public void sync(Dml dml) { + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + String table = dml.getTable(); + MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); + rdbSyncService.sync(config, dml); + } + + @Override + public void destroy() { + if (dataSource != null) { + dataSource.close(); + } + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java new file mode 100644 index 00000000..d0709db6 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -0,0 +1,142 @@ +package com.alibaba.otter.canal.client.adapter.rdb.config; + +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * RDB表映射配置 + * + * @author rewerma 2018-11-07 下午02:41:34 + * @version 1.0.0 + */ +public class MappingConfig { + + private String dataSourceKey; // 数据源key + + private String destination; // canal实例或MQ的topic + + private DbMapping dbMapping; // db映射配置 + + public String getDataSourceKey() { + return dataSourceKey; + } + + public void setDataSourceKey(String dataSourceKey) { + this.dataSourceKey = dataSourceKey; + } + + public DbMapping getDbMapping() { + return dbMapping; + } + + public void setDbMapping(DbMapping dbMapping) { + this.dbMapping = dbMapping; + } + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public void validate() { + if (dbMapping.database == null || dbMapping.database.isEmpty()) { + throw new NullPointerException("dbMapping.database"); + } + if (dbMapping.table == null || dbMapping.table.isEmpty()) { + throw new NullPointerException("dbMapping.table"); + } + if (dbMapping.targetTable == null || dbMapping.targetTable.isEmpty()) { + throw new NullPointerException("dbMapping.targetTable"); + } + } + + public static class DbMapping { + + private String database; // 数据库名或schema名 + private String table; // 表面名 + private boolean mapAll = false; // 映射所有字段 + private String targetTable; // 目标表名 + private Map targetColums; // 目标表字段映射 + private String etlCondition; // etl条件sql + + private Set families = new LinkedHashSet<>(); // column family列表 + private int readBatch = 5000; + private int commitBatch = 5000; // etl等批量提交大小 + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public boolean isMapAll() { + return mapAll; + } + + public void setMapAll(boolean mapAll) { + this.mapAll = mapAll; + } + + public String getTargetTable() { + return targetTable; + } + + public void setTargetTable(String targetTable) { + this.targetTable = targetTable; + } + + public Map getTargetColums() { + return targetColums; + } + + public void setTargetColums(Map targetColums) { + this.targetColums = targetColums; + } + + public String getEtlCondition() { + return etlCondition; + } + + public void setEtlCondition(String etlCondition) { + this.etlCondition = etlCondition; + } + + public Set getFamilies() { + return families; + } + + public void setFamilies(Set families) { + this.families = families; + } + + public int getReadBatch() { + return readBatch; + } + + public void setReadBatch(int readBatch) { + this.readBatch = readBatch; + } + + public int getCommitBatch() { + return commitBatch; + } + + public void setCommitBatch(int commitBatch) { + this.commitBatch = commitBatch; + } + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java new file mode 100644 index 00000000..8d3613a8 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java @@ -0,0 +1,102 @@ +package com.alibaba.otter.canal.client.adapter.rdb.config; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; + +/** + * RDB表映射配置加载器 + * + * @author rewerma 2018-11-07 下午02:41:34 + * @version 1.0.0 + */ +public class MappingConfigLoader { + + private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class); + + private static final String BASE_PATH = "rdb"; + + /** + * 加载HBase表映射配置 + * + * @return 配置名/配置文件名--对象 + */ + public static Map load() { + logger.info("## Start loading rdb mapping config ... "); + + Map result = new LinkedHashMap<>(); + + Collection configs = AdapterConfigs.get("rdb"); + if (configs == null) { + return result; + } + for (String c : configs) { + if (c == null) { + continue; + } + c = c.trim(); + if (c.equals("") || c.startsWith("#")) { + continue; + } + + String configContent = null; + + if (c.endsWith(".yml")) { + configContent = readConfigContent(BASE_PATH + "/" + c); + } + + MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); + + try { + config.validate(); + } catch (Exception e) { + throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e); + } + result.put(c, config); + } + + logger.info("## Rdb mapping config loaded"); + return result; + } + + public static String readConfigContent(String config) { + InputStream in = null; + try { + // 先取本地文件,再取类路径 + File configFile = new File("../config/" + config); + if (configFile.exists()) { + in = new FileInputStream(configFile); + } else { + in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config); + } + if (in == null) { + throw new RuntimeException("Rdb mapping config file not found."); + } + + byte[] bytes = new byte[in.available()]; + in.read(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Read rdb mapping config error. ", e); + } finally { + try { + if (in != null) { + in.close(); + } + } catch (IOException e) { + // ignore + } + } + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java new file mode 100644 index 00000000..9012b6d5 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -0,0 +1,368 @@ +package com.alibaba.otter.canal.client.adapter.rdb.service; + +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.*; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; + +import javax.sql.DataSource; + +import org.joda.time.DateTime; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.fastjson.JSON; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; +import com.alibaba.otter.canal.client.adapter.support.Dml; + +/** + * RDB同步操作业务 + * + * @author rewerma 2018-11-7 下午06:45:49 + * @version 1.0.0 + */ +public class RdbSyncService { + + private static Logger logger = LoggerFactory.getLogger(RdbSyncService.class); + + private DataSource dataSource; + + public RdbSyncService(DataSource dataSource){ + this.dataSource = dataSource; + } + + public void sync(MappingConfig config, Dml dml) { + try { + if (config != null) { + String type = dml.getType(); + if (type != null && type.equalsIgnoreCase("INSERT")) { + insert(config, dml); + } else if (type != null && type.equalsIgnoreCase("UPDATE")) { + // update(config, dml); + } else if (type != null && type.equalsIgnoreCase("DELETE")) { + // delete(config, dml); + } + if (logger.isDebugEnabled()) { + logger.debug("DML: {}", JSON.toJSONString(dml)); + } + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + /** + * 插入操作 + * + * @param config 配置项 + * @param dml DML数据 + */ + private void insert(MappingConfig config, Dml dml) throws SQLException { + List> data = dml.getData(); + if (data == null || data.isEmpty()) { + return; + } + + DbMapping dbMapping = config.getDbMapping(); + + int idx = 1; + boolean complete = false; + + Connection conn = dataSource.getConnection(); + + conn.setAutoCommit(false); + + StringBuilder insertSql = new StringBuilder(); + insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); + if (!dbMapping.isMapAll()) { + dbMapping.getTargetColums().forEach((targetColumnName, srcColumnName) -> { + insertSql.append(targetColumnName).append(","); + }); + int len = insertSql.length(); + insertSql.delete(len - 1, len).append(") VALUES ("); + int mapLen = dbMapping.getTargetColums().size(); + for (int i = 0; i < mapLen; i++) { + insertSql.append("?,"); + } + len = insertSql.length(); + insertSql.delete(len - 1, len).append(")"); + } + + PreparedStatement pstmt = conn.prepareStatement(insertSql.toString()); + + for (Map r : data) { + pstmt.clearParameters(); + convertData2DbRow(conn, config, r, pstmt); + + pstmt.execute(); + + if (idx % config.getDbMapping().getCommitBatch() == 0) { + conn.commit(); + complete = true; + } + idx++; + } + if (!complete) { + conn.commit(); + } + conn.close(); + } + + private static void sqlRS(Connection conn, String sql, Consumer consumer) { + Statement stmt = null; + ResultSet rs = null; + try { + stmt = conn.createStatement(); + rs = stmt.executeQuery(sql); + consumer.accept(rs); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } finally { + if (rs != null) { + try { + rs.close(); + rs = null; + } catch (SQLException e) { + // ignore + } + } + if (stmt != null) { + try { + stmt.close(); + stmt = null; + } catch (SQLException e) { + // ignore + } + } + } + } + + private static Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); + + private Map getTargetColumnType(Connection conn, MappingConfig config) { + DbMapping dbMapping = config.getDbMapping(); + String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable(); + Map columnType = COLUMNS_TYPE_CACHE.get(cacheKey); + if (columnType == null) { + synchronized (RdbSyncService.class) { + columnType = COLUMNS_TYPE_CACHE.get(cacheKey); + if (columnType == null) { + columnType = new LinkedHashMap<>(); + final Map columnTypeTmp = columnType; + String sql = "SELECT * FROM " + dbMapping.getTargetTable() + " WHERE 1=2"; + sqlRS(conn, sql, rs -> { + try { + ResultSetMetaData rsd = rs.getMetaData(); + int columnCount = rsd.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + columnTypeTmp.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i)); + } + COLUMNS_TYPE_CACHE.put(cacheKey, columnTypeTmp); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + }); + } + } + } + return columnType; + } + + /** + * 新增类型转换 + * + * @param config + * @param data + * @param pstmt + * @throws SQLException + */ + private void convertData2DbRow(Connection conn, MappingConfig config, Map data, + PreparedStatement pstmt) throws SQLException { + DbMapping dbMapping = config.getDbMapping(); + if (dbMapping.isMapAll()) { + + } else { + Map ctype = getTargetColumnType(conn, config); + + int i = 1; + for (Map.Entry entry : dbMapping.getTargetColums().entrySet()) { + String targetClassName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetClassName; + } + + Integer type = ctype.get(targetClassName.toLowerCase()); + + Object value = data.get(srcColumnName); + if (value != null) { + if (type == null) { + throw new RuntimeException("No column: " + targetClassName + " found in target db"); + } + switch (type) { + case Types.BIT: + case Types.BOOLEAN: + if (value instanceof Boolean) { + pstmt.setBoolean(i, (Boolean) value); + } else if (value instanceof String) { + boolean v = !value.equals("0"); + pstmt.setBoolean(i, v); + } else if (value instanceof Number) { + boolean v = ((Number) value).intValue() != 0; + pstmt.setBoolean(i, v); + } else { + pstmt.setNull(i, type); + } + break; + case Types.CHAR: + case Types.NCHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + pstmt.setString(i, value.toString()); + break; + case Types.TINYINT: + if (value instanceof Number) { + pstmt.setByte(i, ((Number) value).byteValue()); + } else if (value instanceof String) { + pstmt.setByte(i, Byte.parseByte((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.SMALLINT: + if (value instanceof Number) { + pstmt.setShort(i, ((Number) value).shortValue()); + } else if (value instanceof String) { + pstmt.setShort(i, Short.parseShort((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.INTEGER: + if (value instanceof Number) { + pstmt.setInt(i, ((Number) value).intValue()); + } else if (value instanceof String) { + pstmt.setInt(i, Integer.parseInt((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.BIGINT: + if (value instanceof Number) { + pstmt.setLong(i, ((Number) value).longValue()); + } else if (value instanceof String) { + pstmt.setLong(i, Long.parseLong((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.DECIMAL: + case Types.NUMERIC: + pstmt.setBigDecimal(i, new BigDecimal(value.toString())); + break; + case Types.REAL: + if (value instanceof Number) { + pstmt.setFloat(i, ((Number) value).floatValue()); + } else if (value instanceof String) { + pstmt.setFloat(i, Float.parseFloat((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.FLOAT: + case Types.DOUBLE: + if (value instanceof Number) { + pstmt.setDouble(i, ((Number) value).doubleValue()); + } else if (value instanceof String) { + pstmt.setDouble(i, Double.parseDouble((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BLOB: + + if (value instanceof byte[]) { + pstmt.setBytes(i, (byte[]) value); + } else if (value instanceof String) { + pstmt.setBytes(i, ((String) value).getBytes(StandardCharsets.ISO_8859_1)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.CLOB: + if (value instanceof byte[]) { + pstmt.setBytes(i, (byte[]) value); + } else if (value instanceof String) { + Reader clobReader = new StringReader((String) value); + pstmt.setCharacterStream(i, clobReader); + } else { + pstmt.setNull(i, type); + } + break; + case Types.DATE: + if (value instanceof Date) { + pstmt.setDate(i, new java.sql.Date(((Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + if (!v.startsWith("0000-00-00")) { + v = v.trim().replace(" ", "T"); + DateTime dt = new DateTime(v); + pstmt.setDate(i, new java.sql.Date(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + } else { + pstmt.setNull(i, type); + } + break; + case Types.TIME: + if (value instanceof Date) { + pstmt.setTime(i, new java.sql.Time(((Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + v = "T" + v; + DateTime dt = new DateTime(v); + pstmt.setTime(i, new Time(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + break; + case Types.TIMESTAMP: + if (value instanceof Date) { + pstmt.setTimestamp(i, new java.sql.Timestamp(((Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + if (!v.startsWith("0000-00-00")) { + v = v.trim().replace(" ", "T"); + DateTime dt = new DateTime(v); + pstmt.setTimestamp(i, new Timestamp(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + } else { + pstmt.setNull(i, type); + } + break; + default: + pstmt.setObject(i, value, type); + } + + } else { + pstmt.setNull(i, type); + } + i++; + } + } + } +} diff --git a/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter b/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter new file mode 100644 index 00000000..0a840275 --- /dev/null +++ b/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter @@ -0,0 +1 @@ +rdb=com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter \ No newline at end of file diff --git a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml new file mode 100644 index 00000000..ff6058b0 --- /dev/null +++ b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml @@ -0,0 +1,12 @@ +dataSourceKey: defaultDS +destination: example +dbMapping: + database: mytest + table: user + targetTable: mytest.tb_user + commitBatch: 3000 + mapAll: false + columns: + id: + name: + role_id: \ No newline at end of file diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java new file mode 100644 index 00000000..ace59b55 --- /dev/null +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java @@ -0,0 +1,29 @@ +package com.alibaba.otter.canal.client.adapter.rdb.test; + +import java.util.Map; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfigLoader; +import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; + +public class ConfigLoadTest { + + @Before + public void before() { + AdapterConfigs.put("rdb", "mytest_user.yml"); + // 加载数据源连接池 + DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); + } + + @Test + public void testLoad() { + Map configMap = MappingConfigLoader.load(); + + Assert.assertFalse(configMap.isEmpty()); + } +} diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java new file mode 100644 index 00000000..54e6c447 --- /dev/null +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java @@ -0,0 +1,82 @@ +package com.alibaba.otter.canal.client.adapter.rdb.test; + +import java.io.BufferedReader; +import java.io.Reader; +import java.math.BigDecimal; +import java.sql.*; + +import org.junit.Test; + +import com.alibaba.druid.pool.DruidDataSource; + +public class DBTest { + + @Test + public void test01() throws SQLException { + DruidDataSource dataSource = new DruidDataSource(); + // dataSource.setDriverClassName("oracle.jdbc.OracleDriver"); + // dataSource.setUrl("jdbc:oracle:thin:@127.0.0.1:49161:XE"); + // dataSource.setUsername("mytest"); + // dataSource.setPassword("m121212"); + + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true"); + dataSource.setUsername("root"); + dataSource.setPassword("121212"); + + dataSource.setInitialSize(1); + dataSource.setMinIdle(1); + dataSource.setMaxActive(2); + dataSource.setMaxWait(60000); + dataSource.setTimeBetweenEvictionRunsMillis(60000); + dataSource.setMinEvictableIdleTimeMillis(300000); + + dataSource.init(); + + Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("select * from user t where 1=2"); + + ResultSetMetaData rsm = rs.getMetaData(); + int cnt = rsm.getColumnCount(); + for (int i = 1; i <= cnt; i++) { + System.out.println(rsm.getColumnName(i) + " " + rsm.getColumnType(i)); + } + + rs.close(); + stmt.close(); + +// PreparedStatement pstmt = conn +// .prepareStatement("insert into tb_user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)"); +// pstmt.setBigDecimal(1, new BigDecimal("5")); +// pstmt.setString(2, "test"); +// pstmt.setBigDecimal(3, new BigDecimal("1")); +// pstmt.setDate(4, new Date(new java.util.Date().getTime())); +// byte[] a = { (byte) 1, (byte) 2 }; +// pstmt.setBytes(5, a); +// pstmt.setBytes(6, a); +// pstmt.execute(); +// +// pstmt.close(); + + conn.close(); + dataSource.close(); + } + + private String clob2Str(Clob clob) { + String content = ""; + try { + Reader is = clob.getCharacterStream(); + BufferedReader buff = new BufferedReader(is); + String line = buff.readLine(); + StringBuffer sb = new StringBuffer(); + while (line != null) { + sb.append(line); + line = buff.readLine(); + } + content = sb.toString(); + } catch (Exception e) { + } + return content; + } +} diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/TestConstant.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/TestConstant.java new file mode 100644 index 00000000..813f0e85 --- /dev/null +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/TestConstant.java @@ -0,0 +1,37 @@ +package com.alibaba.otter.canal.client.adapter.rdb.test; + +import java.sql.SQLException; + +import com.alibaba.druid.pool.DruidDataSource; + +public class TestConstant { + + public final static String jdbcUrl = "jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true"; + public final static String jdbcUser = "root"; + public final static String jdbcPassword = "121212"; + + public static DruidDataSource dataSource; + + static { + dataSource = new DruidDataSource(); + dataSource.setDriverClassName("com.mysql.jdbc.Driver"); + dataSource.setUrl(jdbcUrl); + dataSource.setUsername(jdbcUser); + dataSource.setPassword(jdbcPassword); + dataSource.setInitialSize(1); + dataSource.setMinIdle(1); + dataSource.setMaxActive(1); + dataSource.setMaxWait(60000); + dataSource.setTimeBetweenEvictionRunsMillis(60000); + dataSource.setMinEvictableIdleTimeMillis(300000); + dataSource.setPoolPreparedStatements(false); + dataSource.setMaxPoolPreparedStatementPerConnectionSize(20); + dataSource.setValidationQuery("select 1"); + try { + dataSource.init(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + +} diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java new file mode 100644 index 00000000..3e7917de --- /dev/null +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java @@ -0,0 +1,28 @@ +package com.alibaba.otter.canal.client.adapter.rdb.test.sync; + +import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.rdb.test.TestConstant; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; + +import java.util.HashMap; +import java.util.Map; + +public class Common { + public static RdbAdapter init() { + DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); + + OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig(); + outerAdapterConfig.setName("rdb"); + Map properties = new HashMap<>(); + properties.put("jdbc.driveClassName", "oracle.jdbc.OracleDriver"); + properties.put("jdbc.url", "jdbc:oracle:thin:@127.0.0.1:49161:XE"); + properties.put("jdbc.username", "mytest"); + properties.put("jdbc.password", "m121212"); + outerAdapterConfig.setProperties(properties); + + RdbAdapter adapter = new RdbAdapter(); + adapter.init(outerAdapterConfig); + return adapter; + } +} diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java new file mode 100644 index 00000000..3324e41c --- /dev/null +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java @@ -0,0 +1,47 @@ +package com.alibaba.otter.canal.client.adapter.rdb.test.sync; + +import java.io.BufferedReader; +import java.io.Reader; +import java.sql.Clob; +import java.util.*; + +import org.junit.Before; +import org.junit.Test; + +import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.support.Dml; + +public class OracleSyncTest { + + private RdbAdapter rdbAdapter; + + @Before + public void init() { + AdapterConfigs.put("rdb", "mytest_user.yml"); + rdbAdapter = Common.init(); + } + + @Test + public void test01() { + Dml dml = new Dml(); + dml.setDestination("example"); + dml.setTs(new Date().getTime()); + dml.setType("INSERT"); + dml.setDatabase("mytest"); + dml.setTable("user"); + List> dataList = new ArrayList<>(); + Map data = new LinkedHashMap<>(); + dataList.add(data); + data.put("id", 1L); + data.put("name", "Eric"); + data.put("role_id", 1L); + data.put("c_time", new Date()); + data.put("test1", "sdfasdfawe中国asfwef"); + dml.setData(dataList); + + rdbAdapter.sync(dml); + } + + +} diff --git a/client-adapter/rdb/src/test/resources/log4j2-test.xml b/client-adapter/rdb/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000..9c3d2ee6 --- /dev/null +++ b/client-adapter/rdb/src/test/resources/log4j2-test.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/client-adapter/rdb/src/test/resources/logback-test.xml b/client-adapter/rdb/src/test/resources/logback-test.xml new file mode 100644 index 00000000..91f0de80 --- /dev/null +++ b/client-adapter/rdb/src/test/resources/logback-test.xml @@ -0,0 +1,13 @@ + + + + + %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml new file mode 100644 index 00000000..1f111b79 --- /dev/null +++ b/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml @@ -0,0 +1,14 @@ +dataSourceKey: defaultDS +destination: example +dbMapping: + database: mytest + table: user + targetTable: mytest.tb_user + commitBatch: 3000 + mapAll: false + targetColums: + id: + name: + role_id: + c_time: + test1: \ No newline at end of file From f7ea8df89e618c1d50844ad7c439759077df6314 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 8 Nov 2018 11:19:50 +0800 Subject: [PATCH 292/360] =?UTF-8?q?rdb=E6=96=B0=E5=A2=9E=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/rdb/config/MappingConfig.java | 37 +- .../adapter/rdb/service/RdbSyncService.java | 545 +++++++++++------- .../src/main/resources/rdb/mytest_user.yml | 2 +- .../src/test/resources/rdb/mytest_user.yml | 14 +- .../otter/canal/kafka/CanalKafkaProducer.java | 4 +- .../canal/rocketmq/CanalRocketMQProducer.java | 9 +- 6 files changed, 367 insertions(+), 244 deletions(-) diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index d0709db6..f7113d7f 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -56,16 +56,19 @@ public class MappingConfig { public static class DbMapping { - private String database; // 数据库名或schema名 - private String table; // 表面名 - private boolean mapAll = false; // 映射所有字段 - private String targetTable; // 目标表名 - private Map targetColums; // 目标表字段映射 - private String etlCondition; // etl条件sql + private String database; // 数据库名或schema名 + private String table; // 表面名 + private boolean mapAll = false; // 映射所有字段 + private String targetTable; // 目标表名 + private Map targetColumns; // 目标表字段映射 - private Set families = new LinkedHashSet<>(); // column family列表 - private int readBatch = 5000; - private int commitBatch = 5000; // etl等批量提交大小 + private String etlCondition; // etl条件sql + + private Set families = new LinkedHashSet<>(); // column family列表 + private int readBatch = 5000; + private int commitBatch = 5000; // etl等批量提交大小 + + private volatile Map allColumns; // mapAll为true,自动设置改字段 public String getDatabase() { return database; @@ -99,12 +102,12 @@ public class MappingConfig { this.targetTable = targetTable; } - public Map getTargetColums() { - return targetColums; + public Map getTargetColumns() { + return targetColumns; } - public void setTargetColums(Map targetColums) { - this.targetColums = targetColums; + public void setTargetColumns(Map targetColumns) { + this.targetColumns = targetColumns; } public String getEtlCondition() { @@ -138,5 +141,13 @@ public class MappingConfig { public void setCommitBatch(int commitBatch) { this.commitBatch = commitBatch; } + + public Map getAllColumns() { + return allColumns; + } + + public void setAllColumns(Map allColumns) { + this.allColumns = allColumns; + } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 9012b6d5..a6b46fd9 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -5,7 +5,7 @@ import java.io.StringReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.sql.*; -import java.util.Date; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; /** @@ -31,9 +32,12 @@ import com.alibaba.otter.canal.client.adapter.support.Dml; */ public class RdbSyncService { - private static Logger logger = LoggerFactory.getLogger(RdbSyncService.class); + private static final Logger logger = LoggerFactory + .getLogger(RdbSyncService.class); - private DataSource dataSource; + private static final Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); + + private DataSource dataSource; public RdbSyncService(DataSource dataSource){ this.dataSource = dataSource; @@ -42,6 +46,52 @@ public class RdbSyncService { public void sync(MappingConfig config, Dml dml) { try { if (config != null) { + { + DbMapping dbMapping = config.getDbMapping(); + // 从源表加载所有字段名 + if (dbMapping.getAllColumns() == null) { + synchronized (RdbSyncService.class) { + if (dbMapping.getAllColumns() == null) { + DataSource srcDS = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); + Connection srcConn = srcDS.getConnection(); + String srcMetaSql = "SELECT * FROM " + dbMapping.getDatabase() + "." + + dbMapping.getTable() + " WHERE 1=2 "; + List srcColumns = new ArrayList<>(); + sqlRS(srcConn, srcMetaSql, rs -> { + try { + ResultSetMetaData rmd = rs.getMetaData(); + int cnt = rmd.getColumnCount(); + for (int i = 1; i <= cnt; i++) { + srcColumns.add(rmd.getColumnName(i).toLowerCase()); + } + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + }); + Map columnsMap = new LinkedHashMap<>(); + + for (String srcColumn : srcColumns) { + String targetColumn = srcColumn; + if (dbMapping.getTargetColumns() != null) { + for (Map.Entry entry : dbMapping.getTargetColumns() + .entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + + if (srcColumnName != null + && srcColumnName.toLowerCase().equals(srcColumn.toUpperCase())) { + targetColumn = targetColumnName; + } + } + } + columnsMap.put(targetColumn, srcColumn); + } + dbMapping.setAllColumns(columnsMap); + } + } + } + } + String type = dml.getType(); if (type != null && type.equalsIgnoreCase("INSERT")) { insert(config, dml); @@ -59,6 +109,72 @@ public class RdbSyncService { } } + private void update(MappingConfig config, Dml dml) throws SQLException { + List> data = dml.getData(); + if (data == null || data.isEmpty()) { + return; + } + + List> old = dml.getOld(); + if (old == null || old.isEmpty()) { + return; + } + + DbMapping dbMapping = config.getDbMapping(); + + int idx = 1; + boolean complete = false; + + Connection conn = dataSource.getConnection(); + boolean oriAutoCommit = conn.getAutoCommit(); + conn.setAutoCommit(false); + + try { + Map columnsMap; + if (dbMapping.isMapAll()) { + columnsMap = dbMapping.getAllColumns(); + } else { + columnsMap = dbMapping.getTargetColumns(); + } + + int i = 0; + for (Map o : old) { + Map d = data.get(i); + StringBuilder updateSql = new StringBuilder(); + updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); + List values = new ArrayList<>(); + boolean flag = false; + for (String srcColumnName : o.keySet()) { + List targetColumnNames = new ArrayList<>(); + columnsMap.forEach((targetColumn, srcColumn) -> { + if (srcColumnName.toLowerCase().equals(srcColumn)) { + targetColumnNames.add(targetColumn); + } + }); + if (!targetColumnNames.isEmpty()) { + if (!flag) { + flag = true; + } + for (String targetColumnName : targetColumnNames) { + updateSql.append(targetColumnName).append("=?, "); + values.add(data.get(i)); + } + } + } + if (flag) { + int len = updateSql.length(); + updateSql.delete(len - 2, len).append(" WHERE "); + } + i++; + } + } catch (Exception e) { + conn.rollback(); + } finally { + conn.setAutoCommit(oriAutoCommit); + conn.close(); + } + } + /** * 插入操作 * @@ -74,79 +190,68 @@ public class RdbSyncService { DbMapping dbMapping = config.getDbMapping(); int idx = 1; - boolean complete = false; + boolean completed = false; Connection conn = dataSource.getConnection(); - + boolean oriAutoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); + try { + Map columnsMap; + if (dbMapping.isMapAll()) { + columnsMap = dbMapping.getAllColumns(); + } else { + columnsMap = dbMapping.getTargetColumns(); + } - StringBuilder insertSql = new StringBuilder(); - insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); - if (!dbMapping.isMapAll()) { - dbMapping.getTargetColums().forEach((targetColumnName, srcColumnName) -> { - insertSql.append(targetColumnName).append(","); - }); + StringBuilder insertSql = new StringBuilder(); + insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); + + columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); int len = insertSql.length(); insertSql.delete(len - 1, len).append(") VALUES ("); - int mapLen = dbMapping.getTargetColums().size(); + int mapLen = columnsMap.size(); for (int i = 0; i < mapLen; i++) { insertSql.append("?,"); } len = insertSql.length(); insertSql.delete(len - 1, len).append(")"); - } - PreparedStatement pstmt = conn.prepareStatement(insertSql.toString()); + PreparedStatement pstmt = conn.prepareStatement(insertSql.toString()); - for (Map r : data) { - pstmt.clearParameters(); - convertData2DbRow(conn, config, r, pstmt); + for (Map r : data) { + pstmt.clearParameters(); + convertData2DbRow(conn, config, r, pstmt); - pstmt.execute(); + pstmt.execute(); + if (logger.isTraceEnabled()) { + logger.trace("Insert into target db, sql: {}", insertSql); + } - if (idx % config.getDbMapping().getCommitBatch() == 0) { - conn.commit(); - complete = true; + if (idx % config.getDbMapping().getCommitBatch() == 0) { + conn.commit(); + completed = true; + } + idx++; } - idx++; + if (!completed) { + conn.commit(); + } + } catch (Exception e) { + conn.rollback(); + } finally { + conn.setAutoCommit(oriAutoCommit); + conn.close(); } - if (!complete) { - conn.commit(); - } - conn.close(); } private static void sqlRS(Connection conn, String sql, Consumer consumer) { - Statement stmt = null; - ResultSet rs = null; - try { - stmt = conn.createStatement(); - rs = stmt.executeQuery(sql); + try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { consumer.accept(rs); } catch (SQLException e) { logger.error(e.getMessage(), e); - } finally { - if (rs != null) { - try { - rs.close(); - rs = null; - } catch (SQLException e) { - // ignore - } - } - if (stmt != null) { - try { - stmt.close(); - stmt = null; - } catch (SQLException e) { - // ignore - } - } } } - private static Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); - private Map getTargetColumnType(Connection conn, MappingConfig config) { DbMapping dbMapping = config.getDbMapping(); String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable(); @@ -187,182 +292,188 @@ public class RdbSyncService { private void convertData2DbRow(Connection conn, MappingConfig config, Map data, PreparedStatement pstmt) throws SQLException { DbMapping dbMapping = config.getDbMapping(); + Map columnsMap; if (dbMapping.isMapAll()) { - + columnsMap = dbMapping.getAllColumns(); } else { - Map ctype = getTargetColumnType(conn, config); + columnsMap = dbMapping.getTargetColumns(); + } + Map ctype = getTargetColumnType(conn, config); - int i = 1; - for (Map.Entry entry : dbMapping.getTargetColums().entrySet()) { - String targetClassName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetClassName; + int i = 1; + for (Map.Entry entry : columnsMap.entrySet()) { + String targetClassName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetClassName; + } + + Integer type = ctype.get(targetClassName.toLowerCase()); + + Object value = data.get(srcColumnName); + if (value != null) { + if (type == null) { + throw new RuntimeException("No column: " + targetClassName + " found in target db"); } - Integer type = ctype.get(targetClassName.toLowerCase()); - - Object value = data.get(srcColumnName); - if (value != null) { - if (type == null) { - throw new RuntimeException("No column: " + targetClassName + " found in target db"); - } - switch (type) { - case Types.BIT: - case Types.BOOLEAN: - if (value instanceof Boolean) { - pstmt.setBoolean(i, (Boolean) value); - } else if (value instanceof String) { - boolean v = !value.equals("0"); - pstmt.setBoolean(i, v); - } else if (value instanceof Number) { - boolean v = ((Number) value).intValue() != 0; - pstmt.setBoolean(i, v); - } else { - pstmt.setNull(i, type); - } - break; - case Types.CHAR: - case Types.NCHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - pstmt.setString(i, value.toString()); - break; - case Types.TINYINT: - if (value instanceof Number) { - pstmt.setByte(i, ((Number) value).byteValue()); - } else if (value instanceof String) { - pstmt.setByte(i, Byte.parseByte((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.SMALLINT: - if (value instanceof Number) { - pstmt.setShort(i, ((Number) value).shortValue()); - } else if (value instanceof String) { - pstmt.setShort(i, Short.parseShort((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.INTEGER: - if (value instanceof Number) { - pstmt.setInt(i, ((Number) value).intValue()); - } else if (value instanceof String) { - pstmt.setInt(i, Integer.parseInt((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.BIGINT: - if (value instanceof Number) { - pstmt.setLong(i, ((Number) value).longValue()); - } else if (value instanceof String) { - pstmt.setLong(i, Long.parseLong((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.DECIMAL: - case Types.NUMERIC: - pstmt.setBigDecimal(i, new BigDecimal(value.toString())); - break; - case Types.REAL: - if (value instanceof Number) { - pstmt.setFloat(i, ((Number) value).floatValue()); - } else if (value instanceof String) { - pstmt.setFloat(i, Float.parseFloat((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.FLOAT: - case Types.DOUBLE: - if (value instanceof Number) { - pstmt.setDouble(i, ((Number) value).doubleValue()); - } else if (value instanceof String) { - pstmt.setDouble(i, Double.parseDouble((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - case Types.BLOB: - - if (value instanceof byte[]) { - pstmt.setBytes(i, (byte[]) value); - } else if (value instanceof String) { - pstmt.setBytes(i, ((String) value).getBytes(StandardCharsets.ISO_8859_1)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.CLOB: - if (value instanceof byte[]) { - pstmt.setBytes(i, (byte[]) value); - } else if (value instanceof String) { - Reader clobReader = new StringReader((String) value); - pstmt.setCharacterStream(i, clobReader); - } else { - pstmt.setNull(i, type); - } - break; - case Types.DATE: - if (value instanceof Date) { - pstmt.setDate(i, new java.sql.Date(((Date) value).getTime())); - } else if (value instanceof String) { - String v = (String) value; - if (!v.startsWith("0000-00-00")) { - v = v.trim().replace(" ", "T"); - DateTime dt = new DateTime(v); - pstmt.setDate(i, new java.sql.Date(dt.toDate().getTime())); - } else { - pstmt.setNull(i, type); - } - } else { - pstmt.setNull(i, type); - } - break; - case Types.TIME: - if (value instanceof Date) { - pstmt.setTime(i, new java.sql.Time(((Date) value).getTime())); - } else if (value instanceof String) { - String v = (String) value; - v = "T" + v; - DateTime dt = new DateTime(v); - pstmt.setTime(i, new Time(dt.toDate().getTime())); - } else { - pstmt.setNull(i, type); - } - break; - case Types.TIMESTAMP: - if (value instanceof Date) { - pstmt.setTimestamp(i, new java.sql.Timestamp(((Date) value).getTime())); - } else if (value instanceof String) { - String v = (String) value; - if (!v.startsWith("0000-00-00")) { - v = v.trim().replace(" ", "T"); - DateTime dt = new DateTime(v); - pstmt.setTimestamp(i, new Timestamp(dt.toDate().getTime())); - } else { - pstmt.setNull(i, type); - } - } else { - pstmt.setNull(i, type); - } - break; - default: - pstmt.setObject(i, value, type); - } + setPStmt(type, pstmt, value, i); + } else { + pstmt.setNull(i, type); + } + i++; + } + } + private void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException { + switch (type) { + case Types.BIT: + case Types.BOOLEAN: + if (value instanceof Boolean) { + pstmt.setBoolean(i, (Boolean) value); + } else if (value instanceof String) { + boolean v = !value.equals("0"); + pstmt.setBoolean(i, v); + } else if (value instanceof Number) { + boolean v = ((Number) value).intValue() != 0; + pstmt.setBoolean(i, v); } else { pstmt.setNull(i, type); } - i++; - } + break; + case Types.CHAR: + case Types.NCHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + pstmt.setString(i, value.toString()); + break; + case Types.TINYINT: + if (value instanceof Number) { + pstmt.setByte(i, ((Number) value).byteValue()); + } else if (value instanceof String) { + pstmt.setByte(i, Byte.parseByte((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.SMALLINT: + if (value instanceof Number) { + pstmt.setShort(i, ((Number) value).shortValue()); + } else if (value instanceof String) { + pstmt.setShort(i, Short.parseShort((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.INTEGER: + if (value instanceof Number) { + pstmt.setInt(i, ((Number) value).intValue()); + } else if (value instanceof String) { + pstmt.setInt(i, Integer.parseInt((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.BIGINT: + if (value instanceof Number) { + pstmt.setLong(i, ((Number) value).longValue()); + } else if (value instanceof String) { + pstmt.setLong(i, Long.parseLong((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.DECIMAL: + case Types.NUMERIC: + pstmt.setBigDecimal(i, new BigDecimal(value.toString())); + break; + case Types.REAL: + if (value instanceof Number) { + pstmt.setFloat(i, ((Number) value).floatValue()); + } else if (value instanceof String) { + pstmt.setFloat(i, Float.parseFloat((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.FLOAT: + case Types.DOUBLE: + if (value instanceof Number) { + pstmt.setDouble(i, ((Number) value).doubleValue()); + } else if (value instanceof String) { + pstmt.setDouble(i, Double.parseDouble((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BLOB: + + if (value instanceof byte[]) { + pstmt.setBytes(i, (byte[]) value); + } else if (value instanceof String) { + pstmt.setBytes(i, ((String) value).getBytes(StandardCharsets.ISO_8859_1)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.CLOB: + if (value instanceof byte[]) { + pstmt.setBytes(i, (byte[]) value); + } else if (value instanceof String) { + Reader clobReader = new StringReader((String) value); + pstmt.setCharacterStream(i, clobReader); + } else { + pstmt.setNull(i, type); + } + break; + case Types.DATE: + if (value instanceof java.util.Date) { + pstmt.setDate(i, new java.sql.Date(((java.util.Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + if (!v.startsWith("0000-00-00")) { + v = v.trim().replace(" ", "T"); + DateTime dt = new DateTime(v); + pstmt.setDate(i, new Date(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + } else { + pstmt.setNull(i, type); + } + break; + case Types.TIME: + if (value instanceof java.util.Date) { + pstmt.setTime(i, new java.sql.Time(((java.util.Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + v = "T" + v; + DateTime dt = new DateTime(v); + pstmt.setTime(i, new Time(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + break; + case Types.TIMESTAMP: + if (value instanceof java.util.Date) { + pstmt.setTimestamp(i, new java.sql.Timestamp(((java.util.Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + if (!v.startsWith("0000-00-00")) { + v = v.trim().replace(" ", "T"); + DateTime dt = new DateTime(v); + pstmt.setTimestamp(i, new Timestamp(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + } else { + pstmt.setNull(i, type); + } + break; + default: + pstmt.setObject(i, value, type); } } } diff --git a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml index ff6058b0..d7bb4ae8 100644 --- a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml @@ -6,7 +6,7 @@ dbMapping: targetTable: mytest.tb_user commitBatch: 3000 mapAll: false - columns: + targetColumns: id: name: role_id: \ No newline at end of file diff --git a/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml index 1f111b79..09dda295 100644 --- a/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml @@ -5,10 +5,10 @@ dbMapping: table: user targetTable: mytest.tb_user commitBatch: 3000 - mapAll: false - targetColums: - id: - name: - role_id: - c_time: - test1: \ No newline at end of file + mapAll: true +# targetColumns: +# id: +# name: +# role_id: +# c_time: +# test1: \ No newline at end of file diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index a4c9eaa8..78a67c13 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -104,7 +104,7 @@ public class CanalKafkaProducer implements CanalMQProducer { ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), canalDestination.getPartition(), null, - JSON.toJSONString(flatMessage)); + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); producer2.send(record).get(); } catch (Exception e) { logger.error(e.getMessage(), e); @@ -126,7 +126,7 @@ public class CanalKafkaProducer implements CanalMQProducer { ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), i, null, - JSON.toJSONString(flatMessagePart)); + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue)); producer2.send(record).get(); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index d8bd45e4..3501930b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -1,6 +1,7 @@ package com.alibaba.otter.canal.rocketmq; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.common.CanalMessageSerializer; import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -75,11 +76,11 @@ public class CanalRocketMQProducer implements CanalMQProducer { if (destination.getPartition() != null) { try { logger.info("send flat message: {} to topic: {} fixed partition: {}", - JSON.toJSONString(flatMessage), + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue), destination.getTopic(), destination.getPartition()); Message message = new Message(destination.getTopic(), - JSON.toJSONString(flatMessage).getBytes()); + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue).getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override @@ -102,12 +103,12 @@ public class CanalRocketMQProducer implements CanalMQProducer { FlatMessage flatMessagePart = partitionFlatMessage[i]; if (flatMessagePart != null) { logger.debug("flatMessagePart: {}, partition: {}", - JSON.toJSONString(flatMessagePart), + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue), i); final int index = i; try { Message message = new Message(destination.getTopic(), - JSON.toJSONString(flatMessagePart).getBytes()); + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue).getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override From 6fa111866f5b6b5a43fb8de4a652f1bf92eb42cd Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 8 Nov 2018 11:22:34 +0800 Subject: [PATCH 293/360] =?UTF-8?q?flagMessage=20json=20=E5=BA=8F=E5=88=97?= =?UTF-8?q?=E5=8C=96=E6=97=A0null=E5=80=BC=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alibaba/otter/canal/kafka/CanalKafkaProducer.java | 4 ++-- .../otter/canal/rocketmq/CanalRocketMQProducer.java | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index a4c9eaa8..78a67c13 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -104,7 +104,7 @@ public class CanalKafkaProducer implements CanalMQProducer { ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), canalDestination.getPartition(), null, - JSON.toJSONString(flatMessage)); + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); producer2.send(record).get(); } catch (Exception e) { logger.error(e.getMessage(), e); @@ -126,7 +126,7 @@ public class CanalKafkaProducer implements CanalMQProducer { ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), i, null, - JSON.toJSONString(flatMessagePart)); + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue)); producer2.send(record).get(); } catch (Exception e) { logger.error(e.getMessage(), e); diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index d8bd45e4..3501930b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -1,6 +1,7 @@ package com.alibaba.otter.canal.rocketmq; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.common.CanalMessageSerializer; import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -75,11 +76,11 @@ public class CanalRocketMQProducer implements CanalMQProducer { if (destination.getPartition() != null) { try { logger.info("send flat message: {} to topic: {} fixed partition: {}", - JSON.toJSONString(flatMessage), + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue), destination.getTopic(), destination.getPartition()); Message message = new Message(destination.getTopic(), - JSON.toJSONString(flatMessage).getBytes()); + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue).getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override @@ -102,12 +103,12 @@ public class CanalRocketMQProducer implements CanalMQProducer { FlatMessage flatMessagePart = partitionFlatMessage[i]; if (flatMessagePart != null) { logger.debug("flatMessagePart: {}, partition: {}", - JSON.toJSONString(flatMessagePart), + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue), i); final int index = i; try { Message message = new Message(destination.getTopic(), - JSON.toJSONString(flatMessagePart).getBytes()); + JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue).getBytes()); this.defaultMQProducer.send(message, new MessageQueueSelector() { @Override From 2c1f3e649aafef87f2ed7255dba513aa34d07e28 Mon Sep 17 00:00:00 2001 From: "jiangahng.loujh" Date: Thu, 8 Nov 2018 13:50:23 +0800 Subject: [PATCH 294/360] fixed issue #1107 , more info message --- .../otter/canal/parse/inbound/AbstractEventParser.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java index 4d5cf7ec..cd6e25be 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/AbstractEventParser.java @@ -181,6 +181,8 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle serverId = queryServerId; } // 4. 获取最后的位置信息 + long start = System.currentTimeMillis(); + logger.warn("---> begin to find start position, it will be long time for reset or first position"); EntryPosition position = findStartPosition(erosaConnection); final EntryPosition startPosition = position; if (startPosition == null) { @@ -191,7 +193,10 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle throw new CanalParseException("can't find init table meta for " + destination + " with position : " + startPosition); } - logger.warn("find start position : {}", startPosition.toString()); + long end = System.currentTimeMillis(); + logger.warn("---> find start position successfully, {}", startPosition.toString() + " cost : " + + (end - start) + + "ms , the next step is binlog dump"); // 重新链接,因为在找position过程中可能有状态,需要断开后重建 erosaConnection.reconnect(); @@ -417,7 +422,7 @@ public abstract class AbstractEventParser extends AbstractCanalLifeCycle } protected LogPosition buildLastTransactionPosition(List entries) { // 初始化一下 - for (int i = entries.size() - 1; i >= 0; i--) { + for (int i = entries.size() - 1; i > 0; i--) { CanalEntry.Entry entry = entries.get(i); if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) {// 尽量记录一个事务做为position return buildLastPosition(entry); From 0677443ccf9dce72d521aee8eca2113edf5be309 Mon Sep 17 00:00:00 2001 From: "jiangahng.loujh" Date: Thu, 8 Nov 2018 13:57:12 +0800 Subject: [PATCH 295/360] fixed issue #1100 , fix type compare --- .../inbound/mysql/tsdb/DatabaseTableMeta.java | 91 +++++++++++-------- 1 file changed, 55 insertions(+), 36 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index a958dbbe..ad15a695 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -47,32 +47,34 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition; */ public class DatabaseTableMeta implements TableMetaTSDB { - public static final EntryPosition INIT_POSITION = new EntryPosition("0", 0L, -2L, -1L); - private static Logger logger = LoggerFactory.getLogger(DatabaseTableMeta.class); - private static Pattern pattern = Pattern.compile("Duplicate entry '.*' for key '*'"); - private static Pattern h2Pattern = Pattern.compile("Unique index or primary key violation"); - private static ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { - @Override - public Thread newThread(Runnable r) { - Thread thread = new Thread(r, "[scheduler-table-meta-snapshot]"); - thread.setDaemon(true); - return thread; - } - }); - private ReadWriteLock lock = new ReentrantReadWriteLock(); - private String destination; - private MemoryTableMeta memoryTableMeta; - private MysqlConnection connection; // 查询meta信息的链接 - private CanalEventFilter filter; - private CanalEventFilter blackFilter; - private EntryPosition lastPosition; - private boolean hasNewDdl; - private MetaHistoryDAO metaHistoryDAO; - private MetaSnapshotDAO metaSnapshotDAO; - private int snapshotInterval = 24; - private int snapshotExpire = 360; - private ScheduledFuture scheduleSnapshotFuture; - + public static final EntryPosition INIT_POSITION = new EntryPosition("0", 0L, -2L, -1L); + private static Logger logger = LoggerFactory.getLogger(DatabaseTableMeta.class); + private static Pattern pattern = Pattern.compile("Duplicate entry '.*' for key '*'"); + private static Pattern h2Pattern = Pattern.compile("Unique index or primary key violation"); + private static ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, + "[scheduler-table-meta-snapshot]"); + thread.setDaemon(true); + return thread; + } + }); + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private String destination; + private MemoryTableMeta memoryTableMeta; + private MysqlConnection connection; // 查询meta信息的链接 + private CanalEventFilter filter; + private CanalEventFilter blackFilter; + private EntryPosition lastPosition; + private boolean hasNewDdl; + private MetaHistoryDAO metaHistoryDAO; + private MetaSnapshotDAO metaSnapshotDAO; + private int snapshotInterval = 24; + private int snapshotExpire = 360; + private ScheduledFuture scheduleSnapshotFuture; + public DatabaseTableMeta(){ } @@ -109,13 +111,13 @@ public class DatabaseTableMeta implements TableMetaTSDB { } return true; } - + @Override public void destory() { if (memoryTableMeta != null) { memoryTableMeta.destory(); } - + if (connection != null) { try { connection.disconnect(); @@ -124,7 +126,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { .getAddress(), e); } } - + if (scheduleSnapshotFuture != null) { scheduleSnapshotFuture.cancel(false); } @@ -133,9 +135,9 @@ public class DatabaseTableMeta implements TableMetaTSDB { @Override public TableMeta find(String schema, String table) { lock.readLock().lock(); - try{ + try { return memoryTableMeta.find(schema, table); - }finally { + } finally { lock.readLock().unlock(); } } @@ -144,7 +146,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { public boolean apply(EntryPosition position, String schema, String ddl, String extra) { // 首先记录到内存结构 lock.writeLock().lock(); - try{ + try { if (memoryTableMeta.apply(position, schema, ddl, extra)) { this.lastPosition = position; this.hasNewDdl = true; @@ -153,7 +155,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { } else { throw new RuntimeException("apply to memory is failed"); } - }finally { + } finally { lock.writeLock().unlock(); } } @@ -280,14 +282,14 @@ public class DatabaseTableMeta implements TableMetaTSDB { // 获取一份快照 Map schemaDdls = null; lock.readLock().lock(); - try{ + try { if (!init && !hasNewDdl) { // 如果是持续构建,则识别一下是否有DDL变更过,如果没有就忽略了 return false; } this.hasNewDdl = false; schemaDdls = memoryTableMeta.snapshot(); - }finally{ + } finally { lock.readLock().unlock(); } @@ -488,7 +490,24 @@ public class DatabaseTableMeta implements TableMetaTSDB { return false; } - if (!StringUtils.equalsIgnoreCase(sourceField.getColumnType(), targetField.getColumnType())) { + // if (!StringUtils.equalsIgnoreCase(sourceField.getColumnType(), + // targetField.getColumnType())) { + // return false; + // } + + // https://github.com/alibaba/canal/issues/1100 + // 支持一下 int vs int(10) + if ((sourceField.isUnsigned() && !targetField.isUnsigned()) + || (!sourceField.isUnsigned() && targetField.isUnsigned())) { + return false; + } + + boolean columnTypeCompare = false; + columnTypeCompare |= StringUtils.containsIgnoreCase(sourceField.getColumnType(), + targetField.getColumnType()); + columnTypeCompare |= StringUtils.containsIgnoreCase(targetField.getColumnType(), + sourceField.getColumnType()); + if (!columnTypeCompare) { return false; } From 2ac84b4320bd06d8ee97ded033d741fd62920d5a Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 8 Nov 2018 19:34:15 +0800 Subject: [PATCH 296/360] =?UTF-8?q?=E5=85=B3=E7=B3=BB=E5=9E=8B=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=90=8C=E6=AD=A5adapter,=20=E6=94=AF=E6=8C=81=20mysq?= =?UTF-8?q?l=20oracle=20postgresql=20sqlserver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/support/Util.java | 90 ++++ .../adapter/es/service/ESSyncService.java | 3 +- .../hbase/service/HbaseEtlService.java | 44 +- .../hbase/service/HbaseSyncService.java | 3 +- client-adapter/launcher/pom.xml | 13 + .../launcher/src/main/assembly/dev.xml | 7 + .../launcher/src/main/assembly/release.xml | 7 + .../loader/CanalAdapterKafkaWorker.java | 3 +- .../src/main/resources/application.yml | 15 + .../adapter/logger/LoggerAdapterExample.java | 6 +- .../canal/client/adapter/rdb/RdbAdapter.java | 161 ++++++- .../adapter/rdb/adapters/MysqlAdapter.java | 8 + .../adapter/rdb/adapters/OracleAdapter.java | 8 + .../rdb/adapters/PostgresqlAdapter.java | 8 + .../rdb/adapters/SqlserverAdapter.java | 8 + .../adapter/rdb/config/MappingConfig.java | 9 + .../rdb/config/MappingConfigLoader.java | 8 +- .../adapter/rdb/service/RdbEtlService.java | 290 +++++++++++++ .../adapter/rdb/service/RdbSyncService.java | 392 ++++++++++-------- ...ba.otter.canal.client.adapter.OuterAdapter | 5 +- .../resources/oracle}/mytest_user.yml | 3 +- .../main/resources/postgresql/mytest_user.yml | 15 + .../adapter/rdb/test/ConfigLoadTest.java | 4 +- .../client/adapter/rdb/test/sync/Common.java | 5 +- .../adapter/rdb/test/sync/OracleSyncTest.java | 27 +- .../resources/oracle}/mytest_user.yml | 15 +- 26 files changed, 906 insertions(+), 251 deletions(-) create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java rename client-adapter/rdb/src/{test/resources/rdb => main/resources/oracle}/mytest_user.yml (90%) create mode 100644 client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml rename client-adapter/rdb/src/{main/resources/rdb => test/resources/oracle}/mytest_user.yml (51%) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java new file mode 100644 index 00000000..ac2c32c3 --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java @@ -0,0 +1,90 @@ +package com.alibaba.otter.canal.client.adapter.support; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.function.Consumer; +import java.util.function.Function; + +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class Util { + + private static final Logger logger = LoggerFactory.getLogger(Util.class); + + /** + * 通过DS执行sql + */ + public static Object sqlRS(DataSource ds, String sql, Function fun) throws SQLException { + Connection conn = null; + Statement stmt = null; + ResultSet rs = null; + try { + conn = ds.getConnection(); + stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); + stmt.setFetchSize(Integer.MIN_VALUE); + rs = stmt.executeQuery(sql); + return fun.apply(rs); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + } + } + + /** + * sql执行获取resultSet + * + * @param conn sql connection + * @param sql sql + * @param consumer 回调方法 + */ + public static void sqlRS(Connection conn, String sql, Consumer consumer) { + Statement stmt = null; + ResultSet rs = null; + try { + stmt = conn.createStatement(); + rs = stmt.executeQuery(sql); + consumer.accept(rs); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + } + } +} diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java index 272ef1a9..04d208f7 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java @@ -10,6 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader; @@ -73,7 +74,7 @@ public class ESSyncService { dml.getDestination()); } if (logger.isDebugEnabled()) { - logger.debug("DML: {}", JSON.toJSONString(dml)); + logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java index 912baf08..172592e2 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseEtlService.java @@ -14,6 +14,7 @@ import java.util.function.Function; import javax.sql.DataSource; +import com.alibaba.otter.canal.client.adapter.support.Util; import org.apache.hadoop.hbase.util.Bytes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,43 +35,6 @@ public class HbaseEtlService { private static Logger logger = LoggerFactory.getLogger(HbaseEtlService.class); - public static Object sqlRS(DataSource ds, String sql, Function fun) throws SQLException { - Connection conn = null; - Statement stmt = null; - ResultSet rs = null; - try { - conn = ds.getConnection(); - stmt = conn.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); - stmt.setFetchSize(Integer.MIN_VALUE); - rs = stmt.executeQuery(sql); - return fun.apply(rs); - } finally { - if (rs != null) { - try { - rs.close(); - } catch (SQLException e) { - logger.error(e.getMessage(), e); - } - } - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException e) { - logger.error(e.getMessage(), e); - } - } - if (conn != null) { - try { - conn.close(); - } catch (SQLException e) { - logger.error(e.getMessage(), e); - } - } - rs = null; - stmt = null; - conn = null; - } - } /** * 建表 @@ -138,7 +102,7 @@ public class HbaseEtlService { if (params != null && params.size() == 1 && hbaseMapping.getEtlCondition() == null) { AtomicBoolean stExists = new AtomicBoolean(false); // 验证是否有SYS_TIME字段 - sqlRS(ds, sql, rs -> { + Util.sqlRS(ds, sql, rs -> { try { ResultSetMetaData rsmd = rs.getMetaData(); int cnt = rsmd.getColumnCount(); @@ -169,7 +133,7 @@ public class HbaseEtlService { // 获取总数 String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT "; - long cnt = (Long) sqlRS(ds, countSql, rs -> { + long cnt = (Long) Util.sqlRS(ds, countSql, rs -> { Long count = null; try { if (rs.next()) { @@ -244,7 +208,7 @@ public class HbaseEtlService { private static boolean executeSqlImport(DataSource ds, String sql, MappingConfig.HbaseMapping hbaseMapping, HbaseTemplate hbaseTemplate, AtomicLong successCount, List errMsg) { try { - sqlRS(ds, sql, rs -> { + Util.sqlRS(ds, sql, rs -> { int i = 1; try { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java index 2a242093..3ec58369 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/service/HbaseSyncService.java @@ -7,6 +7,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.support.*; import com.alibaba.otter.canal.client.adapter.support.Dml; @@ -39,7 +40,7 @@ public class HbaseSyncService { delete(config, dml); } if (logger.isDebugEnabled()) { - logger.debug("DML: {}", JSON.toJSONString(dml)); + logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } } } catch (Exception e) { diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index b6ed1c12..d48e6e71 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -107,6 +107,19 @@ jar-with-dependencies provided + + com.alibaba.otter + client-adapter.rdb + ${project.version} + + + * + * + + + jar-with-dependencies + provided + diff --git a/client-adapter/launcher/src/main/assembly/dev.xml b/client-adapter/launcher/src/main/assembly/dev.xml index 1be787b5..394fe33f 100644 --- a/client-adapter/launcher/src/main/assembly/dev.xml +++ b/client-adapter/launcher/src/main/assembly/dev.xml @@ -43,6 +43,13 @@ **/* + + ../rdb/src/main/resources/ + /config + + META-INF/** + + target logs diff --git a/client-adapter/launcher/src/main/assembly/release.xml b/client-adapter/launcher/src/main/assembly/release.xml index 57a815c8..163f2015 100644 --- a/client-adapter/launcher/src/main/assembly/release.xml +++ b/client-adapter/launcher/src/main/assembly/release.xml @@ -44,6 +44,13 @@ **/* + + ../rdb/src/main/resources/ + /config + + META-INF/** + + target logs diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 2e861642..045fee37 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import org.apache.kafka.common.errors.WakeupException; @@ -36,7 +37,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { groupId, canalClientConfig.getBatchSize(), flatMessage); - // connector.setSessionTimeout(1L, TimeUnit.MINUTES); + connector.setSessionTimeout(30L, TimeUnit.SECONDS); } @Override diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 894d123e..2de61937 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -4,6 +4,7 @@ logging: level: com.alibaba.otter.canal.client.adapter.hbase: DEBUG com.alibaba.otter.canal.client.adapter.es: DEBUG + com.alibaba.otter.canal.client.adapter.rdb: DEBUG spring: jackson: date-format: yyyy-MM-dd HH:mm:ss @@ -36,6 +37,18 @@ spring: # - groupId: g2 # outAdapters: # - name: logger +# - name: oracle +# properties: +# jdbc.driverClassName: oracle.jdbc.OracleDriver +# jdbc.url: jdbc:oracle:thin:@127.0.0.1:49161:XE +# jdbc.username: mytest +# jdbc.password: m121212 +# - name: postgresql +# properties: +# jdbc.driverClassName: org.postgresql.Driver +# jdbc.url: jdbc:postgresql://localhost:5432/postgres +# jdbc.username: postgres +# jdbc.password: 121212 # mqTopics: # - mqMode: rocketmq # topic: example @@ -53,3 +66,5 @@ spring: # adapterConfigs: # - hbase/mytest_person2.yml # - es/mytest_user.yml +# - oracle/mytest_user.yml +# - postgresql/mytest_user.yml diff --git a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java index 8f95b212..e9c693b7 100644 --- a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java +++ b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java @@ -3,9 +3,11 @@ package com.alibaba.otter.canal.client.adapter.logger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; import com.alibaba.otter.canal.client.adapter.support.SPI; /** @@ -27,7 +29,7 @@ public class LoggerAdapterExample implements OuterAdapter { @Override public void sync(Dml dml) { - logger.info("DML: {}", dml.toString()); + logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } @Override diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index d8fb9bcb..5dd47a05 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -1,8 +1,11 @@ package com.alibaba.otter.canal.client.adapter.rdb; +import java.sql.Connection; +import java.sql.ResultSetMetaData; import java.sql.SQLException; -import java.util.HashMap; -import java.util.Map; +import java.util.*; + +import javax.sql.DataSource; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -12,29 +15,28 @@ import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfigLoader; +import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; -import com.alibaba.otter.canal.client.adapter.support.Dml; -import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; -import com.alibaba.otter.canal.client.adapter.support.SPI; +import com.alibaba.otter.canal.client.adapter.support.*; -@SPI("rdb") -public class RdbAdapter implements OuterAdapter { +public abstract class RdbAdapter implements OuterAdapter { - private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); + private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); - private static volatile Map rdbMapping = null; // 文件名对应配置 - private static volatile Map mappingConfigCache = null; // 库名-表名对应配置 + private volatile Map rdbMapping = null; // 文件名对应配置 + private volatile Map mappingConfigCache = null; // 库名-表名对应配置 - private DruidDataSource dataSource; + private DruidDataSource dataSource; - private RdbSyncService rdbSyncService; + private RdbSyncService rdbSyncService; @Override public void init(OuterAdapterConfig configuration) { if (mappingConfigCache == null) { synchronized (MappingConfig.class) { if (mappingConfigCache == null) { - rdbMapping = MappingConfigLoader.load(); + SPI spi = this.getClass().getAnnotation(SPI.class); + rdbMapping = MappingConfigLoader.load(spi.value()); mappingConfigCache = new HashMap<>(); for (MappingConfig mappingConfig : rdbMapping.values()) { mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." @@ -65,6 +67,55 @@ public class RdbAdapter implements OuterAdapter { logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e); } + rdbMapping.values().forEach(config -> { + try { + MappingConfig.DbMapping dbMapping = config.getDbMapping(); + // 从源表加载所有字段名 + if (dbMapping.getAllColumns() == null) { + synchronized (RdbSyncService.class) { + if (dbMapping.getAllColumns() == null) { + DataSource srcDS = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); + Connection srcConn = srcDS.getConnection(); + String srcMetaSql = "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable() + + " WHERE 1=2 "; + List srcColumns = new ArrayList<>(); + Util.sqlRS(srcConn, srcMetaSql, rs -> { + try { + ResultSetMetaData rmd = rs.getMetaData(); + int cnt = rmd.getColumnCount(); + for (int i = 1; i <= cnt; i++) { + srcColumns.add(rmd.getColumnName(i).toLowerCase()); + } + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + }); + Map columnsMap = new LinkedHashMap<>(); + + for (String srcColumn : srcColumns) { + String targetColumn = srcColumn; + if (dbMapping.getTargetColumns() != null) { + for (Map.Entry entry : dbMapping.getTargetColumns().entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + + if (srcColumnName != null + && srcColumnName.toLowerCase().equals(srcColumn.toUpperCase())) { + targetColumn = targetColumnName; + } + } + } + columnsMap.put(targetColumn, srcColumn); + } + dbMapping.setAllColumns(columnsMap); + } + } + } + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + }); + rdbSyncService = new RdbSyncService(dataSource); } @@ -74,9 +125,93 @@ public class RdbAdapter implements OuterAdapter { String database = dml.getDatabase(); String table = dml.getTable(); MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); + rdbSyncService.sync(config, dml); } + @Override + public EtlResult etl(String task, List params) { + EtlResult etlResult = new EtlResult(); + MappingConfig config = rdbMapping.get(task); + if (config != null) { + DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); + if (srcDataSource != null) { + return RdbEtlService.importData(srcDataSource, dataSource, config, params); + } else { + etlResult.setSucceeded(false); + etlResult.setErrorMessage("DataSource not found"); + return etlResult; + } + } else { + StringBuilder resultMsg = new StringBuilder(); + boolean resSucc = true; + // ds不为空说明传入的是destination + for (MappingConfig configTmp : rdbMapping.values()) { + // 取所有的destination为task的配置 + if (configTmp.getDestination().equals(task)) { + DataSource srcDataSource = DatasourceConfig.DATA_SOURCES.get(configTmp.getDataSourceKey()); + if (srcDataSource == null) { + continue; + } + EtlResult etlRes = RdbEtlService.importData(srcDataSource, dataSource, configTmp, params); + if (!etlRes.getSucceeded()) { + resSucc = false; + resultMsg.append(etlRes.getErrorMessage()).append("\n"); + } else { + resultMsg.append(etlRes.getResultMessage()).append("\n"); + } + } + } + if (resultMsg.length() > 0) { + etlResult.setSucceeded(resSucc); + if (resSucc) { + etlResult.setResultMessage(resultMsg.toString()); + } else { + etlResult.setErrorMessage(resultMsg.toString()); + } + return etlResult; + } + } + etlResult.setSucceeded(false); + etlResult.setErrorMessage("Task not found"); + return etlResult; + } + + @Override + public Map count(String task) { + MappingConfig config = rdbMapping.get(task); + MappingConfig.DbMapping dbMapping = config.getDbMapping(); + String sql = "SELECT COUNT(1) AS cnt FROM " + dbMapping.getTargetTable(); + Connection conn = null; + Map res = new LinkedHashMap<>(); + try { + conn = dataSource.getConnection(); + Util.sqlRS(conn, sql, rs -> { + try { + if (rs.next()) { + Long rowCount = rs.getLong("cnt"); + res.put("count", rowCount); + } + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + }); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } finally { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + } + res.put("targetTable", dbMapping.getTargetTable()); + + return res; + } + @Override public void destroy() { if (dataSource != null) { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java new file mode 100644 index 00000000..ea73b1c0 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java @@ -0,0 +1,8 @@ +package com.alibaba.otter.canal.client.adapter.rdb.adapters; + +import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.support.SPI; + +@SPI("mysql") +public class MysqlAdapter extends RdbAdapter { +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java new file mode 100644 index 00000000..fe6111a2 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java @@ -0,0 +1,8 @@ +package com.alibaba.otter.canal.client.adapter.rdb.adapters; + +import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.support.SPI; + +@SPI("oracle") +public class OracleAdapter extends RdbAdapter { +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java new file mode 100644 index 00000000..2cf4a444 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java @@ -0,0 +1,8 @@ +package com.alibaba.otter.canal.client.adapter.rdb.adapters; + +import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.support.SPI; + +@SPI("postgresql") +public class PostgresqlAdapter extends RdbAdapter { +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java new file mode 100644 index 00000000..2fe7e0ed --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java @@ -0,0 +1,8 @@ +package com.alibaba.otter.canal.client.adapter.rdb.adapters; + +import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.support.SPI; + +@SPI("sqlserver") +public class SqlserverAdapter extends RdbAdapter { +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index f7113d7f..da7e6ef8 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -58,6 +58,7 @@ public class MappingConfig { private String database; // 数据库名或schema名 private String table; // 表面名 + private Map targetPk; // 目标表主键字段 private boolean mapAll = false; // 映射所有字段 private String targetTable; // 目标表名 private Map targetColumns; // 目标表字段映射 @@ -86,6 +87,14 @@ public class MappingConfig { this.table = table; } + public Map getTargetPk() { + return targetPk; + } + + public void setTargetPk(Map targetPk) { + this.targetPk = targetPk; + } + public boolean isMapAll() { return mapAll; } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java index 8d3613a8..8658d45e 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java @@ -25,19 +25,17 @@ public class MappingConfigLoader { private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class); - private static final String BASE_PATH = "rdb"; - /** * 加载HBase表映射配置 * * @return 配置名/配置文件名--对象 */ - public static Map load() { + public static Map load(String name) { logger.info("## Start loading rdb mapping config ... "); Map result = new LinkedHashMap<>(); - Collection configs = AdapterConfigs.get("rdb"); + Collection configs = AdapterConfigs.get(name); if (configs == null) { return result; } @@ -53,7 +51,7 @@ public class MappingConfigLoader { String configContent = null; if (c.endsWith(".yml")) { - configContent = readConfigContent(BASE_PATH + "/" + c); + configContent = readConfigContent(name + "/" + c); } MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java new file mode 100644 index 00000000..321c9f21 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java @@ -0,0 +1,290 @@ +package com.alibaba.otter.canal.client.adapter.rdb.service; + +import java.sql.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import javax.sql.DataSource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; +import com.alibaba.otter.canal.client.adapter.support.EtlResult; +import com.alibaba.otter.canal.client.adapter.support.Util; +import com.google.common.base.Joiner; + +/** + * RDB ETL 操作业务类 + * + * @author rewerma @ 2018-11-7 + * @version 1.0.0 + */ +public class RdbEtlService { + + private static final Logger logger = LoggerFactory.getLogger(RdbEtlService.class); + + /** + * 导入数据 + */ + public static EtlResult importData(DataSource srcDS, DataSource targetDS, MappingConfig config, + List params) { + EtlResult etlResult = new EtlResult(); + AtomicLong successCount = new AtomicLong(); + List errMsg = new ArrayList<>(); + String hbaseTable = ""; + try { + if (config == null) { + logger.error("Config is null!"); + etlResult.setSucceeded(false); + etlResult.setErrorMessage("Config is null!"); + return etlResult; + } + DbMapping dbMapping = config.getDbMapping(); + + long start = System.currentTimeMillis(); + + // 拼接sql + StringBuilder sql = new StringBuilder( + "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable()); + + // 拼接条件 + appendCondition(params, dbMapping, srcDS, sql); + + // 获取总数 + String countSql = "SELECT COUNT(1) FROM ( " + sql + ") _CNT "; + long cnt = (Long) Util.sqlRS(srcDS, countSql, rs -> { + Long count = null; + try { + if (rs.next()) { + count = ((Number) rs.getObject(1)).longValue(); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + return count == null ? 0 : count; + }); + + // 当大于1万条记录时开启多线程 + if (cnt >= 10000) { + int threadCount = 3; + long perThreadCnt = cnt / threadCount; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + List> futures = new ArrayList<>(threadCount); + for (int i = 0; i < threadCount; i++) { + long offset = i * perThreadCnt; + Long size = null; + if (i != threadCount - 1) { + size = perThreadCnt; + } + String sqlFinal; + if (size != null) { + sqlFinal = sql + " LIMIT " + offset + "," + size; + } else { + sqlFinal = sql + " LIMIT " + offset + "," + cnt; + } + Future future = executor.submit( + () -> executeSqlImport(params, srcDS, targetDS, sqlFinal, dbMapping, successCount, errMsg)); + futures.add(future); + } + + for (Future future : futures) { + future.get(); + } + + executor.shutdown(); + } else { + executeSqlImport(params, srcDS, targetDS, sql.toString(), dbMapping, successCount, errMsg); + } + + logger.info( + dbMapping.getTable() + " etl completed in: " + (System.currentTimeMillis() - start) / 1000 + "s!"); + + etlResult.setResultMessage("导入目标表 " + dbMapping.getTargetTable() + " 数据:" + successCount.get() + " 条"); + } catch (Exception e) { + logger.error(e.getMessage(), e); + errMsg.add(hbaseTable + " etl failed! ==>" + e.getMessage()); + } + + if (errMsg.isEmpty()) { + etlResult.setSucceeded(true); + } else { + etlResult.setErrorMessage(Joiner.on("\n").join(errMsg)); + } + return etlResult; + } + + private static void appendCondition(List params, DbMapping dbMapping, DataSource ds, + StringBuilder sql) throws SQLException { + if (params != null && params.size() == 1 && dbMapping.getEtlCondition() == null) { + AtomicBoolean stExists = new AtomicBoolean(false); + // 验证是否有SYS_TIME字段 + Util.sqlRS(ds, sql.toString(), rs -> { + try { + ResultSetMetaData rsmd = rs.getMetaData(); + int cnt = rsmd.getColumnCount(); + for (int i = 1; i <= cnt; i++) { + String columnName = rsmd.getColumnName(i); + if ("SYS_TIME".equalsIgnoreCase(columnName)) { + stExists.set(true); + break; + } + } + } catch (Exception e) { + // ignore + } + return null; + }); + if (stExists.get()) { + sql.append(" WHERE SYS_TIME >= '").append(params.get(0)).append("' "); + } + } else if (dbMapping.getEtlCondition() != null && params != null) { + String etlCondition = dbMapping.getEtlCondition(); + int size = params.size(); + for (int i = 0; i < size; i++) { + etlCondition = etlCondition.replace("{" + i + "}", params.get(i)); + } + + sql.append(" ").append(etlCondition); + } + } + + /** + * 执行导入 + */ + private static boolean executeSqlImport(List params, DataSource srcDS, DataSource targetDS, String sql, + DbMapping dbMapping, AtomicLong successCount, List errMsg) { + try { + Util.sqlRS(srcDS, sql, rs -> { + int idx = 1; + + try { + boolean completed = false; + + Map columnType = new LinkedHashMap<>(); + ResultSetMetaData rsd = rs.getMetaData(); + int columnCount = rsd.getColumnCount(); + for (int i = 1; i <= columnCount; i++) { + columnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i)); + } + + Map columnsMap; + if (dbMapping.isMapAll()) { + columnsMap = dbMapping.getAllColumns(); + } else { + columnsMap = dbMapping.getTargetColumns(); + } + + StringBuilder insertSql = new StringBuilder(); + insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); + columnsMap + .forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); + + int len = insertSql.length(); + insertSql.delete(len - 1, len).append(") VALUES ("); + int mapLen = columnsMap.size(); + for (int i = 0; i < mapLen; i++) { + insertSql.append("?,"); + } + len = insertSql.length(); + insertSql.delete(len - 1, len).append(")"); + try (Connection connTarget = targetDS.getConnection(); + PreparedStatement pstmt = connTarget.prepareStatement(insertSql.toString())) { + connTarget.setAutoCommit(false); + + while (rs.next()) { + pstmt.clearParameters(); + + // 删除数据 + Map values = new LinkedHashMap<>(); + StringBuilder deleteSql = new StringBuilder( + "DELETE FROM " + dbMapping.getTargetTable() + " WHERE "); + appendCondition(dbMapping, deleteSql, values, rs); + try (PreparedStatement pstmt2 = connTarget.prepareStatement(deleteSql.toString())) { + int k = 1; + for (Object val : values.values()) { + pstmt2.setObject(k++, val); + } + pstmt2.execute(); + } + + int i = 1; + for (Map.Entry entry : columnsMap.entrySet()) { + String targetClolumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetClolumnName; + } + + Integer type = columnType.get(targetClolumnName.toLowerCase()); + + Object value = rs.getObject(srcColumnName); + if (value != null) { + RdbSyncService.setPStmt(type, pstmt, value, i); + } else { + pstmt.setNull(i, type); + } + + i++; + } + + pstmt.execute(); + if (logger.isTraceEnabled()) { + logger.trace("Insert into target table, sql: {}", insertSql); + } + + if (idx % dbMapping.getCommitBatch() == 0) { + connTarget.commit(); + completed = true; + } + idx++; + successCount.incrementAndGet(); + if (logger.isDebugEnabled()) { + logger.debug("successful import count:" + successCount.get()); + } + } + if (!completed) { + connTarget.commit(); + } + } + + } catch (Exception e) { + logger.error(dbMapping.getTable() + " etl failed! ==>" + e.getMessage(), e); + errMsg.add(dbMapping.getTable() + " etl failed! ==>" + e.getMessage()); + } + return idx; + }); + return true; + } catch (Exception e) { + logger.error(e.getMessage(), e); + return false; + } + } + + /** + * 拼接目标表主键where条件 + */ + private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map values, + ResultSet rs) throws SQLException { + // 拼接主键 + for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetColumnName; + } + sql.append(targetColumnName).append("=? AND "); + values.put(targetColumnName, rs.getObject(srcColumnName)); + } + int len = sql.length(); + sql.delete(len - 4, len); + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index a6b46fd9..ab46bf4b 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -14,11 +14,13 @@ import java.util.function.Consumer; import javax.sql.DataSource; +import com.alibaba.otter.canal.client.adapter.support.Util; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; @@ -32,12 +34,11 @@ import com.alibaba.otter.canal.client.adapter.support.Dml; */ public class RdbSyncService { - private static final Logger logger = LoggerFactory - .getLogger(RdbSyncService.class); + private static final Logger logger = LoggerFactory.getLogger(RdbSyncService.class); - private static final Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); + private final Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); - private DataSource dataSource; + private DataSource dataSource; public RdbSyncService(DataSource dataSource){ this.dataSource = dataSource; @@ -46,62 +47,16 @@ public class RdbSyncService { public void sync(MappingConfig config, Dml dml) { try { if (config != null) { - { - DbMapping dbMapping = config.getDbMapping(); - // 从源表加载所有字段名 - if (dbMapping.getAllColumns() == null) { - synchronized (RdbSyncService.class) { - if (dbMapping.getAllColumns() == null) { - DataSource srcDS = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); - Connection srcConn = srcDS.getConnection(); - String srcMetaSql = "SELECT * FROM " + dbMapping.getDatabase() + "." - + dbMapping.getTable() + " WHERE 1=2 "; - List srcColumns = new ArrayList<>(); - sqlRS(srcConn, srcMetaSql, rs -> { - try { - ResultSetMetaData rmd = rs.getMetaData(); - int cnt = rmd.getColumnCount(); - for (int i = 1; i <= cnt; i++) { - srcColumns.add(rmd.getColumnName(i).toLowerCase()); - } - } catch (SQLException e) { - logger.error(e.getMessage(), e); - } - }); - Map columnsMap = new LinkedHashMap<>(); - - for (String srcColumn : srcColumns) { - String targetColumn = srcColumn; - if (dbMapping.getTargetColumns() != null) { - for (Map.Entry entry : dbMapping.getTargetColumns() - .entrySet()) { - String targetColumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - - if (srcColumnName != null - && srcColumnName.toLowerCase().equals(srcColumn.toUpperCase())) { - targetColumn = targetColumnName; - } - } - } - columnsMap.put(targetColumn, srcColumn); - } - dbMapping.setAllColumns(columnsMap); - } - } - } - } - String type = dml.getType(); if (type != null && type.equalsIgnoreCase("INSERT")) { insert(config, dml); } else if (type != null && type.equalsIgnoreCase("UPDATE")) { - // update(config, dml); + update(config, dml); } else if (type != null && type.equalsIgnoreCase("DELETE")) { - // delete(config, dml); + delete(config, dml); } if (logger.isDebugEnabled()) { - logger.debug("DML: {}", JSON.toJSONString(dml)); + logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } } } catch (Exception e) { @@ -109,72 +64,6 @@ public class RdbSyncService { } } - private void update(MappingConfig config, Dml dml) throws SQLException { - List> data = dml.getData(); - if (data == null || data.isEmpty()) { - return; - } - - List> old = dml.getOld(); - if (old == null || old.isEmpty()) { - return; - } - - DbMapping dbMapping = config.getDbMapping(); - - int idx = 1; - boolean complete = false; - - Connection conn = dataSource.getConnection(); - boolean oriAutoCommit = conn.getAutoCommit(); - conn.setAutoCommit(false); - - try { - Map columnsMap; - if (dbMapping.isMapAll()) { - columnsMap = dbMapping.getAllColumns(); - } else { - columnsMap = dbMapping.getTargetColumns(); - } - - int i = 0; - for (Map o : old) { - Map d = data.get(i); - StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); - List values = new ArrayList<>(); - boolean flag = false; - for (String srcColumnName : o.keySet()) { - List targetColumnNames = new ArrayList<>(); - columnsMap.forEach((targetColumn, srcColumn) -> { - if (srcColumnName.toLowerCase().equals(srcColumn)) { - targetColumnNames.add(targetColumn); - } - }); - if (!targetColumnNames.isEmpty()) { - if (!flag) { - flag = true; - } - for (String targetColumnName : targetColumnNames) { - updateSql.append(targetColumnName).append("=?, "); - values.add(data.get(i)); - } - } - } - if (flag) { - int len = updateSql.length(); - updateSql.delete(len - 2, len).append(" WHERE "); - } - i++; - } - } catch (Exception e) { - conn.rollback(); - } finally { - conn.setAutoCommit(oriAutoCommit); - conn.close(); - } - } - /** * 插入操作 * @@ -193,7 +82,6 @@ public class RdbSyncService { boolean completed = false; Connection conn = dataSource.getConnection(); - boolean oriAutoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); try { Map columnsMap; @@ -216,15 +104,38 @@ public class RdbSyncService { len = insertSql.length(); insertSql.delete(len - 1, len).append(")"); + Map ctype = getTargetColumnType(conn, config); + PreparedStatement pstmt = conn.prepareStatement(insertSql.toString()); - for (Map r : data) { + for (Map d : data) { pstmt.clearParameters(); - convertData2DbRow(conn, config, r, pstmt); + int i = 1; + for (Map.Entry entry : columnsMap.entrySet()) { + String targetClolumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetClolumnName; + } + + Integer type = ctype.get(targetClolumnName.toLowerCase()); + + Object value = d.get(srcColumnName); + if (value != null) { + if (type == null) { + throw new RuntimeException("No column: " + targetClolumnName + " found in target db"); + } + + setPStmt(type, pstmt, value, i); + } else { + pstmt.setNull(i, type); + } + i++; + } pstmt.execute(); if (logger.isTraceEnabled()) { - logger.trace("Insert into target db, sql: {}", insertSql); + logger.trace("Insert into target table, sql: {}", insertSql); } if (idx % config.getDbMapping().getCommitBatch() == 0) { @@ -239,19 +150,154 @@ public class RdbSyncService { } catch (Exception e) { conn.rollback(); } finally { - conn.setAutoCommit(oriAutoCommit); conn.close(); } } - private static void sqlRS(Connection conn, String sql, Consumer consumer) { - try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { - consumer.accept(rs); - } catch (SQLException e) { - logger.error(e.getMessage(), e); + /** + * 更新操作 + * + * @param config 配置项 + * @param dml DML数据 + */ + private void update(MappingConfig config, Dml dml) throws SQLException { + List> data = dml.getData(); + if (data == null || data.isEmpty()) { + return; + } + + List> old = dml.getOld(); + if (old == null || old.isEmpty()) { + return; + } + + DbMapping dbMapping = config.getDbMapping(); + + int idx = 1; + boolean completed = false; + + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + + try { + Map columnsMap; + if (dbMapping.isMapAll()) { + columnsMap = dbMapping.getAllColumns(); + } else { + columnsMap = dbMapping.getTargetColumns(); + } + + Map ctype = getTargetColumnType(conn, config); + + for (Map o : old) { + Map d = data.get(idx - 1); + StringBuilder updateSql = new StringBuilder(); + updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); + Map values = new LinkedHashMap<>(); + for (String srcColumnName : o.keySet()) { + List targetColumnNames = new ArrayList<>(); + columnsMap.forEach((targetColumn, srcColumn) -> { + if (srcColumnName.toLowerCase().equals(srcColumn)) { + targetColumnNames.add(targetColumn); + } + }); + if (!targetColumnNames.isEmpty()) { + + for (String targetColumnName : targetColumnNames) { + updateSql.append(targetColumnName).append("=?, "); + values.put(targetColumnName, d.get(srcColumnName)); + } + } + } + int len = updateSql.length(); + updateSql.delete(len - 2, len).append(" WHERE "); + + // 拼接主键 + appendCondition(dbMapping, updateSql, values, d); + + sqlExe(conn, updateSql.toString(), ctype, values); + + if (logger.isTraceEnabled()) { + logger.trace("Update target table, sql: {}", updateSql); + } + if (idx % config.getDbMapping().getCommitBatch() == 0) { + conn.commit(); + completed = true; + } + idx++; + } + if (!completed) { + conn.commit(); + } + } catch (Exception e) { + conn.rollback(); + } finally { + conn.close(); } } + /** + * 删除操作 + * + * @param config + * @param dml + * @throws SQLException + */ + private void delete(MappingConfig config, Dml dml) throws SQLException { + List> data = dml.getData(); + if (data == null || data.isEmpty()) { + return; + } + + DbMapping dbMapping = config.getDbMapping(); + + int idx = 1; + boolean completed = false; + + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + + try { + Map ctype = getTargetColumnType(conn, config); + + for (Map d : data) { + StringBuilder sql = new StringBuilder(); + sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); + + Map values = new LinkedHashMap<>(); + // 拼接主键 + appendCondition(dbMapping, sql, values, d); + + sqlExe(conn, sql.toString(), ctype, values); + + if (logger.isTraceEnabled()) { + logger.trace("Delete from target table, sql: {}", sql); + } + if (idx % config.getDbMapping().getCommitBatch() == 0) { + conn.commit(); + completed = true; + } + idx++; + } + if (!completed) { + conn.commit(); + } + } catch (Exception e) { + conn.rollback(); + } finally { + conn.close(); + } + } + + + + /** + * 获取目标字段类型 + * + * @param conn sql connection + * @param config 映射配置 + * @return 字段sqlType + */ private Map getTargetColumnType(Connection conn, MappingConfig config) { DbMapping dbMapping = config.getDbMapping(); String cacheKey = config.getDestination() + "." + dbMapping.getDatabase() + "." + dbMapping.getTable(); @@ -263,7 +309,7 @@ public class RdbSyncService { columnType = new LinkedHashMap<>(); final Map columnTypeTmp = columnType; String sql = "SELECT * FROM " + dbMapping.getTargetTable() + " WHERE 1=2"; - sqlRS(conn, sql, rs -> { + Util.sqlRS(conn, sql, rs -> { try { ResultSetMetaData rsd = rs.getMetaData(); int columnCount = rsd.getColumnCount(); @@ -282,49 +328,14 @@ public class RdbSyncService { } /** - * 新增类型转换 - * - * @param config - * @param data - * @param pstmt - * @throws SQLException + * 设置 preparedStatement + * + * @param type sqlType + * @param pstmt 需要设置的preparedStatement + * @param value 值 + * @param i 索引号 */ - private void convertData2DbRow(Connection conn, MappingConfig config, Map data, - PreparedStatement pstmt) throws SQLException { - DbMapping dbMapping = config.getDbMapping(); - Map columnsMap; - if (dbMapping.isMapAll()) { - columnsMap = dbMapping.getAllColumns(); - } else { - columnsMap = dbMapping.getTargetColumns(); - } - Map ctype = getTargetColumnType(conn, config); - - int i = 1; - for (Map.Entry entry : columnsMap.entrySet()) { - String targetClassName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetClassName; - } - - Integer type = ctype.get(targetClassName.toLowerCase()); - - Object value = data.get(srcColumnName); - if (value != null) { - if (type == null) { - throw new RuntimeException("No column: " + targetClassName + " found in target db"); - } - - setPStmt(type, pstmt, value, i); - } else { - pstmt.setNull(i, type); - } - i++; - } - } - - private void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException { + public static void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException { switch (type) { case Types.BIT: case Types.BOOLEAN: @@ -476,4 +487,41 @@ public class RdbSyncService { pstmt.setObject(i, value, type); } } + + /** + * 拼接主键 where条件 + */ + private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map values, + Map d) { + // 拼接主键 + for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetColumnName; + } + sql.append(targetColumnName).append("=? AND "); + values.put(targetColumnName, d.get(srcColumnName)); + } + int len = sql.length(); + sql.delete(len - 4, len); + } + + /** + * 执行sql + */ + private static void sqlExe(Connection conn, String sql, Map ctype, Map values) { + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + int i = 1; + for (Map.Entry entry : values.entrySet()) { + String targetColumnName = entry.getKey(); + Object value = entry.getValue(); + Integer type = ctype.get(targetColumnName.toLowerCase()); + setPStmt(type, pstmt, value, i++); + } + pstmt.execute(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } } diff --git a/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter b/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter index 0a840275..c005f7fd 100644 --- a/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter +++ b/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter @@ -1 +1,4 @@ -rdb=com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter \ No newline at end of file +mysql=com.alibaba.otter.canal.client.adapter.rdb.adapters.MysqlAdapter +postgresql=com.alibaba.otter.canal.client.adapter.rdb.adapters.PostgresqlAdapter +oracle=com.alibaba.otter.canal.client.adapter.rdb.adapters.OracleAdapter +sqlserver=com.alibaba.otter.canal.client.adapter.rdb.adapters.SqlserverAdapter \ No newline at end of file diff --git a/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/main/resources/oracle/mytest_user.yml similarity index 90% rename from client-adapter/rdb/src/test/resources/rdb/mytest_user.yml rename to client-adapter/rdb/src/main/resources/oracle/mytest_user.yml index 09dda295..a7bede03 100644 --- a/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/main/resources/oracle/mytest_user.yml @@ -4,7 +4,8 @@ dbMapping: database: mytest table: user targetTable: mytest.tb_user - commitBatch: 3000 + targetPk: + id: id mapAll: true # targetColumns: # id: diff --git a/client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml b/client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml new file mode 100644 index 00000000..1b4eaed7 --- /dev/null +++ b/client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml @@ -0,0 +1,15 @@ +dataSourceKey: defaultDS +destination: example +dbMapping: + database: mytest + table: user + targetTable: public.tb_user + targetPk: + id: id + mapAll: true +# targetColumns: +# id: +# name: +# role_id: +# c_time: +# test1: \ No newline at end of file diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java index ace59b55..dea6cee7 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java @@ -15,14 +15,14 @@ public class ConfigLoadTest { @Before public void before() { - AdapterConfigs.put("rdb", "mytest_user.yml"); + AdapterConfigs.put("oracle", "mytest_user.yml"); // 加载数据源连接池 DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); } @Test public void testLoad() { - Map configMap = MappingConfigLoader.load(); + Map configMap = MappingConfigLoader.load("oracle"); Assert.assertFalse(configMap.isEmpty()); } diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java index 3e7917de..0682332a 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java @@ -1,6 +1,7 @@ package com.alibaba.otter.canal.client.adapter.rdb.test.sync; import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.rdb.adapters.OracleAdapter; import com.alibaba.otter.canal.client.adapter.rdb.test.TestConstant; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; @@ -13,7 +14,7 @@ public class Common { DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig(); - outerAdapterConfig.setName("rdb"); + outerAdapterConfig.setName("oracle"); Map properties = new HashMap<>(); properties.put("jdbc.driveClassName", "oracle.jdbc.OracleDriver"); properties.put("jdbc.url", "jdbc:oracle:thin:@127.0.0.1:49161:XE"); @@ -21,7 +22,7 @@ public class Common { properties.put("jdbc.password", "m121212"); outerAdapterConfig.setProperties(properties); - RdbAdapter adapter = new RdbAdapter(); + RdbAdapter adapter = new OracleAdapter(); adapter.init(outerAdapterConfig); return adapter; } diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java index 3324e41c..514ffaa5 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java @@ -1,8 +1,5 @@ package com.alibaba.otter.canal.client.adapter.rdb.test.sync; -import java.io.BufferedReader; -import java.io.Reader; -import java.sql.Clob; import java.util.*; import org.junit.Before; @@ -18,7 +15,7 @@ public class OracleSyncTest { @Before public void init() { - AdapterConfigs.put("rdb", "mytest_user.yml"); + AdapterConfigs.put("oracle", "mytest_user.yml"); rdbAdapter = Common.init(); } @@ -43,5 +40,27 @@ public class OracleSyncTest { rdbAdapter.sync(dml); } + @Test + public void test02() { + Dml dml = new Dml(); + dml.setDestination("example"); + dml.setTs(new Date().getTime()); + dml.setType("UPDATE"); + dml.setDatabase("mytest"); + dml.setTable("user"); + List> dataList = new ArrayList<>(); + Map data = new LinkedHashMap<>(); + dataList.add(data); + data.put("id", 1L); + data.put("name", "Eric2"); + dml.setData(dataList); + List> oldList = new ArrayList<>(); + Map old = new LinkedHashMap<>(); + oldList.add(old); + old.put("name", "Eric"); + dml.setOld(oldList); + + rdbAdapter.sync(dml); + } } diff --git a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/test/resources/oracle/mytest_user.yml similarity index 51% rename from client-adapter/rdb/src/main/resources/rdb/mytest_user.yml rename to client-adapter/rdb/src/test/resources/oracle/mytest_user.yml index d7bb4ae8..a7bede03 100644 --- a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/test/resources/oracle/mytest_user.yml @@ -4,9 +4,12 @@ dbMapping: database: mytest table: user targetTable: mytest.tb_user - commitBatch: 3000 - mapAll: false - targetColumns: - id: - name: - role_id: \ No newline at end of file + targetPk: + id: id + mapAll: true +# targetColumns: +# id: +# name: +# role_id: +# c_time: +# test1: \ No newline at end of file From f9778a0aff910ec187fbd26f52d443547505cc07 Mon Sep 17 00:00:00 2001 From: agapple Date: Fri, 9 Nov 2018 13:58:39 +0800 Subject: [PATCH 297/360] fixed issue #1087, support kafka docker check --- docker/image/admin/app.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/image/admin/app.sh b/docker/image/admin/app.sh index ca9000c2..413378c8 100755 --- a/docker/image/admin/app.sh +++ b/docker/image/admin/app.sh @@ -76,9 +76,9 @@ function checkStart() { function start_canal() { echo "start canal ..." - serverPort=`perl -le 'print $ENV{"canal.port"}'` - if [ -z "$serverPort" ] ; then - serverPort=11111 + metricsPort=`perl -le 'print $ENV{"canal.metrics.pull.port"}'` + if [ -z "$metricsPort" ] ; then + metricsPort=11112 fi destination=`perl -le 'print $ENV{"canal.destinations"}'` @@ -95,7 +95,7 @@ function start_canal() { su admin -c 'cd /home/admin/canal-server/bin/ && sh restart.sh 1>>/tmp/start.log 2>&1' sleep 5 #check start - checkStart "canal" "nc 127.0.0.1 $serverPort -w 1 -z | wc -l" 30 + checkStart "canal" "nc 127.0.0.1 $metricsPort -w 1 -z | wc -l" 30 } function stop_canal() { From c5bd42bbf1f48f0b12c0ac98fda938e282612b9b Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 9 Nov 2018 16:39:52 +0800 Subject: [PATCH 298/360] =?UTF-8?q?canal=20server=E7=AB=AF=20MQ=20?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BF=AE=E6=94=B9=20MQ=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=8F=AF=E5=8A=A8=E6=80=81=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/deployer/CanalConstants.java | 9 + .../otter/canal/deployer/CanalController.java | 177 +++++++++++------- .../otter/canal/deployer/CanalLauncher.java | 7 +- deployer/src/main/resources/canal.properties | 15 +- .../resources/example/instance.properties | 15 +- deployer/src/main/resources/mq.yml | 23 --- .../resources/spring/default-instance.xml | 10 + .../main/resources/spring/file-instance.xml | 10 + .../main/resources/spring/group-instance.xml | 10 + .../main/resources/spring/memory-instance.xml | 10 + .../instance/core/AbstractCanalInstance.java | 8 + .../canal/instance/core/CanalInstance.java | 2 + .../canal/instance/core/CanalMQConfig.java | 68 +++++++ .../spring/CanalInstanceWithSpring.java | 6 + server/pom.xml | 5 - .../otter/canal/common/MQProperties.java | 33 ++-- .../otter/canal/server/CanalMQStarter.java | 98 +++++----- 17 files changed, 333 insertions(+), 173 deletions(-) delete mode 100644 deployer/src/main/resources/mq.yml create mode 100644 instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index a2a2d532..2bfb32ea 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -37,6 +37,15 @@ public class CanalConstants { public static final String CANAL_SOCKETCHANNEL = ROOT + "." + "socketChannel"; + public static final String CANAL_MQ_SERVERS = ROOT + "." + "mq.servers"; + public static final String CANAL_MQ_RETRIES = ROOT + "." + "mq.retries"; + public static final String CANAL_MQ_BATCHSIZE = ROOT + "." + "mq.batchSize"; + public static final String CANAL_MQ_LINGERMS = ROOT + "." + "mq.lingerMs"; + public static final String CANAL_MQ_BUFFERMEMORY = ROOT + "." + "mq.bufferMemory"; + public static final String CANAL_MQ_CANALBATCHSIZE = ROOT + "." + "mq.canalBatchSize"; + public static final String CANAL_MQ_CANALGETTIMEOUT = ROOT + "." + "mq.canalGetTimeout"; + public static final String CANAL_MQ_FLATMESSAGE = ROOT + "." + "mq.flatMessage"; + public static String getInstanceModeKey(String destination) { return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination); } diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index a15921de..535dbd44 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -17,6 +17,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.alibaba.otter.canal.common.CanalException; +import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.common.utils.AddressUtils; import com.alibaba.otter.canal.common.zookeeper.ZkClientx; import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; @@ -35,6 +36,7 @@ import com.alibaba.otter.canal.instance.manager.CanalConfigClient; import com.alibaba.otter.canal.instance.manager.ManagerCanalInstanceGenerator; import com.alibaba.otter.canal.instance.spring.SpringCanalInstanceGenerator; import com.alibaba.otter.canal.parse.CanalEventParser; +import com.alibaba.otter.canal.server.CanalMQStarter; import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; import com.alibaba.otter.canal.server.exception.CanalServerException; import com.alibaba.otter.canal.server.netty.CanalServerWithNetty; @@ -68,6 +70,8 @@ public class CanalController { private CanalInstanceGenerator instanceGenerator; private ZkClientx zkclientx; + private CanalMQStarter canalMQStarter; + public CanalController(){ this(System.getProperties()); } @@ -105,7 +109,7 @@ public class CanalController { logger.info("No valid metrics server port found, use default 11112."); embededCanalServer.setMetricsPort(11112); } - + String canalWithoutNetty = getProperty(properties, CanalConstants.CANAL_WITHOUT_NETTY); if (canalWithoutNetty == null || "false".equals(canalWithoutNetty)) { canalServer = CanalServerWithNetty.instance(); @@ -127,80 +131,81 @@ public class CanalController { final ServerRunningData serverData = new ServerRunningData(cid, ip + ":" + port); ServerRunningMonitors.setServerData(serverData); - ServerRunningMonitors.setRunningMonitors(MigrateMap.makeComputingMap(new Function() { + ServerRunningMonitors + .setRunningMonitors(MigrateMap.makeComputingMap(new Function() { - public ServerRunningMonitor apply(final String destination) { - ServerRunningMonitor runningMonitor = new ServerRunningMonitor(serverData); - runningMonitor.setDestination(destination); - runningMonitor.setListener(new ServerRunningListener() { + public ServerRunningMonitor apply(final String destination) { + ServerRunningMonitor runningMonitor = new ServerRunningMonitor(serverData); + runningMonitor.setDestination(destination); + runningMonitor.setListener(new ServerRunningListener() { - public void processActiveEnter() { - try { - MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); - embededCanalServer.start(destination); - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); - } - } - - public void processActiveExit() { - try { - MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); - embededCanalServer.stop(destination); - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); - } - } - - public void processStart() { - try { - if (zkclientx != null) { - final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, ip + ":" - + port); - initCid(path); - zkclientx.subscribeStateChanges(new IZkStateListener() { - - public void handleStateChanged(KeeperState state) throws Exception { - - } - - public void handleNewSession() throws Exception { - initCid(path); - } - - @Override - public void handleSessionEstablishmentError(Throwable error) throws Exception { - logger.error("failed to connect to zookeeper", error); - } - }); + public void processActiveEnter() { + try { + MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); + embededCanalServer.start(destination); + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); } - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); } - } - public void processStop() { - try { - MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); - if (zkclientx != null) { - final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, ip + ":" - + port); - releaseCid(path); + public void processActiveExit() { + try { + MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); + embededCanalServer.stop(destination); + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); } - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); } - } - }); - if (zkclientx != null) { - runningMonitor.setZkClient(zkclientx); + public void processStart() { + try { + if (zkclientx != null) { + final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, + ip + ":" + port); + initCid(path); + zkclientx.subscribeStateChanges(new IZkStateListener() { + + public void handleStateChanged(KeeperState state) throws Exception { + + } + + public void handleNewSession() throws Exception { + initCid(path); + } + + @Override + public void handleSessionEstablishmentError(Throwable error) throws Exception { + logger.error("failed to connect to zookeeper", error); + } + }); + } + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); + } + } + + public void processStop() { + try { + MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); + if (zkclientx != null) { + final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, + ip + ":" + port); + releaseCid(path); + } + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); + } + } + + }); + if (zkclientx != null) { + runningMonitor.setZkClient(zkclientx); + } + // 触发创建一下cid节点 + runningMonitor.init(); + return runningMonitor; } - // 触发创建一下cid节点 - runningMonitor.init(); - return runningMonitor; - } - })); + })); // 初始化monitor机制 autoScan = BooleanUtils.toBoolean(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN)); @@ -220,6 +225,9 @@ public class CanalController { ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination); if (!config.getLazy() && !runningMonitor.isStart()) { runningMonitor.start(); + if (canalMQStarter != null) { + canalMQStarter.start(getMQProperties(properties)); + } } } } @@ -228,6 +236,9 @@ public class CanalController { // 此处的stop,代表强制退出,非HA机制,所以需要退出HA的monitor和配置信息 InstanceConfig config = instanceConfigs.remove(destination); if (config != null) { + if (canalMQStarter != null) { + canalMQStarter.stop(); + } embededCanalServer.stop(destination); ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination); if (runningMonitor.isStart()) { @@ -246,7 +257,8 @@ public class CanalController { instanceConfigMonitors = MigrateMap.makeComputingMap(new Function() { public InstanceConfigMonitor apply(InstanceMode mode) { - int scanInterval = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN_INTERVAL)); + int scanInterval = Integer + .valueOf(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN_INTERVAL)); if (mode.isSpring()) { SpringInstanceConfigMonitor monitor = new SpringInstanceConfigMonitor(); @@ -354,8 +366,8 @@ public class CanalController { InstanceConfig oldConfig = instanceConfigs.put(destination, config); if (oldConfig != null) { - logger.warn("destination:{} old config:{} has replace by new config:{}", new Object[] { destination, - oldConfig, config }); + logger + .warn("destination:{} old config:{} has replace by new config:{}", destination, oldConfig, config); } } } @@ -514,4 +526,31 @@ public class CanalController { } } + public CanalMQStarter getCanalMQStarter() { + return canalMQStarter; + } + + public void setCanalMQStarter(CanalMQStarter canalMQStarter) { + this.canalMQStarter = canalMQStarter; + } + + public static MQProperties getMQProperties(Properties properties) { + MQProperties mqProperties = new MQProperties(); + mqProperties.setServers(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS)); + mqProperties + .setRetries(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_RETRIES))); + mqProperties + .setBatchSize(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BATCHSIZE))); + mqProperties + .setLingerMs(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_LINGERMS))); + mqProperties.setBufferMemory( + Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY))); + mqProperties.setCanalBatchSize( + Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALBATCHSIZE))); + mqProperties.setCanalGetTimeout( + Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALGETTIMEOUT))); + mqProperties.setFlatMessage( + Boolean.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE))); + return mqProperties; + } } diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 3277c8c1..8ad3d06b 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -71,10 +71,9 @@ public class CanalLauncher { }); if (canalMQProducer != null) { - CanalMQStarter canalServerStarter = new CanalMQStarter(canalMQProducer); - if (canalServerStarter != null) { - canalServerStarter.init(); - } + CanalMQStarter canalMQStarter = new CanalMQStarter(canalMQProducer); + canalMQStarter.start(CanalController.getMQProperties(properties)); + controller.setCanalMQStarter(canalMQStarter); } } catch (Throwable e) { logger.error("## Something goes wrong when starting up the canal Server:", e); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index b5284dec..0ff5997a 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -91,9 +91,22 @@ canal.auto.scan.interval = 5 canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml #canal.instance.tsdb.spring.xml=classpath:spring/tsdb/mysql-tsdb.xml -canal.instance.global.mode = spring +canal.instance.global.mode = spring canal.instance.global.lazy = false #canal.instance.global.manager.address = 127.0.0.1:1099 #canal.instance.global.spring.xml = classpath:spring/memory-instance.xml canal.instance.global.spring.xml = classpath:spring/file-instance.xml #canal.instance.global.spring.xml = classpath:spring/default-instance.xml + +################################################## +######### MQ ############# +################################################## +canal.mq.servers=slave1:6667 +canal.mq.retries=0 +canal.mq.batchSize=16384 +canal.mq.lingerMs=1 +canal.mq.bufferMemory=33554432 + +canal.mq.canalBatchSize=50 +canal.mq.canalGetTimeout=100 +canal.mq.flatMessage=true \ No newline at end of file diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index 0caef6d2..c2e42618 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -18,7 +18,7 @@ canal.instance.rds.secretkey= canal.instance.rds.instanceId= # table meta tsdb info -canal.instance.tsdb.enable=true +canal.instance.tsdb.enable=false #canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb #canal.instance.tsdb.dbUsername=canal #canal.instance.tsdb.dbPassword=canal @@ -30,8 +30,8 @@ canal.instance.tsdb.enable=true #canal.instance.standby.gtid= # username/password -canal.instance.dbUsername=canal -canal.instance.dbPassword=canal +canal.instance.dbUsername=root +canal.instance.dbPassword=121212 canal.instance.connectionCharset = UTF-8 canal.instance.defaultDatabaseName =test # enable druid Decrypt database password @@ -42,4 +42,13 @@ canal.instance.enableDruid=false canal.instance.filter.regex=.*\\..* # table black regex canal.instance.filter.black.regex= + +# mq config +canal.mq.topic=example +# 但分区指定分区0 +canal.mq.partition=0 +# 多分区指定分区数量 +#canal.mq.partitionsNum=3 +# 多分区指定库表主键, 规则: 库名.表名:唯一主键, 多个按逗号分隔 +#canal.mq.partitionHash=mytest.person:id,mytest.role:id ################################################# \ No newline at end of file diff --git a/deployer/src/main/resources/mq.yml b/deployer/src/main/resources/mq.yml deleted file mode 100644 index 1797bd9b..00000000 --- a/deployer/src/main/resources/mq.yml +++ /dev/null @@ -1,23 +0,0 @@ -servers: slave1:6667 #for rocketmq: means the nameserver -retries: 0 -batchSize: 16384 -lingerMs: 1 -bufferMemory: 33554432 - -# Canal的batch size, 默认50K, 由于kafka最大消息体限制请勿超过1M(900K以下) -canalBatchSize: 50 -# Canal get数据的超时时间, 单位: 毫秒, 0为不限超时 -canalGetTimeout: 100 -flatMessage: true - -canalDestinations: - - canalDestination: example - topic: example - partition: -# #对应topic分区数量 -# partitionsNum: 3 -# partitionHash: -# #库名.表名: 唯一主键 -# mytest.person: id - - diff --git a/deployer/src/main/resources/spring/default-instance.xml b/deployer/src/main/resources/spring/default-instance.xml index f8c19bd0..f312b1ce 100644 --- a/deployer/src/main/resources/spring/default-instance.xml +++ b/deployer/src/main/resources/spring/default-instance.xml @@ -29,6 +29,9 @@ + + + @@ -187,4 +190,11 @@ + + + + + + + diff --git a/deployer/src/main/resources/spring/file-instance.xml b/deployer/src/main/resources/spring/file-instance.xml index 5524f73f..3b6d6a8d 100644 --- a/deployer/src/main/resources/spring/file-instance.xml +++ b/deployer/src/main/resources/spring/file-instance.xml @@ -29,6 +29,9 @@ + + + @@ -172,4 +175,11 @@ + + + + + + + diff --git a/deployer/src/main/resources/spring/group-instance.xml b/deployer/src/main/resources/spring/group-instance.xml index e614b86c..d9c61fb8 100644 --- a/deployer/src/main/resources/spring/group-instance.xml +++ b/deployer/src/main/resources/spring/group-instance.xml @@ -29,6 +29,9 @@ + + + @@ -262,4 +265,11 @@ + + + + + + + diff --git a/deployer/src/main/resources/spring/memory-instance.xml b/deployer/src/main/resources/spring/memory-instance.xml index 8fdfb178..9260c166 100644 --- a/deployer/src/main/resources/spring/memory-instance.xml +++ b/deployer/src/main/resources/spring/memory-instance.xml @@ -29,6 +29,9 @@ + + + @@ -160,4 +163,11 @@ + + + + + + + diff --git a/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/AbstractCanalInstance.java b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/AbstractCanalInstance.java index 9143a626..b1b64d5b 100644 --- a/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/AbstractCanalInstance.java +++ b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/AbstractCanalInstance.java @@ -38,6 +38,9 @@ public class AbstractCanalInstance extends AbstractCanalLifeCycle implements Can protected CanalEventSink> eventSink; // 链接parse和store的桥接器 protected CanalMetaManager metaManager; // 消费信息管理器 protected CanalAlarmHandler alarmHandler; // alarm报警机制 + protected CanalMQConfig mqConfig; // mq的配置 + + @Override public boolean subscribeChange(ClientIdentity identity) { @@ -241,4 +244,9 @@ public class AbstractCanalInstance extends AbstractCanalLifeCycle implements Can public CanalAlarmHandler getAlarmHandler() { return alarmHandler; } + + @Override + public CanalMQConfig getMqConfig() { + return mqConfig; + } } diff --git a/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalInstance.java b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalInstance.java index 3bd0464b..3d1a08c2 100644 --- a/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalInstance.java +++ b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalInstance.java @@ -32,4 +32,6 @@ public interface CanalInstance extends CanalLifeCycle { * 客户端发生订阅/取消订阅行为 */ boolean subscribeChange(ClientIdentity identity); + + CanalMQConfig getMqConfig(); } diff --git a/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java new file mode 100644 index 00000000..c275f150 --- /dev/null +++ b/instance/core/src/main/java/com/alibaba/otter/canal/instance/core/CanalMQConfig.java @@ -0,0 +1,68 @@ +package com.alibaba.otter.canal.instance.core; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class CanalMQConfig { + + private String topic; + private Integer partition; + private Integer partitionsNum; + private String partitionHash; + + private volatile Map partitionHashProperties; + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public Integer getPartition() { + return partition; + } + + public void setPartition(Integer partition) { + this.partition = partition; + } + + public Integer getPartitionsNum() { + return partitionsNum; + } + + public void setPartitionsNum(Integer partitionsNum) { + this.partitionsNum = partitionsNum; + } + + public String getPartitionHash() { + return partitionHash; + } + + public void setPartitionHash(String partitionHash) { + this.partitionHash = partitionHash; + } + + public Map getPartitionHashProperties() { + if (partitionHashProperties == null) { + synchronized (CanalMQConfig.class) { + if (partitionHashProperties == null) { + if (partitionHash != null) { + partitionHashProperties = new LinkedHashMap<>(); + String[] items = partitionHash.split(","); + for (String item : items) { + int i = item.indexOf(":"); + if (i > -1) { + String dbTable = item.substring(0, i).trim(); + String pk = item.substring(i + 1).trim(); + partitionHashProperties.put(dbTable, pk); + } + } + } + } + } + } + return partitionHashProperties; + } +} diff --git a/instance/spring/src/main/java/com/alibaba/otter/canal/instance/spring/CanalInstanceWithSpring.java b/instance/spring/src/main/java/com/alibaba/otter/canal/instance/spring/CanalInstanceWithSpring.java index 79693c4c..69672a6c 100644 --- a/instance/spring/src/main/java/com/alibaba/otter/canal/instance/spring/CanalInstanceWithSpring.java +++ b/instance/spring/src/main/java/com/alibaba/otter/canal/instance/spring/CanalInstanceWithSpring.java @@ -2,6 +2,8 @@ package com.alibaba.otter.canal.instance.spring; import java.util.List; +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.instance.core.CanalMQConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,4 +58,8 @@ public class CanalInstanceWithSpring extends AbstractCanalInstance { this.alarmHandler = alarmHandler; } + public void setMqConfig(CanalMQConfig mqConfig){ + this.mqConfig = mqConfig; + } + } diff --git a/server/pom.xml b/server/pom.xml index cc4b63d0..3d3084e7 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -26,11 +26,6 @@ ${project.version} - - org.yaml - snakeyaml - 1.17 - org.apache.kafka kafka_2.11 diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index 14db1deb..c61710e1 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -1,7 +1,5 @@ package com.alibaba.otter.canal.common; -import java.util.ArrayList; -import java.util.List; import java.util.Map; /** @@ -12,18 +10,19 @@ import java.util.Map; */ public class MQProperties { - private String servers = "127.0.0.1:6667"; - private int retries = 0; - private int batchSize = 16384; - private int lingerMs = 1; - private long bufferMemory = 33554432L; - private boolean filterTransactionEntry = true; - private String producerGroup = "Canal-Producer"; - private int canalBatchSize = 50; - private Long canalGetTimeout = 100L; - private boolean flatMessage = true; + private String servers = "127.0.0.1:6667"; + private int retries = 0; + private int batchSize = 16384; + private int lingerMs = 1; + private long bufferMemory = 33554432L; + private boolean filterTransactionEntry = true; + private String producerGroup = "Canal-Producer"; + private int canalBatchSize = 50; + private Long canalGetTimeout = 100L; + private boolean flatMessage = true; - private List canalDestinations = new ArrayList(); + // private List canalDestinations = new + // ArrayList(); public static class CanalDestination { @@ -138,14 +137,6 @@ public class MQProperties { this.flatMessage = flatMessage; } - public List getCanalDestinations() { - return canalDestinations; - } - - public void setCanalDestinations(List canalDestinations) { - this.canalDestinations = canalDestinations; - } - public boolean isFilterTransactionEntry() { return filterTransactionEntry; } diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index 2e861d3f..32cfb762 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -1,17 +1,15 @@ package com.alibaba.otter.canal.server; -import java.io.FileInputStream; -import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.Yaml; import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.instance.core.CanalInstance; +import com.alibaba.otter.canal.instance.core.CanalMQConfig; import com.alibaba.otter.canal.kafka.CanalKafkaProducer; import com.alibaba.otter.canal.protocol.ClientIdentity; import com.alibaba.otter.canal.protocol.Message; @@ -20,37 +18,28 @@ import com.alibaba.otter.canal.spi.CanalMQProducer; public class CanalMQStarter { - private static final Logger logger = LoggerFactory.getLogger(CanalMQStarter.class); + private static final Logger logger = LoggerFactory.getLogger(CanalMQStarter.class); - private static final String CLASSPATH_URL_PREFIX = "classpath:"; + private volatile boolean running = false; - private volatile boolean running = false; + private ExecutorService executorService; - private ExecutorService executorService; + private CanalMQProducer canalMQProducer; - private CanalMQProducer canalMQProducer; + private MQProperties properties; - private MQProperties properties; + private CanalServerWithEmbedded canalServer; public CanalMQStarter(CanalMQProducer canalMQProducer){ this.canalMQProducer = canalMQProducer; } - public void init() { + public synchronized void start(MQProperties properties) { try { - logger.info("## load MQ configurations"); - String conf = System.getProperty("mq.conf", "classpath:mq.yml"); - - if (conf.startsWith(CLASSPATH_URL_PREFIX)) { - conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX); - properties = new Yaml().loadAs(CanalMQStarter.class.getClassLoader().getResourceAsStream(conf), - MQProperties.class); - } else { - properties = new Yaml().loadAs(new FileInputStream(conf), MQProperties.class); + if (running) { + return; } - - // 初始化 kafka producer - // canalMQProducer = new CanalKafkaProducer(); + this.properties = properties; canalMQProducer.init(properties); // set filterTransactionEntry if (properties.isFilterTransactionEntry()) { @@ -62,36 +51,34 @@ public class CanalMQStarter { System.setProperty("canal.instance.memory.rawEntry", "false"); } + canalServer = CanalServerWithEmbedded.instance(); + // 对应每个instance启动一个worker线程 - List destinations = properties.getCanalDestinations(); - - executorService = Executors.newFixedThreadPool(destinations.size()); - + executorService = Executors.newFixedThreadPool(canalServer.getCanalInstances().size()); logger.info("## start the MQ workers."); - for (final MQProperties.CanalDestination destination : destinations) { + for (final CanalInstance canalInstance : canalServer.getCanalInstances().values()) { executorService.execute(new Runnable() { @Override public void run() { + MQProperties.CanalDestination destination = new MQProperties.CanalDestination(); + destination.setCanalDestination(canalInstance.getDestination()); + CanalMQConfig mqConfig = canalInstance.getMqConfig(); + destination.setTopic(mqConfig.getTopic()); + destination.setPartition(mqConfig.getPartition()); + destination.setPartitionsNum(mqConfig.getPartitionsNum()); + destination.setPartitionHash(mqConfig.getPartitionHashProperties()); worker(destination); } }); } + running = true; logger.info("## the MQ workers is running now ......"); Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { - try { - logger.info("## stop the MQ workers"); - running = false; - executorService.shutdown(); - canalMQProducer.stop(); - } catch (Throwable e) { - logger.warn("##something goes wrong when stopping MQ workers:", e); - } finally { - logger.info("## canal MQ is down."); - } + stop(); } }); @@ -102,15 +89,31 @@ public class CanalMQStarter { } } + public synchronized void stop() { + if (!running) { + return; + } + try { + logger.info("## stop the MQ workers"); + running = false; + executorService.shutdown(); + canalMQProducer.stop(); + } catch (Throwable e) { + logger.warn("##something goes wrong when stopping MQ workers:", e); + } finally { + logger.info("## canal MQ is down."); + } + } + private void worker(MQProperties.CanalDestination destination) { while (!running) ; - logger.info("## start the canal consumer: {}.", destination.getCanalDestination()); - final CanalServerWithEmbedded server = CanalServerWithEmbedded.instance(); + logger.info("## start the MQ producer: {}.", destination.getCanalDestination()); + final ClientIdentity clientIdentity = new ClientIdentity(destination.getCanalDestination(), (short) 1001, ""); while (running) { try { - if (!server.getCanalInstances().containsKey(clientIdentity.getDestination())) { + if (!canalServer.getCanalInstances().containsKey(clientIdentity.getDestination())) { try { Thread.sleep(3000); } catch (InterruptedException e) { @@ -118,17 +121,18 @@ public class CanalMQStarter { } continue; } - server.subscribe(clientIdentity); - logger.info("## the canal consumer {} is running now ......", destination.getCanalDestination()); + canalServer.subscribe(clientIdentity); + logger.info("## the MQ producer: {} is running now ......", destination.getCanalDestination()); Long getTimeout = properties.getCanalGetTimeout(); int getBatchSize = properties.getCanalBatchSize(); while (running) { Message message; if (getTimeout != null && getTimeout > 0) { - message = server.getWithoutAck(clientIdentity, getBatchSize, getTimeout, TimeUnit.MILLISECONDS); + message = canalServer + .getWithoutAck(clientIdentity, getBatchSize, getTimeout, TimeUnit.MILLISECONDS); } else { - message = server.getWithoutAck(clientIdentity, getBatchSize); + message = canalServer.getWithoutAck(clientIdentity, getBatchSize); } final long batchId = message.getId(); @@ -139,12 +143,12 @@ public class CanalMQStarter { @Override public void commit() { - server.ack(clientIdentity, batchId); // 提交确认 + canalServer.ack(clientIdentity, batchId); // 提交确认 } @Override public void rollback() { - server.rollback(clientIdentity, batchId); + canalServer.rollback(clientIdentity, batchId); } }); // 发送message到topic } else { From 2a38bca34ba322b547155b7fd14a3f60f88b1bf7 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 9 Nov 2018 16:52:34 +0800 Subject: [PATCH 299/360] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/example/instance.properties | 2 +- .../main/java/com/alibaba/otter/canal/common/MQProperties.java | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index c2e42618..55566641 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -45,7 +45,7 @@ canal.instance.filter.black.regex= # mq config canal.mq.topic=example -# 但分区指定分区0 +# 多分区指定分区0 canal.mq.partition=0 # 多分区指定分区数量 #canal.mq.partitionsNum=3 diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index c61710e1..29375e2f 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -21,9 +21,6 @@ public class MQProperties { private Long canalGetTimeout = 100L; private boolean flatMessage = true; - // private List canalDestinations = new - // ArrayList(); - public static class CanalDestination { private String canalDestination; From 0c3f7a806e491efe7a83d8160b074019ed096952 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 9 Nov 2018 17:06:42 +0800 Subject: [PATCH 300/360] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/example/instance.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index 55566641..ee3ed601 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -18,7 +18,7 @@ canal.instance.rds.secretkey= canal.instance.rds.instanceId= # table meta tsdb info -canal.instance.tsdb.enable=false +canal.instance.tsdb.enable=true #canal.instance.tsdb.url=jdbc:mysql://127.0.0.1:3306/canal_tsdb #canal.instance.tsdb.dbUsername=canal #canal.instance.tsdb.dbPassword=canal @@ -30,8 +30,8 @@ canal.instance.tsdb.enable=false #canal.instance.standby.gtid= # username/password -canal.instance.dbUsername=root -canal.instance.dbPassword=121212 +canal.instance.dbUsername=canal +canal.instance.dbPassword=canal canal.instance.connectionCharset = UTF-8 canal.instance.defaultDatabaseName =test # enable druid Decrypt database password @@ -45,7 +45,7 @@ canal.instance.filter.black.regex= # mq config canal.mq.topic=example -# 多分区指定分区0 +# 单分区指定分区0 canal.mq.partition=0 # 多分区指定分区数量 #canal.mq.partitionsNum=3 From 7d7c82ce9180708ea173b644671248f38ecd4d46 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 9 Nov 2018 18:12:04 +0800 Subject: [PATCH 301/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9mq=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=92=88=E5=AF=B9destination=E7=BA=A7=E5=88=AB=E9=87=8D?= =?UTF-8?q?=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/deployer/CanalController.java | 24 +--- .../otter/canal/deployer/CanalLauncher.java | 19 ++- .../otter/canal/server/CanalMQStarter.java | 110 ++++++++++++------ 3 files changed, 92 insertions(+), 61 deletions(-) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 535dbd44..9161bff0 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -226,7 +226,7 @@ public class CanalController { if (!config.getLazy() && !runningMonitor.isStart()) { runningMonitor.start(); if (canalMQStarter != null) { - canalMQStarter.start(getMQProperties(properties)); + canalMQStarter.startDestination(destination); } } } @@ -237,7 +237,7 @@ public class CanalController { InstanceConfig config = instanceConfigs.remove(destination); if (config != null) { if (canalMQStarter != null) { - canalMQStarter.stop(); + canalMQStarter.stopDestination(destination); } embededCanalServer.stop(destination); ServerRunningMonitor runningMonitor = ServerRunningMonitors.getRunningMonitor(destination); @@ -533,24 +533,4 @@ public class CanalController { public void setCanalMQStarter(CanalMQStarter canalMQStarter) { this.canalMQStarter = canalMQStarter; } - - public static MQProperties getMQProperties(Properties properties) { - MQProperties mqProperties = new MQProperties(); - mqProperties.setServers(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS)); - mqProperties - .setRetries(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_RETRIES))); - mqProperties - .setBatchSize(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BATCHSIZE))); - mqProperties - .setLingerMs(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_LINGERMS))); - mqProperties.setBufferMemory( - Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY))); - mqProperties.setCanalBatchSize( - Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALBATCHSIZE))); - mqProperties.setCanalGetTimeout( - Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALGETTIMEOUT))); - mqProperties.setFlatMessage( - Boolean.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE))); - return mqProperties; - } } diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 8ad3d06b..d1663305 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -3,6 +3,7 @@ package com.alibaba.otter.canal.deployer; import java.io.FileInputStream; import java.util.Properties; +import com.alibaba.otter.canal.common.MQProperties; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,7 +73,23 @@ public class CanalLauncher { if (canalMQProducer != null) { CanalMQStarter canalMQStarter = new CanalMQStarter(canalMQProducer); - canalMQStarter.start(CanalController.getMQProperties(properties)); + MQProperties mqProperties = new MQProperties(); + mqProperties.setServers(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS)); + mqProperties + .setRetries(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_RETRIES))); + mqProperties + .setBatchSize(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BATCHSIZE))); + mqProperties + .setLingerMs(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_LINGERMS))); + mqProperties.setBufferMemory( + Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY))); + mqProperties.setCanalBatchSize( + Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALBATCHSIZE))); + mqProperties.setCanalGetTimeout( + Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALGETTIMEOUT))); + mqProperties.setFlatMessage( + Boolean.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE))); + canalMQStarter.start(mqProperties); controller.setCanalMQStarter(canalMQStarter); } } catch (Throwable e) { diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index 32cfb762..d10fad77 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -1,8 +1,11 @@ package com.alibaba.otter.canal.server; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,17 +21,19 @@ import com.alibaba.otter.canal.spi.CanalMQProducer; public class CanalMQStarter { - private static final Logger logger = LoggerFactory.getLogger(CanalMQStarter.class); + private static final Logger logger = LoggerFactory.getLogger(CanalMQStarter.class); - private volatile boolean running = false; + private volatile boolean running = false; - private ExecutorService executorService; + private ExecutorService executorService; - private CanalMQProducer canalMQProducer; + private CanalMQProducer canalMQProducer; - private MQProperties properties; + private MQProperties properties; - private CanalServerWithEmbedded canalServer; + private CanalServerWithEmbedded canalServer; + + private Map canalMQWorks = new ConcurrentHashMap<>(); public CanalMQStarter(CanalMQProducer canalMQProducer){ this.canalMQProducer = canalMQProducer; @@ -57,20 +62,9 @@ public class CanalMQStarter { executorService = Executors.newFixedThreadPool(canalServer.getCanalInstances().size()); logger.info("## start the MQ workers."); for (final CanalInstance canalInstance : canalServer.getCanalInstances().values()) { - executorService.execute(new Runnable() { - - @Override - public void run() { - MQProperties.CanalDestination destination = new MQProperties.CanalDestination(); - destination.setCanalDestination(canalInstance.getDestination()); - CanalMQConfig mqConfig = canalInstance.getMqConfig(); - destination.setTopic(mqConfig.getTopic()); - destination.setPartition(mqConfig.getPartition()); - destination.setPartitionsNum(mqConfig.getPartitionsNum()); - destination.setPartitionHash(mqConfig.getPartitionHashProperties()); - worker(destination); - } - }); + CanalMQRunnable canalMQRunnable = new CanalMQRunnable(canalInstance); + canalMQWorks.put(canalInstance.getDestination(), canalMQRunnable); + executorService.execute(canalMQRunnable); } running = true; @@ -78,7 +72,16 @@ public class CanalMQStarter { Runtime.getRuntime().addShutdownHook(new Thread() { public void run() { - stop(); + try { + logger.info("## stop the MQ workers"); + running = false; + executorService.shutdown(); + canalMQProducer.stop(); + } catch (Throwable e) { + logger.warn("##something goes wrong when stopping MQ workers:", e); + } finally { + logger.info("## canal MQ is down."); + } } }); @@ -89,29 +92,33 @@ public class CanalMQStarter { } } - public synchronized void stop() { - if (!running) { - return; - } - try { - logger.info("## stop the MQ workers"); - running = false; - executorService.shutdown(); - canalMQProducer.stop(); - } catch (Throwable e) { - logger.warn("##something goes wrong when stopping MQ workers:", e); - } finally { - logger.info("## canal MQ is down."); + public synchronized void startDestination(String destination) { + CanalInstance canalInstance = canalServer.getCanalInstances().get(destination); + if (canalInstance != null) { + stopDestination(destination); + CanalMQRunnable canalMQRunnable = new CanalMQRunnable(canalInstance); + canalMQWorks.put(canalInstance.getDestination(), canalMQRunnable); + executorService.execute(canalMQRunnable); + logger.info("## Start the MQ work of destination:" + destination); } } - private void worker(MQProperties.CanalDestination destination) { - while (!running) + public synchronized void stopDestination(String destination) { + CanalMQRunnable canalMQRunable = canalMQWorks.get(destination); + if (canalMQRunable != null) { + canalMQRunable.stop(); + canalMQWorks.remove(destination); + logger.info("## Stop the MQ work of destination:" + destination); + } + } + + private void worker(MQProperties.CanalDestination destination, AtomicBoolean destinationRunning) { + while (!running || !destinationRunning.get()) ; logger.info("## start the MQ producer: {}.", destination.getCanalDestination()); final ClientIdentity clientIdentity = new ClientIdentity(destination.getCanalDestination(), (short) 1001, ""); - while (running) { + while (running && destinationRunning.get()) { try { if (!canalServer.getCanalInstances().containsKey(clientIdentity.getDestination())) { try { @@ -126,7 +133,7 @@ public class CanalMQStarter { Long getTimeout = properties.getCanalGetTimeout(); int getBatchSize = properties.getCanalBatchSize(); - while (running) { + while (running && destinationRunning.get()) { Message message; if (getTimeout != null && getTimeout > 0) { message = canalServer @@ -168,4 +175,31 @@ public class CanalMQStarter { } } } + + private class CanalMQRunnable implements Runnable { + + private CanalInstance canalInstance; + + public CanalMQRunnable(CanalInstance canalInstance){ + this.canalInstance = canalInstance; + } + + private AtomicBoolean running = new AtomicBoolean(true); + + @Override + public void run() { + MQProperties.CanalDestination destination = new MQProperties.CanalDestination(); + destination.setCanalDestination(canalInstance.getDestination()); + CanalMQConfig mqConfig = canalInstance.getMqConfig(); + destination.setTopic(mqConfig.getTopic()); + destination.setPartition(mqConfig.getPartition()); + destination.setPartitionsNum(mqConfig.getPartitionsNum()); + destination.setPartitionHash(mqConfig.getPartitionHashProperties()); + worker(destination, running); + } + + public void stop() { + running.set(false); + } + } } From 2d41bb99607ceb342be19cfc23aa4f275c9532cf Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 9 Nov 2018 18:19:51 +0800 Subject: [PATCH 302/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9mq=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=92=88=E5=AF=B9destination=E7=BA=A7=E5=88=AB=E9=87=8D?= =?UTF-8?q?=E5=90=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/otter/canal/server/CanalMQStarter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index d10fad77..ad4fc266 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -59,7 +59,7 @@ public class CanalMQStarter { canalServer = CanalServerWithEmbedded.instance(); // 对应每个instance启动一个worker线程 - executorService = Executors.newFixedThreadPool(canalServer.getCanalInstances().size()); + executorService = Executors.newCachedThreadPool(); logger.info("## start the MQ workers."); for (final CanalInstance canalInstance : canalServer.getCanalInstances().values()) { CanalMQRunnable canalMQRunnable = new CanalMQRunnable(canalInstance); From 417e04f65584d2758b878249c060b2788f79c7ed Mon Sep 17 00:00:00 2001 From: wuwo Date: Fri, 9 Nov 2018 19:23:03 +0800 Subject: [PATCH 303/360] fixed DatabaseTableMeta#compareTableMeta --- .../parse/inbound/mysql/tsdb/DatabaseTableMeta.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index ad15a695..068db5ed 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -502,11 +502,13 @@ public class DatabaseTableMeta implements TableMetaTSDB { return false; } + String sign = sourceField.isUnsigned() ? "unsigned" : "signed"; + String sourceColumnType = StringUtils.removeEndIgnoreCase(sourceField.getColumnType(), sign).trim(); + String targetColumnType = StringUtils.removeEndIgnoreCase(targetField.getColumnType(), sign).trim(); + boolean columnTypeCompare = false; - columnTypeCompare |= StringUtils.containsIgnoreCase(sourceField.getColumnType(), - targetField.getColumnType()); - columnTypeCompare |= StringUtils.containsIgnoreCase(targetField.getColumnType(), - sourceField.getColumnType()); + columnTypeCompare |= StringUtils.containsIgnoreCase(sourceColumnType, targetColumnType); + columnTypeCompare |= StringUtils.containsIgnoreCase(targetColumnType, sourceColumnType); if (!columnTypeCompare) { return false; } From d7ab6e2dc109325c7f584c3d66d075f2640ce42e Mon Sep 17 00:00:00 2001 From: machey Date: Sat, 10 Nov 2018 23:55:09 +0800 Subject: [PATCH 304/360] =?UTF-8?q?rdb=E5=90=8C=E6=AD=A5=E9=80=82=E9=85=8D?= =?UTF-8?q?=E5=A4=9A=E6=95=B0=E6=8D=AE=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/CanalClientConfig.java | 17 ++++++- .../adapter/support/ExtensionLoader.java | 16 +++++++ .../adapter/support/OuterAdapterConfig.java | 10 ++++ .../launcher/loader/CanalAdapterLoader.java | 8 +++- .../src/main/resources/application.yml | 29 +++++------ client-adapter/rdb/pom.xml | 48 +++++++++---------- .../canal/client/adapter/rdb/RdbAdapter.java | 35 +++++++------- .../adapter/rdb/adapters/MysqlAdapter.java | 8 ---- .../adapter/rdb/adapters/OracleAdapter.java | 8 ---- .../rdb/adapters/PostgresqlAdapter.java | 8 ---- .../rdb/adapters/SqlserverAdapter.java | 8 ---- .../adapter/rdb/config/MappingConfig.java | 16 +++++-- ...ba.otter.canal.client.adapter.OuterAdapter | 5 +- .../main/resources/postgresql/mytest_user.yml | 15 ------ .../resources/{oracle => rdb}/mytest_user.yml | 1 + .../client/adapter/rdb/test/sync/Common.java | 6 +-- .../adapter/rdb/test/sync/OracleSyncTest.java | 2 +- 17 files changed, 125 insertions(+), 115 deletions(-) delete mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java delete mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java delete mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java delete mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java delete mode 100644 client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml rename client-adapter/rdb/src/main/resources/{oracle => rdb}/mytest_user.yml (90%) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 3d16a067..97a7a426 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -1,7 +1,9 @@ package com.alibaba.otter.canal.client.adapter.support; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; /** * 配置信息类 @@ -129,7 +131,9 @@ public class CanalClientConfig { public static class Group { - private List outAdapters; // 适配器列表 + private List outAdapters; // 适配器列表 + + private Map outAdaptersMap = new LinkedHashMap<>(); public List getOutAdapters() { return outAdapters; @@ -137,6 +141,17 @@ public class CanalClientConfig { public void setOutAdapters(List outAdapters) { this.outAdapters = outAdapters; + if (outAdapters != null) { + outAdapters.forEach(outAdapter -> outAdaptersMap.put(outAdapter.getKey(), outAdapter)); + } + } + + public Map getOutAdaptersMap() { + return outAdaptersMap; + } + + public void setOutAdaptersMap(Map outAdaptersMap) { + this.outAdaptersMap = outAdaptersMap; } } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index cf69fde5..ce7de834 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -148,6 +148,22 @@ public class ExtensionLoader { } } + @SuppressWarnings("unchecked") + public T newInstance(String name) { + Class clazz = getExtensionClasses().get(name); + if (clazz == null) { + throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + + ") could not be instantiated: class could not be found"); + } + try { + return (T) clazz.newInstance(); + } catch (Throwable t) { + throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + + ") could not be instantiated: " + t.getMessage(), + t); + } + } + private Map> getExtensionClasses() { Map> classes = cachedClasses.get(); if (classes == null) { diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java index 4e73791e..a767b203 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/OuterAdapterConfig.java @@ -12,6 +12,8 @@ public class OuterAdapterConfig { private String name; // 适配器名称, 如: logger, hbase, es + private String key; // 适配器唯一键 + private String hosts; // 适配器内部的地址, 比如对应es该参数可以填写es的server地址 private String zkHosts; // 适配器内部的ZK地址, 比如对应HBase该参数可以填写HBase对应的ZK地址 @@ -26,6 +28,14 @@ public class OuterAdapterConfig { this.name = name; } + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + public String getHosts() { return hosts; } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index df641c45..78eab682 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -123,7 +123,12 @@ public class CanalAdapterLoader { private void loadConnector(OuterAdapterConfig config, List canalOutConnectors) { try { - OuterAdapter adapter = loader.getExtension(config.getName()); + OuterAdapter adapter; + if ("rdb".equalsIgnoreCase(config.getName())) { + adapter = loader.newInstance(config.getName()); + } else { + adapter = loader.getExtension(config.getName()); + } ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 替换ClassLoader Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader()); @@ -133,6 +138,7 @@ public class CanalAdapterLoader { logger.info("Load canal adapter: {} succeed", config.getName()); } catch (Exception e) { logger.error("Load canal adapter: {} failed", config.getName(), e); + System.exit(0); } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 2de61937..9595e3fb 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -21,6 +21,20 @@ spring: # groups: # - outAdapters: # - name: logger +# - name: rdb +# key: oracle1 +# properties: +# jdbc.driverClassName: oracle.jdbc.OracleDriver +# jdbc.url: jdbc:oracle:thin:@127.0.0.1:1521:orcl +# jdbc.username: mytest +# jdbc.password: m121212 +# - name: rdb +# key: postgres1 +# properties: +# jdbc.driverClassName: org.postgresql.Driver +# jdbc.url: jdbc:postgresql://localhost:5432/postgres +# jdbc.username: postgres +# jdbc.password: 121212 # - name: hbase # properties: # hbase.zookeeper.quorum: 127.0.0.1 @@ -37,18 +51,6 @@ spring: # - groupId: g2 # outAdapters: # - name: logger -# - name: oracle -# properties: -# jdbc.driverClassName: oracle.jdbc.OracleDriver -# jdbc.url: jdbc:oracle:thin:@127.0.0.1:49161:XE -# jdbc.username: mytest -# jdbc.password: m121212 -# - name: postgresql -# properties: -# jdbc.driverClassName: org.postgresql.Driver -# jdbc.url: jdbc:postgresql://localhost:5432/postgres -# jdbc.username: postgres -# jdbc.password: 121212 # mqTopics: # - mqMode: rocketmq # topic: example @@ -66,5 +68,4 @@ spring: # adapterConfigs: # - hbase/mytest_person2.yml # - es/mytest_user.yml -# - oracle/mytest_user.yml -# - postgresql/mytest_user.yml +# - rdb/mytest_user.yml diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml index 951579a9..994d28aa 100644 --- a/client-adapter/rdb/pom.xml +++ b/client-adapter/rdb/pom.xml @@ -27,30 +27,30 @@ provided - - mysql - mysql-connector-java - 5.1.40 - test - - - org.postgresql - postgresql - 42.1.4 - test - - - com.oracle - ojdbc6 - 11.2.0.4.0-atlassian-hosted - test - - - com.microsoft.sqlserver - mssql-jdbc - 7.0.0.jre8 - test - + + + + + + + + + + + + + + + + + + + + + + + + junit junit diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 5dd47a05..c07dee6b 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -19,12 +19,13 @@ import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; import com.alibaba.otter.canal.client.adapter.support.*; -public abstract class RdbAdapter implements OuterAdapter { +@SPI("rdb") +public class RdbAdapter implements OuterAdapter { - private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); + private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); - private volatile Map rdbMapping = null; // 文件名对应配置 - private volatile Map mappingConfigCache = null; // 库名-表名对应配置 + private volatile Map rdbMapping = new HashMap<>(); // 文件名对应配置 + private Map mappingConfigCache; // 库名-表名对应配置 private DruidDataSource dataSource; @@ -32,20 +33,20 @@ public abstract class RdbAdapter implements OuterAdapter { @Override public void init(OuterAdapterConfig configuration) { - if (mappingConfigCache == null) { - synchronized (MappingConfig.class) { - if (mappingConfigCache == null) { - SPI spi = this.getClass().getAnnotation(SPI.class); - rdbMapping = MappingConfigLoader.load(spi.value()); - mappingConfigCache = new HashMap<>(); - for (MappingConfig mappingConfig : rdbMapping.values()) { - mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." - + mappingConfig.getDbMapping().getDatabase() + "." - + mappingConfig.getDbMapping().getTable(), - mappingConfig); - } - } + SPI spi = this.getClass().getAnnotation(SPI.class); + Map rdbMappingTmp = MappingConfigLoader.load(spi.value()); + // 过滤其他key的配置 + rdbMappingTmp.forEach((key, mappingConfig) -> { + if (mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { + rdbMapping.put(key, mappingConfig); } + }); + mappingConfigCache = new HashMap<>(); + for (MappingConfig mappingConfig : rdbMapping.values()) { + mappingConfigCache + .put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getDbMapping().getDatabase() + "." + mappingConfig.getDbMapping().getTable(), + mappingConfig); } Map properties = configuration.getProperties(); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java deleted file mode 100644 index ea73b1c0..00000000 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/MysqlAdapter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.rdb.adapters; - -import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; -import com.alibaba.otter.canal.client.adapter.support.SPI; - -@SPI("mysql") -public class MysqlAdapter extends RdbAdapter { -} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java deleted file mode 100644 index fe6111a2..00000000 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/OracleAdapter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.rdb.adapters; - -import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; -import com.alibaba.otter.canal.client.adapter.support.SPI; - -@SPI("oracle") -public class OracleAdapter extends RdbAdapter { -} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java deleted file mode 100644 index 2cf4a444..00000000 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/PostgresqlAdapter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.rdb.adapters; - -import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; -import com.alibaba.otter.canal.client.adapter.support.SPI; - -@SPI("postgresql") -public class PostgresqlAdapter extends RdbAdapter { -} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java deleted file mode 100644 index 2fe7e0ed..00000000 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/adapters/SqlserverAdapter.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.rdb.adapters; - -import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; -import com.alibaba.otter.canal.client.adapter.support.SPI; - -@SPI("sqlserver") -public class SqlserverAdapter extends RdbAdapter { -} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index da7e6ef8..95623ea4 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -12,11 +12,13 @@ import java.util.Set; */ public class MappingConfig { - private String dataSourceKey; // 数据源key + private String dataSourceKey; // 数据源key - private String destination; // canal实例或MQ的topic + private String destination; // canal实例或MQ的topic - private DbMapping dbMapping; // db映射配置 + private String outerAdapterKey; // 对应适配器的key + + private DbMapping dbMapping; // db映射配置 public String getDataSourceKey() { return dataSourceKey; @@ -26,6 +28,14 @@ public class MappingConfig { this.dataSourceKey = dataSourceKey; } + public String getOuterAdapterKey() { + return outerAdapterKey; + } + + public void setOuterAdapterKey(String outerAdapterKey) { + this.outerAdapterKey = outerAdapterKey; + } + public DbMapping getDbMapping() { return dbMapping; } diff --git a/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter b/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter index c005f7fd..0a840275 100644 --- a/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter +++ b/client-adapter/rdb/src/main/resources/META-INF/canal/com.alibaba.otter.canal.client.adapter.OuterAdapter @@ -1,4 +1 @@ -mysql=com.alibaba.otter.canal.client.adapter.rdb.adapters.MysqlAdapter -postgresql=com.alibaba.otter.canal.client.adapter.rdb.adapters.PostgresqlAdapter -oracle=com.alibaba.otter.canal.client.adapter.rdb.adapters.OracleAdapter -sqlserver=com.alibaba.otter.canal.client.adapter.rdb.adapters.SqlserverAdapter \ No newline at end of file +rdb=com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter \ No newline at end of file diff --git a/client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml b/client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml deleted file mode 100644 index 1b4eaed7..00000000 --- a/client-adapter/rdb/src/main/resources/postgresql/mytest_user.yml +++ /dev/null @@ -1,15 +0,0 @@ -dataSourceKey: defaultDS -destination: example -dbMapping: - database: mytest - table: user - targetTable: public.tb_user - targetPk: - id: id - mapAll: true -# targetColumns: -# id: -# name: -# role_id: -# c_time: -# test1: \ No newline at end of file diff --git a/client-adapter/rdb/src/main/resources/oracle/mytest_user.yml b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml similarity index 90% rename from client-adapter/rdb/src/main/resources/oracle/mytest_user.yml rename to client-adapter/rdb/src/main/resources/rdb/mytest_user.yml index a7bede03..56d8e518 100644 --- a/client-adapter/rdb/src/main/resources/oracle/mytest_user.yml +++ b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml @@ -1,5 +1,6 @@ dataSourceKey: defaultDS destination: example +outerAdapterKey: oracle1 dbMapping: database: mytest table: user diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java index 0682332a..4ae04643 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java @@ -1,7 +1,6 @@ package com.alibaba.otter.canal.client.adapter.rdb.test.sync; import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; -import com.alibaba.otter.canal.client.adapter.rdb.adapters.OracleAdapter; import com.alibaba.otter.canal.client.adapter.rdb.test.TestConstant; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; @@ -14,7 +13,8 @@ public class Common { DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig(); - outerAdapterConfig.setName("oracle"); + outerAdapterConfig.setName("rdb"); + outerAdapterConfig.setKey("oralce1"); Map properties = new HashMap<>(); properties.put("jdbc.driveClassName", "oracle.jdbc.OracleDriver"); properties.put("jdbc.url", "jdbc:oracle:thin:@127.0.0.1:49161:XE"); @@ -22,7 +22,7 @@ public class Common { properties.put("jdbc.password", "m121212"); outerAdapterConfig.setProperties(properties); - RdbAdapter adapter = new OracleAdapter(); + RdbAdapter adapter = new RdbAdapter(); adapter.init(outerAdapterConfig); return adapter; } diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java index 514ffaa5..5b8a45e3 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java @@ -15,7 +15,7 @@ public class OracleSyncTest { @Before public void init() { - AdapterConfigs.put("oracle", "mytest_user.yml"); + AdapterConfigs.put("rdb", "mytest_user.yml"); rdbAdapter = Common.init(); } From cc1574d9138b47b6724d53f38c0bbca15a18715b Mon Sep 17 00:00:00 2001 From: agapple Date: Sun, 11 Nov 2018 01:15:02 +0800 Subject: [PATCH 305/360] fixed adapter --- .../adapter/es/config/ESSyncConfigLoader.java | 8 +++- .../hbase/config/MappingConfigLoader.java | 2 +- .../launcher/src/main/assembly/release.xml | 8 ++-- .../launcher/src/main/bin/startup.bat | 2 +- .../launcher/src/main/bin/startup.sh | 2 +- .../rdb/config/MappingConfigLoader.java | 2 +- .../otter/canal/deployer/CanalLauncher.java | 39 +++++++++++-------- deployer/src/main/resources/canal.properties | 3 +- .../resources/example/instance.properties | 4 +- 9 files changed, 38 insertions(+), 32 deletions(-) diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java index 681537cf..81beede2 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java @@ -5,7 +5,11 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -99,7 +103,7 @@ public class ESSyncConfigLoader { InputStream in = null; try { // 先取本地文件,再取类路径 - File configFile = new File("../config/" + config); + File configFile = new File("../conf/" + config); if (configFile.exists()) { in = new FileInputStream(configFile); } else { diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index f2ee6994..2ac3a0d0 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -125,7 +125,7 @@ public class MappingConfigLoader { InputStream in = null; try { // 先取本地文件,再取类路径 - File configFile = new File("../config/" + config); + File configFile = new File("../conf/" + config); if (configFile.exists()) { in = new FileInputStream(configFile); } else { diff --git a/client-adapter/launcher/src/main/assembly/release.xml b/client-adapter/launcher/src/main/assembly/release.xml index 163f2015..bebad929 100644 --- a/client-adapter/launcher/src/main/assembly/release.xml +++ b/client-adapter/launcher/src/main/assembly/release.xml @@ -24,7 +24,7 @@ ./src/main/resources - /config + /conf **/* @@ -32,21 +32,21 @@ ../elasticsearch/src/main/resources/es - /config/es + /conf/es **/* ../hbase/src/main/resources/hbase - /config/hbase + /conf/hbase **/* ../rdb/src/main/resources/ - /config + /conf META-INF/** diff --git a/client-adapter/launcher/src/main/bin/startup.bat b/client-adapter/launcher/src/main/bin/startup.bat index 00555432..0c464dff 100755 --- a/client-adapter/launcher/src/main/bin/startup.bat +++ b/client-adapter/launcher/src/main/bin/startup.bat @@ -5,7 +5,7 @@ set ENV_PATH=.\ if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% -set conf_dir=%ENV_PATH%\..\config +set conf_dir=%ENV_PATH%\..\conf set CLASSPATH=%conf_dir% set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% diff --git a/client-adapter/launcher/src/main/bin/startup.sh b/client-adapter/launcher/src/main/bin/startup.sh index 001f7fb9..63255c59 100644 --- a/client-adapter/launcher/src/main/bin/startup.sh +++ b/client-adapter/launcher/src/main/bin/startup.sh @@ -55,7 +55,7 @@ ADAPTER_OPTS="-DappName=canal-adapter" for i in $base/lib/*; do CLASSPATH=$i:"$CLASSPATH"; done -CLASSPATH="$base/config:$CLASSPATH"; +CLASSPATH="$base/conf:$CLASSPATH"; echo "cd to $bin_abs_path for workaround relative path" cd $bin_abs_path diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java index 8658d45e..aefb467f 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java @@ -72,7 +72,7 @@ public class MappingConfigLoader { InputStream in = null; try { // 先取本地文件,再取类路径 - File configFile = new File("../config/" + config); + File configFile = new File("../conf/" + config); if (configFile.exists()) { in = new FileInputStream(configFile); } else { diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index d1663305..4b85f50c 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -3,11 +3,11 @@ package com.alibaba.otter.canal.deployer; import java.io.FileInputStream; import java.util.Properties; -import com.alibaba.otter.canal.common.MQProperties; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.kafka.CanalKafkaProducer; import com.alibaba.otter.canal.rocketmq.CanalRocketMQProducer; import com.alibaba.otter.canal.server.CanalMQStarter; @@ -73,22 +73,7 @@ public class CanalLauncher { if (canalMQProducer != null) { CanalMQStarter canalMQStarter = new CanalMQStarter(canalMQProducer); - MQProperties mqProperties = new MQProperties(); - mqProperties.setServers(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS)); - mqProperties - .setRetries(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_RETRIES))); - mqProperties - .setBatchSize(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BATCHSIZE))); - mqProperties - .setLingerMs(Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_LINGERMS))); - mqProperties.setBufferMemory( - Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY))); - mqProperties.setCanalBatchSize( - Integer.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALBATCHSIZE))); - mqProperties.setCanalGetTimeout( - Long.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALGETTIMEOUT))); - mqProperties.setFlatMessage( - Boolean.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE))); + MQProperties mqProperties = buildMQPosition(properties); canalMQStarter.start(mqProperties); controller.setCanalMQStarter(canalMQStarter); } @@ -98,6 +83,26 @@ public class CanalLauncher { } } + private static MQProperties buildMQPosition(Properties properties) { + MQProperties mqProperties = new MQProperties(); + mqProperties.setServers(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS)); + mqProperties.setRetries(Integer.valueOf(CanalController.getProperty(properties, + CanalConstants.CANAL_MQ_RETRIES))); + mqProperties.setBatchSize(Integer.valueOf(CanalController.getProperty(properties, + CanalConstants.CANAL_MQ_BATCHSIZE))); + mqProperties.setLingerMs(Integer.valueOf(CanalController.getProperty(properties, + CanalConstants.CANAL_MQ_LINGERMS))); + mqProperties.setBufferMemory(Long.valueOf(CanalController.getProperty(properties, + CanalConstants.CANAL_MQ_BUFFERMEMORY))); + mqProperties.setCanalBatchSize(Integer.valueOf(CanalController.getProperty(properties, + CanalConstants.CANAL_MQ_CANALBATCHSIZE))); + mqProperties.setCanalGetTimeout(Long.valueOf(CanalController.getProperty(properties, + CanalConstants.CANAL_MQ_CANALGETTIMEOUT))); + mqProperties.setFlatMessage(Boolean.valueOf(CanalController.getProperty(properties, + CanalConstants.CANAL_MQ_FLATMESSAGE))); + return mqProperties; + } + private static void setGlobalUncaughtExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 0ff5997a..cf2a225e 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -101,12 +101,11 @@ canal.instance.global.spring.xml = classpath:spring/file-instance.xml ################################################## ######### MQ ############# ################################################## -canal.mq.servers=slave1:6667 +canal.mq.servers=127.0.0.1:6667 canal.mq.retries=0 canal.mq.batchSize=16384 canal.mq.lingerMs=1 canal.mq.bufferMemory=33554432 - canal.mq.canalBatchSize=50 canal.mq.canalGetTimeout=100 canal.mq.flatMessage=true \ No newline at end of file diff --git a/deployer/src/main/resources/example/instance.properties b/deployer/src/main/resources/example/instance.properties index ee3ed601..3a9ac883 100644 --- a/deployer/src/main/resources/example/instance.properties +++ b/deployer/src/main/resources/example/instance.properties @@ -45,10 +45,8 @@ canal.instance.filter.black.regex= # mq config canal.mq.topic=example -# 单分区指定分区0 canal.mq.partition=0 -# 多分区指定分区数量 +# hash partition config #canal.mq.partitionsNum=3 -# 多分区指定库表主键, 规则: 库名.表名:唯一主键, 多个按逗号分隔 #canal.mq.partitionHash=mytest.person:id,mytest.role:id ################################################# \ No newline at end of file From e7086415f86d86794aa131cb5944e40a5666c89f Mon Sep 17 00:00:00 2001 From: machey Date: Sun, 11 Nov 2018 19:44:30 +0800 Subject: [PATCH 306/360] =?UTF-8?q?rdb=E4=B8=93=E7=94=A8=E7=9A=84=E7=9A=84?= =?UTF-8?q?ClassExtensionLoader=EF=BC=8C=E9=BB=98=E8=AE=A4canal=20client?= =?UTF-8?q?=20logger=20example=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/ExtensionLoader.java | 53 ++++++++++++++++++- .../launcher/src/main/assembly/dev.xml | 8 +-- .../launcher/src/main/bin/startup.sh | 2 - .../launcher/loader/CanalAdapterLoader.java | 2 +- .../src/main/resources/application.yml | 14 ++--- 5 files changed, 63 insertions(+), 16 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index ce7de834..5a09d27f 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -40,6 +40,8 @@ public class ExtensionLoader { private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); + private static final ConcurrentMap EXTENSION_KEY_INSTANCES = new ConcurrentHashMap<>(); + private final Class type; private final String classLoaderPolicy; @@ -116,6 +118,30 @@ public class ExtensionLoader { return (T) instance; } + @SuppressWarnings("unchecked") + public T getExtension(String name, String key) { + if (name == null || name.length() == 0) throw new IllegalArgumentException("Extension name == null"); + if ("true".equals(name)) { + return getDefaultExtension(); + } + Holder holder = cachedInstances.get(name + "-" + key); + if (holder == null) { + cachedInstances.putIfAbsent(name + "-" + key, new Holder<>()); + holder = cachedInstances.get(name + "-" + key); + } + Object instance = holder.get(); + if (instance == null) { + synchronized (holder) { + instance = holder.get(); + if (instance == null) { + instance = createExtension(name, key); + holder.set(instance); + } + } + } + return (T) instance; + } + /** * 返回缺省的扩展,如果没有设置则返回null */ @@ -149,14 +175,19 @@ public class ExtensionLoader { } @SuppressWarnings("unchecked") - public T newInstance(String name) { + private T createExtension(String name, String key) { Class clazz = getExtensionClasses().get(name); if (clazz == null) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: class could not be found"); } try { - return (T) clazz.newInstance(); + T instance = (T) EXTENSION_KEY_INSTANCES.get(name + "-" + key); + if (instance == null) { + EXTENSION_KEY_INSTANCES.putIfAbsent(name + "-" + key, clazz.newInstance()); + instance = (T) EXTENSION_KEY_INSTANCES.get(name + "-" + key); + } + return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type + ") could not be instantiated: " + t.getMessage(), @@ -164,6 +195,24 @@ public class ExtensionLoader { } } + @SuppressWarnings("unchecked") + // public T newInstance(String name) { + // Class clazz = getExtensionClasses().get(name); + // if (clazz == null) { + // throw new IllegalStateException("Extension instance(name: " + name + ", + // class: " + type + // + ") could not be instantiated: class could not be found"); + // } + // try { + // return (T) clazz.newInstance(); + // } catch (Throwable t) { + // throw new IllegalStateException("Extension instance(name: " + name + ", + // class: " + type + // + ") could not be instantiated: " + t.getMessage(), + // t); + // } + // } + private Map> getExtensionClasses() { Map> classes = cachedClasses.get(); if (classes == null) { diff --git a/client-adapter/launcher/src/main/assembly/dev.xml b/client-adapter/launcher/src/main/assembly/dev.xml index 394fe33f..84962aec 100644 --- a/client-adapter/launcher/src/main/assembly/dev.xml +++ b/client-adapter/launcher/src/main/assembly/dev.xml @@ -23,7 +23,7 @@ ./src/main/resources - /config + /conf **/* @@ -31,21 +31,21 @@ ../elasticsearch/src/main/resources/es - /config/es + /conf/es **/* ../hbase/src/main/resources/hbase - /config/hbase + /conf/hbase **/* ../rdb/src/main/resources/ - /config + /conf META-INF/** diff --git a/client-adapter/launcher/src/main/bin/startup.sh b/client-adapter/launcher/src/main/bin/startup.sh index 63255c59..69993483 100644 --- a/client-adapter/launcher/src/main/bin/startup.sh +++ b/client-adapter/launcher/src/main/bin/startup.sh @@ -10,8 +10,6 @@ case "`uname`" in ;; esac base=${bin_abs_path}/.. -#canal_conf=$base/config/canal.properties -#logback_configurationFile=$base/conf/logback.xml export LANG=en_US.UTF-8 export BASE=$base diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 78eab682..e3197e7d 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -125,7 +125,7 @@ public class CanalAdapterLoader { try { OuterAdapter adapter; if ("rdb".equalsIgnoreCase(config.getName())) { - adapter = loader.newInstance(config.getName()); + adapter = loader.getExtension(config.getName(), config.getKey()); } else { adapter = loader.getExtension(config.getName()); } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 9595e3fb..2c48fa84 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -11,16 +11,16 @@ spring: time-zone: GMT+8 default-property-inclusion: non_null -#canal.conf: -# canalServerHost: 127.0.0.1:11111 +canal.conf: + canalServerHost: 127.0.0.1:11111 # zookeeperHosts: slave1:2181 # bootstrapServers: slave1:6667 #or rocketmq # flatMessage: true -# canalInstances: -# - instance: example -# groups: -# - outAdapters: -# - name: logger + canalInstances: + - instance: example + groups: + - outAdapters: + - name: logger # - name: rdb # key: oracle1 # properties: From 7cca8857ffab1252ecbcc847bf7502df455c056c Mon Sep 17 00:00:00 2001 From: machey Date: Sun, 11 Nov 2018 19:56:14 +0800 Subject: [PATCH 307/360] =?UTF-8?q?README.md=20=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/README.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/client-adapter/README.md b/client-adapter/README.md index 0ca50b9b..0164aac0 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -19,14 +19,20 @@ client-adapter分为适配器和启动器两部分, 适配器为多个fat jar, 启动器为 SpringBoot 项目, 支持canal-client启动的同时提供相关REST管理接口, 运行目录结构为: ``` -canal-adapter-launcher.jar +- bin + restart.sh + startup.bat + startup.sh + stop.sh - lib client-adapter.logger-1.1.1-jar-with-dependencies.jar client-adapter.hbase-1.1.1-jar-with-dependencies.jar -- config + ... +- conf application.yml - hbase mytest_person2.yml +- logs ``` 以上目录结构最终会打包成 canal-adapter-launcher.tar.gz 压缩包 @@ -96,7 +102,7 @@ curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml ``` ### 2.3 启动canal-adapter示例 #### 2.3.1 启动canal server (单机模式), 参考: [Canal QuickStart](https://github.com/alibaba/canal/wiki/QuickStart) -#### 2.3.2 修改config/application.yml为: +#### 2.3.2 修改conf/application.yml为: ``` server: port: 8081 @@ -118,9 +124,9 @@ canal.conf: - outAdapters: - name: logger ``` -启动 canal-adapter-launcher.jar +启动 ``` -java -jar canal-adapter-launcher.jar +bin/startup.sh ``` ## 三、HBase适配器 @@ -161,7 +167,7 @@ adapter.conf: ``` 其中指定了一个HBase表映射文件: mytest_person.yml ### 3.2 适配器表映射文件 -修改 config/hbase/mytest_person.yml文件: +修改 conf/hbase/mytest_person.yml文件: ``` dataSourceKey: defaultDS # 对应application.yml中的datasourceConfigs下的配置 hbaseMapping: # mysql--HBase的单表映射配置 From 66912d3d0533a0d0046b4c17a34274a4c89edc14 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 12 Nov 2018 15:07:46 +0800 Subject: [PATCH 308/360] =?UTF-8?q?rdb=20etl=20count=20destination=20?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E9=80=82=E9=85=8D=20es=20hbase=20=E5=A4=9A?= =?UTF-8?q?=E6=BA=90=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/ExtensionLoader.java | 36 +++------- .../canal/client/adapter/es/ESAdapter.java | 66 +++++++++++++++--- .../adapter/es/config/ESSyncConfig.java | 24 +++++-- .../adapter/es/config/ESSyncConfigLoader.java | 42 ++---------- .../adapter/es/service/ESSyncService.java | 5 +- .../adapter/es/test/ConfigLoadTest.java | 8 +-- .../es/test/sync/LabelSyncJoinSub2Test.java | 37 ++++++---- .../es/test/sync/LabelSyncJoinSubTest.java | 37 ++++++---- .../es/test/sync/RoleSyncJoinOne2Test.java | 20 ++++-- .../es/test/sync/RoleSyncJoinOneTest.java | 68 ++++++++++++------- .../es/test/sync/UserSyncJoinOneTest.java | 20 ++++-- .../es/test/sync/UserSyncSingleTest.java | 21 ++++-- .../client/adapter/hbase/HbaseAdapter.java | 37 +++++----- .../adapter/hbase/config/MappingConfig.java | 16 ++++- .../hbase/config/MappingConfigLoader.java | 8 +-- .../launcher/loader/CanalAdapterLoader.java | 20 +++--- .../adapter/launcher/rest/CommonRest.java | 51 ++++++++++---- .../canal/client/adapter/rdb/RdbAdapter.java | 25 ++++--- 18 files changed, 333 insertions(+), 208 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index 5a09d27f..62ea78fd 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -13,6 +13,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +41,9 @@ public class ExtensionLoader { private static final ConcurrentMap, Object> EXTENSION_INSTANCES = new ConcurrentHashMap<>(); - private static final ConcurrentMap EXTENSION_KEY_INSTANCES = new ConcurrentHashMap<>(); + private static final ConcurrentMap EXTENSION_KEY_INSTANCE = new ConcurrentHashMap<>(); + + private static final ConcurrentMap> EXTENSION_KEY_INSTANCES = new ConcurrentHashMap<>(); private final Class type; @@ -124,10 +127,11 @@ public class ExtensionLoader { if ("true".equals(name)) { return getDefaultExtension(); } - Holder holder = cachedInstances.get(name + "-" + key); + String extKey = name + "-" + StringUtils.trimToEmpty(key); + Holder holder = cachedInstances.get(extKey); if (holder == null) { - cachedInstances.putIfAbsent(name + "-" + key, new Holder<>()); - holder = cachedInstances.get(name + "-" + key); + cachedInstances.putIfAbsent(extKey, new Holder<>()); + holder = cachedInstances.get(extKey); } Object instance = holder.get(); if (instance == null) { @@ -182,10 +186,10 @@ public class ExtensionLoader { + ") could not be instantiated: class could not be found"); } try { - T instance = (T) EXTENSION_KEY_INSTANCES.get(name + "-" + key); + T instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key); if (instance == null) { - EXTENSION_KEY_INSTANCES.putIfAbsent(name + "-" + key, clazz.newInstance()); - instance = (T) EXTENSION_KEY_INSTANCES.get(name + "-" + key); + EXTENSION_KEY_INSTANCE.putIfAbsent(name + "-" + key, clazz.newInstance()); + instance = (T) EXTENSION_KEY_INSTANCE.get(name + "-" + key); } return instance; } catch (Throwable t) { @@ -195,24 +199,6 @@ public class ExtensionLoader { } } - @SuppressWarnings("unchecked") - // public T newInstance(String name) { - // Class clazz = getExtensionClasses().get(name); - // if (clazz == null) { - // throw new IllegalStateException("Extension instance(name: " + name + ", - // class: " + type - // + ") could not be instantiated: class could not be found"); - // } - // try { - // return (T) clazz.newInstance(); - // } catch (Throwable t) { - // throw new IllegalStateException("Extension instance(name: " + name + ", - // class: " + type - // + ") could not be instantiated: " + t.getMessage(), - // t); - // } - // } - private Map> getExtensionClasses() { Map> classes = cachedClasses.get(); if (classes == null) { diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java index 7d10d702..702fd042 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java @@ -1,9 +1,12 @@ package com.alibaba.otter.canal.client.adapter.es; import java.net.InetAddress; +import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.sql.DataSource; @@ -13,10 +16,13 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.transport.client.PreBuiltTransportClient; +import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader; +import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem; +import com.alibaba.otter.canal.client.adapter.es.config.SqlParser; import com.alibaba.otter.canal.client.adapter.es.service.ESEtlService; import com.alibaba.otter.canal.client.adapter.es.service.ESSyncService; import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate; @@ -31,9 +37,12 @@ import com.alibaba.otter.canal.client.adapter.support.*; @SPI("es") public class ESAdapter implements OuterAdapter { - private TransportClient transportClient; + private Map esSyncConfig = new LinkedHashMap<>(); // 文件名对应配置 + private Map> dbTableEsSyncConfig = new LinkedHashMap<>(); // schema-table对应配置 - private ESSyncService esSyncService; + private TransportClient transportClient; + + private ESSyncService esSyncService; public TransportClient getTransportClient() { return transportClient; @@ -43,10 +52,48 @@ public class ESAdapter implements OuterAdapter { return esSyncService; } + public Map getEsSyncConfig() { + return esSyncConfig; + } + + public Map> getDbTableEsSyncConfig() { + return dbTableEsSyncConfig; + } + @Override public void init(OuterAdapterConfig configuration) { try { - ESSyncConfigLoader.load(); + SPI spi = this.getClass().getAnnotation(SPI.class); + Map esSyncConfigTmp = ESSyncConfigLoader.load(spi.value()); + // 过滤不匹配的key的配置 + esSyncConfigTmp.forEach((key, config) -> { + if ((config.getOuterAdapterKey() == null && configuration.getKey() == null) + || config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { + esSyncConfig.put(key, config); + } + }); + + for (ESSyncConfig config : esSyncConfig.values()) { + SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql()); + config.getEsMapping().setSchemaItem(schemaItem); + + DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); + if (dataSource == null || dataSource.getUrl() == null) { + throw new RuntimeException("No data source found: " + config.getDataSourceKey()); + } + Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$"); + Matcher matcher = pattern.matcher(dataSource.getUrl()); + if (!matcher.find()) { + throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey()); + } + String schema = matcher.group(2); + + schemaItem.getAliasTableItems().values().forEach(tableItem -> { + List esSyncConfigs = dbTableEsSyncConfig + .computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new ArrayList<>()); + esSyncConfigs.add(config); + }); + } Map properties = configuration.getProperties(); Settings.Builder settingBuilder = Settings.builder(); @@ -68,13 +115,16 @@ public class ESAdapter implements OuterAdapter { @Override public void sync(Dml dml) { - esSyncService.sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = dbTableEsSyncConfig.get(database + "-" + table); + esSyncService.sync(esSyncConfigs, dml); } @Override public EtlResult etl(String task, List params) { EtlResult etlResult = new EtlResult(); - ESSyncConfig config = ESSyncConfigLoader.getEsSyncConfig().get(task); + ESSyncConfig config = esSyncConfig.get(task); if (config != null) { DataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); ESEtlService esEtlService = new ESEtlService(transportClient, config); @@ -89,7 +139,7 @@ public class ESAdapter implements OuterAdapter { StringBuilder resultMsg = new StringBuilder(); boolean resSuccess = true; // ds不为空说明传入的是datasourceKey - for (ESSyncConfig configTmp : ESSyncConfigLoader.getEsSyncConfig().values()) { + for (ESSyncConfig configTmp : esSyncConfig.values()) { // 取所有的destination为task的配置 if (configTmp.getDestination().equals(task)) { ESEtlService esEtlService = new ESEtlService(transportClient, configTmp); @@ -119,7 +169,7 @@ public class ESAdapter implements OuterAdapter { @Override public Map count(String task) { - ESSyncConfig config = ESSyncConfigLoader.getEsSyncConfig().get(task); + ESSyncConfig config = esSyncConfig.get(task); ESMapping mapping = config.getEsMapping(); SearchResponse response = transportClient.prepareSearch(mapping.get_index()) .setTypes(mapping.get_type()) @@ -142,7 +192,7 @@ public class ESAdapter implements OuterAdapter { @Override public String getDestination(String task) { - ESSyncConfig config = ESSyncConfigLoader.getEsSyncConfig().get(task); + ESSyncConfig config = esSyncConfig.get(task); if (config != null) { return config.getDestination(); } diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java index 9d4a9da2..65087437 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfig.java @@ -13,9 +13,11 @@ import java.util.Map; */ public class ESSyncConfig { - private String dataSourceKey; // 数据源key + private String dataSourceKey; // 数据源key - private String destination; // canal destination + private String outerAdapterKey; // adapter key + + private String destination; // canal destination private ESMapping esMapping; @@ -42,6 +44,14 @@ public class ESSyncConfig { this.dataSourceKey = dataSourceKey; } + public String getOuterAdapterKey() { + return outerAdapterKey; + } + + public void setOuterAdapterKey(String outerAdapterKey) { + this.outerAdapterKey = outerAdapterKey; + } + public String getDestination() { return destination; } @@ -67,15 +77,15 @@ public class ESSyncConfig { private String parent; private String sql; // 对象字段, 例: objFields: - // - _labels: array:; - private Map objFields = new LinkedHashMap<>(); + // - _labels: array:; + private Map objFields = new LinkedHashMap<>(); private List skips = new ArrayList<>(); private int commitBatch = 1000; private String etlCondition; - private boolean syncByTimestamp = false; // 是否按时间戳定时同步 - private Long syncInterval; // 同步时间间隔 + private boolean syncByTimestamp = false; // 是否按时间戳定时同步 + private Long syncInterval; // 同步时间间隔 - private SchemaItem schemaItem; // sql解析结果模型 + private SchemaItem schemaItem; // sql解析结果模型 public String get_index() { return _index; diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java index 81beede2..5d43572d 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java @@ -32,24 +32,14 @@ public class ESSyncConfigLoader { private static Logger logger = LoggerFactory .getLogger(ESSyncConfigLoader.class); - private static final String BASE_PATH = "es"; - - private static volatile Map esSyncConfig = new LinkedHashMap<>(); // 文件名对应配置 - private static volatile Map> dbTableEsSyncConfig = new LinkedHashMap<>(); // schema-table对应配置 - - public static Map getEsSyncConfig() { - return esSyncConfig; - } - - public static Map> getDbTableEsSyncConfig() { - return dbTableEsSyncConfig; - } - - public static synchronized void load() { + public static synchronized Map load(String name) { logger.info("## Start loading es mapping config ... "); + + Map esSyncConfig = new LinkedHashMap<>(); + Collection configs = AdapterConfigs.get("es"); if (configs == null) { - return; + return esSyncConfig; } for (String c : configs) { if (c == null) { @@ -64,32 +54,13 @@ public class ESSyncConfigLoader { String configContent = null; if (c.endsWith(".yml")) { - configContent = readConfigContent(BASE_PATH + "/" + c); + configContent = readConfigContent(name + "/" + c); } config = new Yaml().loadAs(configContent, ESSyncConfig.class); try { config.validate(); - SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql()); - config.getEsMapping().setSchemaItem(schemaItem); - - DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); - if (dataSource == null || dataSource.getUrl() == null) { - throw new RuntimeException("No data source found: " + config.getDataSourceKey()); - } - Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$"); - Matcher matcher = pattern.matcher(dataSource.getUrl()); - if (!matcher.find()) { - throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey()); - } - String schema = matcher.group(2); - - schemaItem.getAliasTableItems().values().forEach(tableItem -> { - List esSyncConfigs = dbTableEsSyncConfig - .computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new ArrayList<>()); - esSyncConfigs.add(config); - }); } catch (Exception e) { throw new RuntimeException("ERROR Config: " + c, e); } @@ -97,6 +68,7 @@ public class ESSyncConfigLoader { } logger.info("## ES mapping config loaded"); + return esSyncConfig; } private static String readConfigContent(String config) { diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java index 04d208f7..73b1a6bc 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java @@ -39,11 +39,8 @@ public class ESSyncService { this.esTemplate = esTemplate; } - public void sync(Dml dml) { + public void sync(List esSyncConfigs, Dml dml) { long begin = System.currentTimeMillis(); - String database = dml.getDatabase(); - String table = dml.getTable(); - List esSyncConfigs = ESSyncConfigLoader.getDbTableEsSyncConfig().get(database + "-" + table); if (esSyncConfigs != null) { if (logger.isTraceEnabled()) { logger.trace("Destination: {}, database:{}, table:{}, type:{}, effect index count: {}", diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java index 83f0cfb9..680a3725 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java @@ -23,8 +23,7 @@ public class ConfigLoadTest { @Test public void testLoad() { - ESSyncConfigLoader.load(); - Map configMap = ESSyncConfigLoader.getEsSyncConfig(); + Map configMap = ESSyncConfigLoader.load("es"); ESSyncConfig config = configMap.get("mytest_user.yml"); Assert.assertNotNull(config); Assert.assertEquals("defaultDS", config.getDataSourceKey()); @@ -34,7 +33,8 @@ public class ConfigLoadTest { Assert.assertEquals("id", esMapping.get_id()); Assert.assertNotNull(esMapping.getSql()); - Map> dbTableEsSyncConfig = ESSyncConfigLoader.getDbTableEsSyncConfig(); - Assert.assertFalse(dbTableEsSyncConfig.isEmpty()); + // Map> dbTableEsSyncConfig = + // ESSyncConfigLoader.getDbTableEsSyncConfig(); + // Assert.assertFalse(dbTableEsSyncConfig.isEmpty()); } } diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java index cb947de9..5e269a53 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; import org.junit.After; import org.junit.Assert; @@ -32,9 +33,9 @@ public class LabelSyncJoinSub2Test { @Test public void test01() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"delete from label where id=1 or id=2"); - Common.sqlExe(ds,"insert into label (id,user_id,label) values (1,1,'a')"); - Common.sqlExe(ds,"insert into label (id,user_id,label) values (2,1,'b')"); + Common.sqlExe(ds, "delete from label where id=1 or id=2"); + Common.sqlExe(ds, "insert into label (id,user_id,label) values (1,1,'a')"); + Common.sqlExe(ds, "insert into label (id,user_id,label) values (2,1,'b')"); Dml dml = new Dml(); dml.setDestination("example"); @@ -46,12 +47,15 @@ public class LabelSyncJoinSub2Test { Map data = new LinkedHashMap<>(); dataList.add(data); data.put("id", 2L); - data.put("user_id",1L); + data.put("user_id", 1L); data.put("label", "b"); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;a_", response.getSource().get("_labels")); @@ -63,7 +67,7 @@ public class LabelSyncJoinSub2Test { @Test public void test02() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"update label set label='aa' where id=1"); + Common.sqlExe(ds, "update label set label='aa' where id=1"); Dml dml = new Dml(); dml.setDestination("example"); @@ -75,7 +79,7 @@ public class LabelSyncJoinSub2Test { Map data = new LinkedHashMap<>(); dataList.add(data); data.put("id", 1L); - data.put("user_id",1L); + data.put("user_id", 1L); data.put("label", "aa"); dml.setData(dataList); @@ -85,7 +89,11 @@ public class LabelSyncJoinSub2Test { old.put("label", "v"); dml.setOld(oldList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;aa_", response.getSource().get("_labels")); @@ -97,7 +105,7 @@ public class LabelSyncJoinSub2Test { @Test public void test03() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"delete from label where id=1"); + Common.sqlExe(ds, "delete from label where id=1"); Dml dml = new Dml(); dml.setDestination("example"); @@ -109,12 +117,15 @@ public class LabelSyncJoinSub2Test { Map data = new LinkedHashMap<>(); dataList.add(data); data.put("id", 1L); - data.put("user_id",1L); + data.put("user_id", 1L); data.put("label", "a"); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b_", response.getSource().get("_labels")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java index bcf4d372..411e3066 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; import org.junit.After; import org.junit.Assert; @@ -32,9 +33,9 @@ public class LabelSyncJoinSubTest { @Test public void test01() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"delete from label where id=1 or id=2"); - Common.sqlExe(ds,"insert into label (id,user_id,label) values (1,1,'a')"); - Common.sqlExe(ds,"insert into label (id,user_id,label) values (2,1,'b')"); + Common.sqlExe(ds, "delete from label where id=1 or id=2"); + Common.sqlExe(ds, "insert into label (id,user_id,label) values (1,1,'a')"); + Common.sqlExe(ds, "insert into label (id,user_id,label) values (2,1,'b')"); Dml dml = new Dml(); dml.setDestination("example"); @@ -46,12 +47,15 @@ public class LabelSyncJoinSubTest { Map data = new LinkedHashMap<>(); dataList.add(data); data.put("id", 2L); - data.put("user_id",1L); + data.put("user_id", 1L); data.put("label", "b"); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;a", response.getSource().get("_labels")); @@ -63,7 +67,7 @@ public class LabelSyncJoinSubTest { @Test public void test02() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"update label set label='aa' where id=1"); + Common.sqlExe(ds, "update label set label='aa' where id=1"); Dml dml = new Dml(); dml.setDestination("example"); @@ -75,7 +79,7 @@ public class LabelSyncJoinSubTest { Map data = new LinkedHashMap<>(); dataList.add(data); data.put("id", 1L); - data.put("user_id",1L); + data.put("user_id", 1L); data.put("label", "aa"); dml.setData(dataList); @@ -85,7 +89,11 @@ public class LabelSyncJoinSubTest { old.put("label", "a"); dml.setOld(oldList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;aa", response.getSource().get("_labels")); @@ -97,7 +105,7 @@ public class LabelSyncJoinSubTest { @Test public void test03() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"delete from label where id=1"); + Common.sqlExe(ds, "delete from label where id=1"); Dml dml = new Dml(); dml.setDestination("example"); @@ -109,12 +117,15 @@ public class LabelSyncJoinSubTest { Map data = new LinkedHashMap<>(); dataList.add(data); data.put("id", 1L); - data.put("user_id",1L); + data.put("user_id", 1L); data.put("label", "a"); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b", response.getSource().get("_labels")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java index a8fdf119..94e52242 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; import org.junit.After; import org.junit.Assert; @@ -32,8 +33,8 @@ public class RoleSyncJoinOne2Test { @Test public void test01() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"delete from role where id=1"); - Common.sqlExe(ds,"insert into role (id,role_name) values (1,'admin')"); + Common.sqlExe(ds, "delete from role where id=1"); + Common.sqlExe(ds, "insert into role (id,role_name) values (1,'admin')"); Dml dml = new Dml(); dml.setDestination("example"); @@ -46,10 +47,13 @@ public class RoleSyncJoinOne2Test { dataList.add(data); data.put("id", 1L); data.put("role_name", "admin"); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin_", response.getSource().get("_role_name")); @@ -61,7 +65,7 @@ public class RoleSyncJoinOne2Test { @Test public void test02() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"update role set role_name='admin3' where id=1"); + Common.sqlExe(ds, "update role set role_name='admin3' where id=1"); Dml dml = new Dml(); dml.setDestination("example"); @@ -82,7 +86,11 @@ public class RoleSyncJoinOne2Test { old.put("role_name", "admin"); dml.setOld(oldList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin3_", response.getSource().get("_role_name")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java index 9c5ab300..e68b9812 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java @@ -4,6 +4,7 @@ import java.util.*; import javax.sql.DataSource; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; import org.junit.Assert; import org.junit.Before; @@ -47,7 +48,11 @@ public class RoleSyncJoinOneTest { dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin", response.getSource().get("_role_name")); @@ -80,7 +85,11 @@ public class RoleSyncJoinOneTest { old.put("role_name", "admin"); dml.setOld(oldList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin2", response.getSource().get("_role_name")); @@ -96,28 +105,32 @@ public class RoleSyncJoinOneTest { Common.sqlExe(ds, "insert into role (id,role_name) values (2,'operator')"); Common.sqlExe(ds, "update user set role_id=2 where id=1"); - Dml dml = new Dml(); - dml.setDestination("example"); - dml.setTs(new Date().getTime()); - dml.setType("UPDATE"); - dml.setDatabase("mytest"); - dml.setTable("user"); - List> dataList = new ArrayList<>(); - Map data = new LinkedHashMap<>(); - dataList.add(data); - data.put("id", 1L); - data.put("role_id", 2L); - dml.setData(dataList); - List> oldList = new ArrayList<>(); - Map old = new LinkedHashMap<>(); - oldList.add(old); - old.put("role_id", 1L); - dml.setOld(oldList); - esAdapter.getEsSyncService().sync(dml); + Dml dml = new Dml(); + dml.setDestination("example"); + dml.setTs(new Date().getTime()); + dml.setType("UPDATE"); + dml.setDatabase("mytest"); + dml.setTable("user"); + List> dataList = new ArrayList<>(); + Map data = new LinkedHashMap<>(); + dataList.add(data); + data.put("id", 1L); + data.put("role_id", 2L); + dml.setData(dataList); + List> oldList = new ArrayList<>(); + Map old = new LinkedHashMap<>(); + oldList.add(old); + old.put("role_id", 1L); + dml.setOld(oldList); - GetResponse response = - esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); - Assert.assertEquals("operator", response.getSource().get("_role_name")); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + + GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); + Assert.assertEquals("operator", response.getSource().get("_role_name")); Common.sqlExe(ds, "update user set role_id=1 where id=1"); @@ -138,7 +151,8 @@ public class RoleSyncJoinOneTest { oldList2.add(old2); old2.put("role_id", 2L); dml2.setOld(oldList2); - esAdapter.getEsSyncService().sync(dml2); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml2); GetResponse response2 = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin2", response2.getSource().get("_role_name")); @@ -166,7 +180,11 @@ public class RoleSyncJoinOneTest { dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertNull(response.getSource().get("_role_name")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java index d34d61b2..f8a5341a 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java @@ -4,6 +4,7 @@ import java.util.*; import javax.sql.DataSource; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; import org.junit.After; import org.junit.Assert; @@ -32,8 +33,8 @@ public class UserSyncJoinOneTest { @Test public void test01() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"delete from user where id=1"); - Common.sqlExe(ds,"insert into user (id,name,role_id) values (1,'Eric',1)"); + Common.sqlExe(ds, "delete from user where id=1"); + Common.sqlExe(ds, "insert into user (id,name,role_id) values (1,'Eric',1)"); Dml dml = new Dml(); dml.setDestination("example"); @@ -48,10 +49,13 @@ public class UserSyncJoinOneTest { data.put("name", "Eric"); data.put("role_id", 1L); data.put("c_time", new Date()); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric_", response.getSource().get("_name")); @@ -63,7 +67,7 @@ public class UserSyncJoinOneTest { @Test public void test02() { DataSource ds = DatasourceConfig.DATA_SOURCES.get("defaultDS"); - Common.sqlExe(ds,"update user set name='Eric2' where id=1"); + Common.sqlExe(ds, "update user set name='Eric2' where id=1"); Dml dml = new Dml(); dml.setDestination("example"); @@ -83,7 +87,11 @@ public class UserSyncJoinOneTest { old.put("name", "Eric"); dml.setOld(oldList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric2_", response.getSource().get("_name")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java index 0b98a2b0..dccf24a3 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java @@ -2,6 +2,7 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; import org.junit.After; import org.junit.Assert; @@ -42,10 +43,13 @@ public class UserSyncSingleTest { data.put("name", "Eric"); data.put("role_id", 1L); data.put("c_time", new Date()); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric", response.getSource().get("_name")); @@ -74,7 +78,11 @@ public class UserSyncSingleTest { old.put("name", "Eric"); dml.setOld(oldList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric2", response.getSource().get("_name")); @@ -98,10 +106,13 @@ public class UserSyncSingleTest { data.put("name", "Eric"); data.put("role_id", 1L); data.put("c_time", new Date()); - dml.setData(dataList); - esAdapter.getEsSyncService().sync(dml); + String database = dml.getDatabase(); + String table = dml.getTable(); + List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + + esAdapter.getEsSyncService().sync(esSyncConfigs, dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertNull(response.getSource()); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 8e4c6d36..7c8d82db 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -35,31 +35,32 @@ import com.alibaba.otter.canal.client.adapter.support.*; @SPI("hbase") public class HbaseAdapter implements OuterAdapter { - private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class); + private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class); - private static volatile Map hbaseMapping = null; // 文件名对应配置 - private static volatile Map mappingConfigCache = null; // 库名-表名对应配置 + private Map hbaseMapping = new HashMap<>(); // 文件名对应配置 + private Map mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 - private Connection conn; - private HbaseSyncService hbaseSyncService; - private HbaseTemplate hbaseTemplate; + private Connection conn; + private HbaseSyncService hbaseSyncService; + private HbaseTemplate hbaseTemplate; @Override public void init(OuterAdapterConfig configuration) { try { - if (mappingConfigCache == null) { - synchronized (MappingConfig.class) { - if (mappingConfigCache == null) { - hbaseMapping = MappingConfigLoader.load(); - mappingConfigCache = new HashMap<>(); - for (MappingConfig mappingConfig : hbaseMapping.values()) { - mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." - + mappingConfig.getHbaseMapping().getDatabase() + "." - + mappingConfig.getHbaseMapping().getTable(), - mappingConfig); - } - } + SPI spi = this.getClass().getAnnotation(SPI.class); + Map hbaseMappingTmp = MappingConfigLoader.load(spi.value()); + // 过滤不匹配的key的配置 + hbaseMappingTmp.forEach((key, mappingConfig) -> { + if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) + || mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { + hbaseMapping.put(key, mappingConfig); } + }); + for (MappingConfig mappingConfig : hbaseMapping.values()) { + mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getHbaseMapping().getDatabase() + "." + + mappingConfig.getHbaseMapping().getTable(), + mappingConfig); } Map properties = configuration.getProperties(); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java index 326971ac..2d86adee 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfig.java @@ -10,11 +10,13 @@ import java.util.*; */ public class MappingConfig { - private String dataSourceKey; // 数据源key + private String dataSourceKey; // 数据源key - private String destination; // canal实例或MQ的topic + private String outerAdapterKey; // adapter key - private HbaseMapping hbaseMapping; // hbase映射配置 + private String destination; // canal实例或MQ的topic + + private HbaseMapping hbaseMapping; // hbase映射配置 public String getDataSourceKey() { return dataSourceKey; @@ -24,6 +26,14 @@ public class MappingConfig { this.dataSourceKey = dataSourceKey; } + public String getOuterAdapterKey() { + return outerAdapterKey; + } + + public void setOuterAdapterKey(String outerAdapterKey) { + this.outerAdapterKey = outerAdapterKey; + } + public String getDestination() { return destination; } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 2ac3a0d0..058a60f1 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -26,19 +26,17 @@ public class MappingConfigLoader { private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class); - private static final String BASE_PATH = "hbase"; - /** * 加载HBase表映射配置 * * @return 配置名/配置文件名--对象 */ - public static Map load() { + public static Map load(String name) { logger.info("## Start loading hbase mapping config ... "); Map result = new LinkedHashMap<>(); - Collection configs = AdapterConfigs.get("hbase"); + Collection configs = AdapterConfigs.get(name); if (configs == null) { return result; } @@ -55,7 +53,7 @@ public class MappingConfigLoader { String configContent = null; if (c.endsWith(".yml")) { - configContent = readConfigContent(BASE_PATH + "/" + c); + configContent = readConfigContent(name + "/" + c); } // 简单配置database.table@datasourcekey?rowKey=key1,key2 diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index e3197e7d..77567a87 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -33,6 +33,8 @@ public class CanalAdapterLoader { private Map canalMQWorker = new HashMap<>(); + private Map outerAdapters = new HashMap<>(); // 配置文件对应adapter + private ExtensionLoader loader; public CanalAdapterLoader(CanalClientConfig canalClientConfig){ @@ -53,8 +55,8 @@ public class CanalAdapterLoader { } String zkHosts = this.canalClientConfig.getZookeeperHosts(); - // 初始化canal-client的适配器 if (canalClientConfig.getCanalInstances() != null) { + // 初始化canal-client的适配器 for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { List> canalOuterAdapterGroups = new ArrayList<>(); @@ -83,10 +85,8 @@ public class CanalAdapterLoader { worker.start(); logger.info("Start adapter for canal instance: {} succeed", instance.getInstance()); } - } - - // 初始化canal-client-mq的适配器 - if (canalClientConfig.getMqTopics() != null) { + } else if (canalClientConfig.getMqTopics() != null) { + // 初始化canal-client-mq的适配器 for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) { for (CanalClientConfig.MQGroup group : topic.getGroups()) { List> canalOuterAdapterGroups = new ArrayList<>(); @@ -124,11 +124,11 @@ public class CanalAdapterLoader { private void loadConnector(OuterAdapterConfig config, List canalOutConnectors) { try { OuterAdapter adapter; - if ("rdb".equalsIgnoreCase(config.getName())) { - adapter = loader.getExtension(config.getName(), config.getKey()); - } else { - adapter = loader.getExtension(config.getName()); - } + // if ("rdb".equalsIgnoreCase(config.getName())) { + adapter = loader.getExtension(config.getName(), StringUtils.trimToEmpty(config.getKey())); + // } else { + // adapter = loader.getExtension(config.getName()); + // } ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 替换ClassLoader Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader()); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java index 0a6d00f4..302206be 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/rest/CommonRest.java @@ -46,17 +46,17 @@ public class CommonRest { } /** - * ETL curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST - * + * ETL curl http://127.0.0.1:8081/etl/rdb/oracle1/mytest_user.yml -X POST + * * @param type 类型 hbase, es - * @param task 任务名对应配置文件名 mytest_person2.yml + * @param key adapter key + * @param task 任务名对应配置文件名 mytest_user.yml * @param params etl where条件参数, 为空全部导入 - * @return */ - @PostMapping("/etl/{type}/{task}") - public EtlResult etl(@PathVariable String type, @PathVariable String task, + @PostMapping("/etl/{type}/{key}/{task}") + public EtlResult etl(@PathVariable String type, @PathVariable String key, @PathVariable String task, @RequestParam(name = "params", required = false) String params) { - OuterAdapter adapter = loader.getExtension(type); + OuterAdapter adapter = loader.getExtension(type, key); String destination = adapter.getDestination(task); String lockKey = destination == null ? task : destination; @@ -83,12 +83,11 @@ public class CommonRest { } } try { - List paramArr = null; + List paramArray = null; if (params != null) { - String[] parmaArray = params.trim().split(";"); - paramArr = Arrays.asList(parmaArray); + paramArray = Arrays.asList(params.trim().split(";")); } - return adapter.etl(task, paramArr); + return adapter.etl(task, paramArray); } finally { if (destination != null && oriSwitchStatus != null && oriSwitchStatus) { syncSwitch.on(destination); @@ -101,6 +100,33 @@ public class CommonRest { } } + /** + * ETL curl http://127.0.0.1:8081/etl/hbase/mytest_person2.yml -X POST + * + * @param type 类型 hbase, es + * @param task 任务名对应配置文件名 mytest_person2.yml + * @param params etl where条件参数, 为空全部导入 + */ + @PostMapping("/etl/{type}/{task}") + public EtlResult etl(@PathVariable String type, @PathVariable String task, + @RequestParam(name = "params", required = false) String params) { + return etl(type, null, task, params); + } + + /** + * 统计总数 curl http://127.0.0.1:8081/count/rdb/oracle1/mytest_user.yml + * + * @param type 类型 hbase, es + * @param key adapter key + * @param task 任务名对应配置文件名 mytest_person2.yml + * @return + */ + @GetMapping("/count/{type}/{key}/{task}") + public Map count(@PathVariable String type, @PathVariable String key, @PathVariable String task) { + OuterAdapter adapter = loader.getExtension(type, key); + return adapter.count(task); + } + /** * 统计总数 curl http://127.0.0.1:8081/count/hbase/mytest_person2.yml * @@ -110,8 +136,7 @@ public class CommonRest { */ @GetMapping("/count/{type}/{task}") public Map count(@PathVariable String type, @PathVariable String task) { - OuterAdapter adapter = loader.getExtension(type); - return adapter.count(task); + return count(type, null, task); } /** diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index c07dee6b..ef1ffe01 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -22,26 +22,26 @@ import com.alibaba.otter.canal.client.adapter.support.*; @SPI("rdb") public class RdbAdapter implements OuterAdapter { - private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); + private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); - private volatile Map rdbMapping = new HashMap<>(); // 文件名对应配置 - private Map mappingConfigCache; // 库名-表名对应配置 + private Map rdbMapping = new HashMap<>(); // 文件名对应配置 + private Map mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 - private DruidDataSource dataSource; + private DruidDataSource dataSource; - private RdbSyncService rdbSyncService; + private RdbSyncService rdbSyncService; @Override public void init(OuterAdapterConfig configuration) { SPI spi = this.getClass().getAnnotation(SPI.class); Map rdbMappingTmp = MappingConfigLoader.load(spi.value()); - // 过滤其他key的配置 + // 过滤不匹配的key的配置 rdbMappingTmp.forEach((key, mappingConfig) -> { - if (mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { + if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) + || mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { rdbMapping.put(key, mappingConfig); } }); - mappingConfigCache = new HashMap<>(); for (MappingConfig mappingConfig : rdbMapping.values()) { mappingConfigCache .put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." @@ -213,6 +213,15 @@ public class RdbAdapter implements OuterAdapter { return res; } + @Override + public String getDestination(String task) { + MappingConfig config = rdbMapping.get(task); + if (config != null) { + return config.getDestination(); + } + return null; + } + @Override public void destroy() { if (dataSource != null) { From 2e5b7b89af953beb5693baf50d0685f3786ca873 Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 12 Nov 2018 17:30:40 +0800 Subject: [PATCH 309/360] =?UTF-8?q?=E7=AE=80=E5=8C=96adapter=E9=85=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/AdapterConfigs.java | 41 ------ .../adapter/support/MappingConfigsLoader.java | 46 +++++++ client-adapter/elasticsearch/pom.xml | 20 +++ .../canal/client/adapter/es/ESAdapter.java | 6 +- .../adapter/es/config/ESSyncConfigLoader.java | 80 ++---------- .../adapter/es/test/ConfigLoadTest.java | 6 +- .../es/test/sync/LabelSyncJoinSub2Test.java | 11 +- .../es/test/sync/LabelSyncJoinSubTest.java | 11 +- .../es/test/sync/RoleSyncJoinOne2Test.java | 11 +- .../es/test/sync/RoleSyncJoinOneTest.java | 5 +- .../es/test/sync/UserSyncJoinOneTest.java | 7 +- .../es/test/sync/UserSyncSingleTest.java | 8 +- .../resources/es/mytest_user_join_one.yml | 10 -- .../resources/es/mytest_user_join_one2.yml | 10 -- .../resources/es/mytest_user_join_sub.yml | 11 -- .../resources/es/mytest_user_join_sub2.yml | 11 -- ...ser_single.yml => mytest_user_single.yml_} | 0 client-adapter/hbase/pom.xml | 20 +++ .../client/adapter/hbase/HbaseAdapter.java | 6 +- .../hbase/config/MappingConfigLoader.java | 123 ++---------------- .../launcher/config/AdapterCanalConfig.java | 41 +++++- .../launcher/config/AdapterConfig.java | 84 ------------ .../launcher/loader/CanalAdapterService.java | 3 - .../src/main/resources/application.yml | 29 ++--- client-adapter/rdb/pom.xml | 20 +++ .../canal/client/adapter/rdb/RdbAdapter.java | 13 +- .../adapter/rdb/config/ConfigLoader.java | 46 +++++++ .../rdb/config/MappingConfigLoader.java | 100 -------------- .../adapter/rdb/service/RdbSyncService.java | 7 +- .../adapter/rdb/test/ConfigLoadTest.java | 6 +- .../client/adapter/rdb/test/sync/Common.java | 2 +- .../adapter/rdb/test/sync/OracleSyncTest.java | 2 - .../resources/{oracle => rdb}/mytest_user.yml | 0 33 files changed, 260 insertions(+), 536 deletions(-) delete mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java delete mode 100644 client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one.yml delete mode 100644 client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one2.yml delete mode 100644 client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub.yml delete mode 100644 client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub2.yml rename client-adapter/elasticsearch/src/test/resources/es/{mytest_user_single.yml => mytest_user_single.yml_} (100%) delete mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java delete mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java rename client-adapter/rdb/src/test/resources/{oracle => rdb}/mytest_user.yml (100%) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java deleted file mode 100644 index 0ccfb509..00000000 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/AdapterConfigs.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.support; - -import java.util.HashMap; -import java.util.LinkedHashSet; -import java.util.Map; -import java.util.Set; - -/** - * 适配器配置集合, 用于配置加载, 线程不安全 - * - * @author rewerma @ 2018-10-20 - * @version 1.0.0 - */ -public class AdapterConfigs { - - /** - * 类型下对应所有配置名, 如: - * hbase - * ┗━ mytest_person.yml - * ┗━ mytest_role.yml - * ┗━ mytest_department.yml - */ - private static Map> configs = new HashMap<>(); - - public static void put(String key, String value) { - Set values = configs.get(key); - if (values == null) { - values = new LinkedHashSet<>(); - } - values.add(value); - configs.put(key, values); - } - - public static Set get(String key) { - return configs.get(key); - } - - public static void clear() { - configs.clear(); - } -} diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java new file mode 100644 index 00000000..711a52b6 --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java @@ -0,0 +1,46 @@ +package com.alibaba.otter.canal.client.adapter.support; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class MappingConfigsLoader { + + public static Map loadConfigs(String name) { + Map configContentMap = new HashMap<>(); + + // 先取本地文件,再取类路径 + File configDir = new File("../conf/" + name); + if (!configDir.exists()) { + URL url = MappingConfigsLoader.class.getClassLoader().getResource(""); + if (url != null) { + configDir = new File(url.getPath() + name + File.separator); + } + } + + File[] files = configDir.listFiles(); + if (files != null) { + for (File file : files) { + String fileName = file.getName(); + if (!fileName.endsWith(".yml")) { + continue; + } + try (InputStream in = new FileInputStream(file)) { + byte[] bytes = new byte[in.available()]; + in.read(bytes); + String configContent = new String(bytes, StandardCharsets.UTF_8); + configContentMap.put(fileName, configContent); + } catch (IOException e) { + throw new RuntimeException("Read " + name + "mapping config: " + fileName + " error. ", e); + } + } + } + + return configContentMap; + } +} diff --git a/client-adapter/elasticsearch/pom.xml b/client-adapter/elasticsearch/pom.xml index bff1bffd..2d6c3def 100644 --- a/client-adapter/elasticsearch/pom.xml +++ b/client-adapter/elasticsearch/pom.xml @@ -71,6 +71,26 @@ + + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java index 702fd042..24cc9029 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java @@ -63,12 +63,12 @@ public class ESAdapter implements OuterAdapter { @Override public void init(OuterAdapterConfig configuration) { try { - SPI spi = this.getClass().getAnnotation(SPI.class); - Map esSyncConfigTmp = ESSyncConfigLoader.load(spi.value()); + Map esSyncConfigTmp = ESSyncConfigLoader.load(); // 过滤不匹配的key的配置 esSyncConfigTmp.forEach((key, config) -> { if ((config.getOuterAdapterKey() == null && configuration.getKey() == null) - || config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { + || (config.getOuterAdapterKey() != null + && config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) { esSyncConfig.put(key, config); } }); diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java index 5d43572d..e83a96e6 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/config/ESSyncConfigLoader.java @@ -1,25 +1,13 @@ package com.alibaba.otter.canal.client.adapter.es.config; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; -import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; -import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; /** * ES 配置装载器 @@ -29,75 +17,25 @@ import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; */ public class ESSyncConfigLoader { - private static Logger logger = LoggerFactory - .getLogger(ESSyncConfigLoader.class); + private static Logger logger = LoggerFactory.getLogger(ESSyncConfigLoader.class); - public static synchronized Map load(String name) { + public static synchronized Map load() { logger.info("## Start loading es mapping config ... "); Map esSyncConfig = new LinkedHashMap<>(); - Collection configs = AdapterConfigs.get("es"); - if (configs == null) { - return esSyncConfig; - } - for (String c : configs) { - if (c == null) { - continue; - } - c = c.trim(); - if (c.equals("") || c.startsWith("#")) { - continue; - } - - ESSyncConfig config; - String configContent = null; - - if (c.endsWith(".yml")) { - configContent = readConfigContent(name + "/" + c); - } - - config = new Yaml().loadAs(configContent, ESSyncConfig.class); - + Map configContentMap = MappingConfigsLoader.loadConfigs("es"); + configContentMap.forEach((fileName, content) -> { + ESSyncConfig config = new Yaml().loadAs(content, ESSyncConfig.class); try { config.validate(); } catch (Exception e) { - throw new RuntimeException("ERROR Config: " + c, e); + throw new RuntimeException("ERROR Config: " + fileName + " " + e.getMessage(), e); } - esSyncConfig.put(c, config); - } + esSyncConfig.put(fileName, config); + }); logger.info("## ES mapping config loaded"); return esSyncConfig; } - - private static String readConfigContent(String config) { - InputStream in = null; - try { - // 先取本地文件,再取类路径 - File configFile = new File("../conf/" + config); - if (configFile.exists()) { - in = new FileInputStream(configFile); - } else { - in = ESSyncConfigLoader.class.getClassLoader().getResourceAsStream(config); - } - if (in == null) { - throw new RuntimeException("Config file: " + config + " not found."); - } - - byte[] bytes = new byte[in.available()]; - in.read(bytes); - return new String(bytes, StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException("Read es mapping config error ", e); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - // ignore - } - } - } } diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java index 680a3725..7ab197d1 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/ConfigLoadTest.java @@ -1,6 +1,5 @@ package com.alibaba.otter.canal.client.adapter.es.test; -import java.util.List; import java.util.Map; import org.junit.Assert; @@ -9,21 +8,20 @@ import org.junit.Test; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; public class ConfigLoadTest { @Before public void before() { - AdapterConfigs.put("es", "mytest_user.yml"); + // AdapterConfigs.put("es", "mytest_user.yml"); // 加载数据源连接池 DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); } @Test public void testLoad() { - Map configMap = ESSyncConfigLoader.load("es"); + Map configMap = ESSyncConfigLoader.load(); ESSyncConfig config = configMap.get("mytest_user.yml"); Assert.assertNotNull(config); Assert.assertEquals("defaultDS", config.getDataSourceKey()); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java index 5e269a53..2d59f565 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java @@ -2,28 +2,25 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; -import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; +import javax.sql.DataSource; + import org.elasticsearch.action.get.GetResponse; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.es.ESAdapter; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; -import javax.sql.DataSource; - public class LabelSyncJoinSub2Test { private ESAdapter esAdapter; @Before public void init() { - AdapterConfigs.put("es", "mytest_user_join_sub2.yml"); + // AdapterConfigs.put("es", "mytest_user_join_sub2.yml"); esAdapter = Common.init(); } diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java index 411e3066..eafcfe75 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java @@ -2,28 +2,25 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; -import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; +import javax.sql.DataSource; + import org.elasticsearch.action.get.GetResponse; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.es.ESAdapter; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; -import javax.sql.DataSource; - public class LabelSyncJoinSubTest { private ESAdapter esAdapter; @Before public void init() { - AdapterConfigs.put("es", "mytest_user_join_sub.yml"); + // AdapterConfigs.put("es", "mytest_user_join_sub.yml"); esAdapter = Common.init(); } diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java index 94e52242..c87669ba 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java @@ -2,28 +2,25 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; -import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; +import javax.sql.DataSource; + import org.elasticsearch.action.get.GetResponse; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.es.ESAdapter; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; -import javax.sql.DataSource; - public class RoleSyncJoinOne2Test { private ESAdapter esAdapter; @Before public void init() { - AdapterConfigs.put("es", "mytest_user_join_one2.yml"); + // AdapterConfigs.put("es", "mytest_user_join_one2.yml"); esAdapter = Common.init(); } diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java index e68b9812..a7a61d5a 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java @@ -4,14 +4,13 @@ import java.util.*; import javax.sql.DataSource; -import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.alibaba.otter.canal.client.adapter.es.ESAdapter; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; @@ -21,7 +20,7 @@ public class RoleSyncJoinOneTest { @Before public void init() { - AdapterConfigs.put("es", "mytest_user_join_one.yml"); + // AdapterConfigs.put("es", "mytest_user_join_one.yml"); esAdapter = Common.init(); } diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java index f8a5341a..532cbc89 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java @@ -4,16 +4,13 @@ import java.util.*; import javax.sql.DataSource; -import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.es.ESAdapter; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; @@ -23,7 +20,7 @@ public class UserSyncJoinOneTest { @Before public void init() { - AdapterConfigs.put("es", "mytest_user_join_one.yml"); +// AdapterConfigs.put("es", "mytest_user_join_one.yml"); esAdapter = Common.init(); } diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java index dccf24a3..bcc3b7f6 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java @@ -2,17 +2,13 @@ package com.alibaba.otter.canal.client.adapter.es.test.sync; import java.util.*; -import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import org.elasticsearch.action.get.GetResponse; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.es.ESAdapter; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; -import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; public class UserSyncSingleTest { @@ -21,7 +17,7 @@ public class UserSyncSingleTest { @Before public void init() { - AdapterConfigs.put("es", "mytest_user_single.yml"); + // AdapterConfigs.put("es", "mytest_user_single.yml"); esAdapter = Common.init(); } diff --git a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one.yml b/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one.yml deleted file mode 100644 index 8e2f4efa..00000000 --- a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one.yml +++ /dev/null @@ -1,10 +0,0 @@ -dataSourceKey: defaultDS -destination: example -esMapping: - _index: mytest_user - _type: _doc - _id: _id - sql: "select a.id as _id, concat(a.name,'_') as _name, a.role_id as _role_id, - b.role_name as _role_name, a.c_time as _c_time from user a - left join role b on b.id=a.role_id" - commitBatch: 3000 \ No newline at end of file diff --git a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one2.yml b/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one2.yml deleted file mode 100644 index 4a44c5e0..00000000 --- a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_one2.yml +++ /dev/null @@ -1,10 +0,0 @@ -dataSourceKey: defaultDS -destination: example -esMapping: - _index: mytest_user - _type: _doc - _id: _id - sql: "select a.id as _id, concat(a.name,'_') as _name, a.role_id as _role_id, - concat(b.role_name,'_') as _role_name, a.c_time as _c_time from user a - left join role b on b.id=a.role_id" - commitBatch: 3000 \ No newline at end of file diff --git a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub.yml b/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub.yml deleted file mode 100644 index 82c4e247..00000000 --- a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub.yml +++ /dev/null @@ -1,11 +0,0 @@ -dataSourceKey: defaultDS -destination: example -esMapping: - _index: mytest_user - _type: _doc - _id: _id - sql: "select a.id as _id, concat(a.name,'_') as _name, a.role_id as _role_id, - b.labels _labels, a.c_time as _c_time from user a - left join (select user_id, group_concat(label order by id desc separator ';') as labels from label - group by user_id) b on b.user_id=a.id" - commitBatch: 3000 \ No newline at end of file diff --git a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub2.yml b/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub2.yml deleted file mode 100644 index c8c22c36..00000000 --- a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_join_sub2.yml +++ /dev/null @@ -1,11 +0,0 @@ -dataSourceKey: defaultDS -destination: example -esMapping: - _index: mytest_user - _type: _doc - _id: _id - sql: "select a.id as _id, concat(a.name,'_') as _name, a.role_id as _role_id, - concat(b.labels, '_') as _labels, a.c_time as _c_time from user a - left join (select user_id, group_concat(label order by id desc separator ';') as labels from label - group by user_id) b on b.user_id=a.id" - commitBatch: 3000 \ No newline at end of file diff --git a/client-adapter/elasticsearch/src/test/resources/es/mytest_user_single.yml b/client-adapter/elasticsearch/src/test/resources/es/mytest_user_single.yml_ similarity index 100% rename from client-adapter/elasticsearch/src/test/resources/es/mytest_user_single.yml rename to client-adapter/elasticsearch/src/test/resources/es/mytest_user_single.yml_ diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml index 623d919b..10d08681 100644 --- a/client-adapter/hbase/pom.xml +++ b/client-adapter/hbase/pom.xml @@ -62,6 +62,26 @@ + + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 7c8d82db..a64ca3dc 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -47,12 +47,12 @@ public class HbaseAdapter implements OuterAdapter { @Override public void init(OuterAdapterConfig configuration) { try { - SPI spi = this.getClass().getAnnotation(SPI.class); - Map hbaseMappingTmp = MappingConfigLoader.load(spi.value()); + Map hbaseMappingTmp = MappingConfigLoader.load(); // 过滤不匹配的key的配置 hbaseMappingTmp.forEach((key, mappingConfig) -> { if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) - || mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { + || (mappingConfig.getOuterAdapterKey() != null + && mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) { hbaseMapping.put(key, mappingConfig); } }); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java index 058a60f1..1f8c05c1 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/config/MappingConfigLoader.java @@ -1,20 +1,13 @@ package com.alibaba.otter.canal.client.adapter.hbase.config; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; -import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.yaml.snakeyaml.Yaml; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; +import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; /** * HBase表映射配置加载器 @@ -24,128 +17,30 @@ import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; */ public class MappingConfigLoader { - private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class); + private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class); /** * 加载HBase表映射配置 * * @return 配置名/配置文件名--对象 */ - public static Map load(String name) { + public static Map load() { logger.info("## Start loading hbase mapping config ... "); Map result = new LinkedHashMap<>(); - Collection configs = AdapterConfigs.get(name); - if (configs == null) { - return result; - } - for (String c : configs) { - if (c == null) { - continue; - } - c = c.trim(); - if (c.equals("") || c.startsWith("#")) { - continue; - } - - MappingConfig config; - String configContent = null; - - if (c.endsWith(".yml")) { - configContent = readConfigContent(name + "/" + c); - } - - // 简单配置database.table@datasourcekey?rowKey=key1,key2 - if (StringUtils.isEmpty(configContent)) { - String[] mapping = c.split("\\?"); - String params = mapping.length == 2 ? mapping[1] : null; - String rowKey = null; - String srcMeta = mapping[0]; - // - if (params != null) { - for (String entry : params.split("&")) { - if ("rowKey".equals(entry.split("=")[0])) { - rowKey = entry.split("=")[1]; - } - } - } - String dsKey = srcMeta.split("@").length == 2 ? srcMeta.split("@")[1] : null; - String[] dbTable; - if (dsKey == null) { - dbTable = srcMeta.split("\\."); - - } else { - dbTable = srcMeta.split("@")[0].split("\\."); - } - - if (dbTable.length == 2) { - config = new MappingConfig(); - - MappingConfig.HbaseMapping hbaseMapping = new MappingConfig.HbaseMapping(); - hbaseMapping.setHbaseTable(dbTable[0].toUpperCase() + "." + dbTable[1].toUpperCase()); - hbaseMapping.setAutoCreateTable(true); - hbaseMapping.setDatabase(dbTable[0]); - hbaseMapping.setTable(dbTable[1]); - hbaseMapping.setMode(MappingConfig.Mode.PHOENIX); - hbaseMapping.setRowKey(rowKey); - // 有定义rowKey - if (rowKey != null) { - MappingConfig.ColumnItem columnItem = new MappingConfig.ColumnItem(); - columnItem.setRowKey(true); - columnItem.setColumn(rowKey); - hbaseMapping.setRowKeyColumn(columnItem); - } - config.setHbaseMapping(hbaseMapping); - config.setDataSourceKey(dsKey); - - } else { - throw new RuntimeException(String.format("配置项[%s]内容为空, 或格式不符合database.table", c)); - } - - } else { // 配置文件配置 - config = new Yaml().loadAs(configContent, MappingConfig.class); - } - + Map configContentMap = MappingConfigsLoader.loadConfigs("hbase"); + configContentMap.forEach((fileName, content) -> { + MappingConfig config = new Yaml().loadAs(content, MappingConfig.class); try { config.validate(); } catch (Exception e) { - throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e); + throw new RuntimeException("ERROR load Config: " + fileName + " " + e.getMessage(), e); } - result.put(c, config); - } + result.put(fileName, config); + }); logger.info("## Hbase mapping config loaded"); return result; } - - public static String readConfigContent(String config) { - InputStream in = null; - try { - // 先取本地文件,再取类路径 - File configFile = new File("../conf/" + config); - if (configFile.exists()) { - in = new FileInputStream(configFile); - } else { - in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config); - } - if (in == null) { - throw new RuntimeException("Config file not found."); - } - - byte[] bytes = new byte[in.available()]; - in.read(bytes); - return new String(bytes, StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException("Read hbase mapping config error. ", e); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - // ignore - } - } - } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index 2579e1f7..9e4737d6 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -1,9 +1,13 @@ package com.alibaba.otter.canal.adapter.launcher.config; +import java.sql.SQLException; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -19,7 +23,9 @@ import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; @ConfigurationProperties(prefix = "canal.conf") public class AdapterCanalConfig extends CanalClientConfig { - public final Set DESTINATIONS = new LinkedHashSet<>(); + public final Set DESTINATIONS = new LinkedHashSet<>(); + + private Map srcDataSources; @Override public void setCanalInstances(List canalInstances) { @@ -48,4 +54,37 @@ public class AdapterCanalConfig extends CanalClientConfig { } } } + + public Map getSrcDataSources() { + return srcDataSources; + } + + public void setSrcDataSources(Map srcDataSources) { + this.srcDataSources = srcDataSources; + + if (srcDataSources != null) { + for (Map.Entry entry : srcDataSources.entrySet()) { + DatasourceConfig datasourceConfig = entry.getValue(); + // 加载数据源连接池 + DruidDataSource ds = new DruidDataSource(); + ds.setDriverClassName(datasourceConfig.getDriver()); + ds.setUrl(datasourceConfig.getUrl()); + ds.setUsername(datasourceConfig.getUsername()); + ds.setPassword(datasourceConfig.getPassword()); + ds.setInitialSize(1); + ds.setMinIdle(1); + ds.setMaxActive(datasourceConfig.getMaxActive()); + ds.setMaxWait(60000); + ds.setTimeBetweenEvictionRunsMillis(60000); + ds.setMinEvictableIdleTimeMillis(300000); + ds.setValidationQuery("select 1"); + try { + ds.init(); + } catch (SQLException e) { + throw new RuntimeException(e.getMessage(), e); + } + DatasourceConfig.DATA_SOURCES.put(entry.getKey(), ds); + } + } + } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java deleted file mode 100644 index 0f18c996..00000000 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterConfig.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.alibaba.otter.canal.adapter.launcher.config; - -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; - -import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; -import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; - -/** - * 适配器数据源及配置文件列表配置类 - * - * @author rewerma @ 2018-10-20 - * @version 1.0.0 - */ -@Component -@ConfigurationProperties(prefix = "adapter.conf") -public class AdapterConfig { - - private static Logger logger = LoggerFactory.getLogger(AdapterConfig.class); - - private Map datasourceConfigs; - - private List adapterConfigs; - - public List getAdapterConfigs() { - return adapterConfigs; - } - - public Map getDatasourceConfigs() { - return datasourceConfigs; - } - - public void setDatasourceConfigs(Map datasourceConfigs) { - this.datasourceConfigs = datasourceConfigs; - - if (datasourceConfigs != null) { - for (Map.Entry entry : datasourceConfigs.entrySet()) { - DatasourceConfig datasourceConfig = entry.getValue(); - // 加载数据源连接池 - DruidDataSource ds = new DruidDataSource(); - ds.setDriverClassName(datasourceConfig.getDriver()); - ds.setUrl(datasourceConfig.getUrl()); - ds.setUsername(datasourceConfig.getUsername()); - ds.setPassword(datasourceConfig.getPassword()); - ds.setInitialSize(1); - ds.setMinIdle(1); - ds.setMaxActive(datasourceConfig.getMaxActive()); - ds.setMaxWait(60000); - ds.setTimeBetweenEvictionRunsMillis(60000); - ds.setMinEvictableIdleTimeMillis(300000); - ds.setValidationQuery("select 1"); - try { - ds.init(); - } catch (SQLException e) { - logger.error("ERROR ## failed to initial datasource: " + datasourceConfig.getUrl(), e); - } - DatasourceConfig.DATA_SOURCES.put(entry.getKey(), ds); - } - } - } - - public void setAdapterConfigs(List adapterConfigs) { - this.adapterConfigs = adapterConfigs; - - if (adapterConfigs != null) { - AdapterConfigs.clear(); - for (String adapterConfig : adapterConfigs) { - int idx = adapterConfig.indexOf("/"); - if (idx > -1) { - String type = adapterConfig.substring(0, idx); - String ymlFile = adapterConfig.substring(idx + 1); - AdapterConfigs.put(type, ymlFile); - } - } - } - } -} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java index 2e535535..15dafa79 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java @@ -12,7 +12,6 @@ import org.springframework.stereotype.Component; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; -import com.alibaba.otter.canal.adapter.launcher.config.AdapterConfig; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; /** @@ -33,8 +32,6 @@ public class CanalAdapterService { // 注入bean保证优先注册 @Resource - private AdapterConfig adapterConfig; - @Resource private SpringContext springContext; @Resource private SyncSwitch syncSwitch; diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 2c48fa84..61866595 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -16,6 +16,11 @@ canal.conf: # zookeeperHosts: slave1:2181 # bootstrapServers: slave1:6667 #or rocketmq # flatMessage: true +# srcDataSources: +# defaultDS: +# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true +# username: root +# password: 121212 canalInstances: - instance: example groups: @@ -25,7 +30,7 @@ canal.conf: # key: oracle1 # properties: # jdbc.driverClassName: oracle.jdbc.OracleDriver -# jdbc.url: jdbc:oracle:thin:@127.0.0.1:1521:orcl +# jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc.username: mytest # jdbc.password: m121212 # - name: rdb @@ -45,27 +50,9 @@ canal.conf: # properties: # cluster.name: elasticsearch # mqTopics: -# - mqMode: kafka +# - mqMode: kafka # or rocketmq # topic: example # groups: # - groupId: g2 # outAdapters: -# - name: logger -# mqTopics: -# - mqMode: rocketmq -# topic: example -# groups: -# - groupId: g2 -# outAdapters: -# - name: logger - -#adapter.conf: -# datasourceConfigs: -# defaultDS: -# url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true -# username: root -# password: 121212 -# adapterConfigs: -# - hbase/mytest_person2.yml -# - es/mytest_user.yml -# - rdb/mytest_user.yml +# - name: logger \ No newline at end of file diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml index 994d28aa..120aa5e1 100644 --- a/client-adapter/rdb/pom.xml +++ b/client-adapter/rdb/pom.xml @@ -80,6 +80,26 @@ + + maven-antrun-plugin + + + package + + run + + + + + + + + + + + + + \ No newline at end of file diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index ef1ffe01..185423ee 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.client.adapter.rdb; +import java.io.File; import java.sql.Connection; import java.sql.ResultSetMetaData; import java.sql.SQLException; @@ -14,7 +15,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; -import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfigLoader; +import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; import com.alibaba.otter.canal.client.adapter.support.*; @@ -33,12 +34,16 @@ public class RdbAdapter implements OuterAdapter { @Override public void init(OuterAdapterConfig configuration) { - SPI spi = this.getClass().getAnnotation(SPI.class); - Map rdbMappingTmp = MappingConfigLoader.load(spi.value()); + System.out.println("xxxxx: " + this.getClass().getClassLoader().getResource("").getPath()); + File file = new File(this.getClass().getClassLoader().getResource("").getPath() + "rdb" + File.separator); + System.out.println(file.getAbsolutePath()); + + Map rdbMappingTmp = ConfigLoader.load(); // 过滤不匹配的key的配置 rdbMappingTmp.forEach((key, mappingConfig) -> { if ((mappingConfig.getOuterAdapterKey() == null && configuration.getKey() == null) - || mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey())) { + || (mappingConfig.getOuterAdapterKey() != null + && mappingConfig.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) { rdbMapping.put(key, mappingConfig); } }); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java new file mode 100644 index 00000000..c2fe3339 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/ConfigLoader.java @@ -0,0 +1,46 @@ +package com.alibaba.otter.canal.client.adapter.rdb.config; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; + +/** + * RDB表映射配置加载器 + * + * @author rewerma 2018-11-07 下午02:41:34 + * @version 1.0.0 + */ +public class ConfigLoader { + + private static Logger logger = LoggerFactory.getLogger(ConfigLoader.class); + + /** + * 加载HBase表映射配置 + * + * @return 配置名/配置文件名--对象 + */ + public static Map load() { + logger.info("## Start loading rdb mapping config ... "); + + Map result = new LinkedHashMap<>(); + + Map configContentMap = MappingConfigsLoader.loadConfigs("rdb"); + configContentMap.forEach((fileName, content) -> { + MappingConfig config = new Yaml().loadAs(content, MappingConfig.class); + try { + config.validate(); + } catch (Exception e) { + throw new RuntimeException("ERROR Config: " + fileName + " " + e.getMessage(), e); + } + result.put(fileName, config); + }); + + logger.info("## Rdb mapping config loaded"); + return result; + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java deleted file mode 100644 index aefb467f..00000000 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfigLoader.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.rdb.config; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.yaml.snakeyaml.Yaml; - -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; - -/** - * RDB表映射配置加载器 - * - * @author rewerma 2018-11-07 下午02:41:34 - * @version 1.0.0 - */ -public class MappingConfigLoader { - - private static Logger logger = LoggerFactory.getLogger(MappingConfigLoader.class); - - /** - * 加载HBase表映射配置 - * - * @return 配置名/配置文件名--对象 - */ - public static Map load(String name) { - logger.info("## Start loading rdb mapping config ... "); - - Map result = new LinkedHashMap<>(); - - Collection configs = AdapterConfigs.get(name); - if (configs == null) { - return result; - } - for (String c : configs) { - if (c == null) { - continue; - } - c = c.trim(); - if (c.equals("") || c.startsWith("#")) { - continue; - } - - String configContent = null; - - if (c.endsWith(".yml")) { - configContent = readConfigContent(name + "/" + c); - } - - MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); - - try { - config.validate(); - } catch (Exception e) { - throw new RuntimeException("ERROR Config: " + c + " " + e.getMessage(), e); - } - result.put(c, config); - } - - logger.info("## Rdb mapping config loaded"); - return result; - } - - public static String readConfigContent(String config) { - InputStream in = null; - try { - // 先取本地文件,再取类路径 - File configFile = new File("../conf/" + config); - if (configFile.exists()) { - in = new FileInputStream(configFile); - } else { - in = MappingConfigLoader.class.getClassLoader().getResourceAsStream(config); - } - if (in == null) { - throw new RuntimeException("Rdb mapping config file not found."); - } - - byte[] bytes = new byte[in.available()]; - in.read(bytes); - return new String(bytes, StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException("Read rdb mapping config error. ", e); - } finally { - try { - if (in != null) { - in.close(); - } - } catch (IOException e) { - // ignore - } - } - } -} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index ab46bf4b..9a01fbf7 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -148,6 +148,7 @@ public class RdbSyncService { conn.commit(); } } catch (Exception e) { + logger.error(e.getMessage(), e); conn.rollback(); } finally { conn.close(); @@ -230,6 +231,7 @@ public class RdbSyncService { conn.commit(); } } catch (Exception e) { + logger.error(e.getMessage(), e); conn.rollback(); } finally { conn.close(); @@ -283,14 +285,13 @@ public class RdbSyncService { conn.commit(); } } catch (Exception e) { + logger.error(e.getMessage(), e); conn.rollback(); } finally { conn.close(); } } - - /** * 获取目标字段类型 * @@ -309,7 +310,7 @@ public class RdbSyncService { columnType = new LinkedHashMap<>(); final Map columnTypeTmp = columnType; String sql = "SELECT * FROM " + dbMapping.getTargetTable() + " WHERE 1=2"; - Util.sqlRS(conn, sql, rs -> { + Util.sqlRS(conn, sql, rs -> { try { ResultSetMetaData rsd = rs.getMetaData(); int columnCount = rsd.getColumnCount(); diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java index dea6cee7..5eeb0878 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/ConfigLoadTest.java @@ -6,23 +6,21 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; -import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfigLoader; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; public class ConfigLoadTest { @Before public void before() { - AdapterConfigs.put("oracle", "mytest_user.yml"); // 加载数据源连接池 DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); } @Test public void testLoad() { - Map configMap = MappingConfigLoader.load("oracle"); + Map configMap = ConfigLoader.load(); Assert.assertFalse(configMap.isEmpty()); } diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java index 4ae04643..5af9fdcb 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java @@ -14,7 +14,7 @@ public class Common { OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig(); outerAdapterConfig.setName("rdb"); - outerAdapterConfig.setKey("oralce1"); + //outerAdapterConfig.setKey("oralce1"); Map properties = new HashMap<>(); properties.put("jdbc.driveClassName", "oracle.jdbc.OracleDriver"); properties.put("jdbc.url", "jdbc:oracle:thin:@127.0.0.1:49161:XE"); diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java index 5b8a45e3..a00a9ce5 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java @@ -6,7 +6,6 @@ import org.junit.Before; import org.junit.Test; import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; -import com.alibaba.otter.canal.client.adapter.support.AdapterConfigs; import com.alibaba.otter.canal.client.adapter.support.Dml; public class OracleSyncTest { @@ -15,7 +14,6 @@ public class OracleSyncTest { @Before public void init() { - AdapterConfigs.put("rdb", "mytest_user.yml"); rdbAdapter = Common.init(); } diff --git a/client-adapter/rdb/src/test/resources/oracle/mytest_user.yml b/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml similarity index 100% rename from client-adapter/rdb/src/test/resources/oracle/mytest_user.yml rename to client-adapter/rdb/src/test/resources/rdb/mytest_user.yml From b45a27e231e3ef8228a47977c2642076b721f98e Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 12 Nov 2018 18:01:52 +0800 Subject: [PATCH 310/360] =?UTF-8?q?db=E5=90=8C=E6=AD=A5=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/rdb/RdbAdapter.java | 62 ++----------------- .../adapter/rdb/config/MappingConfig.java | 16 ++--- .../adapter/rdb/service/RdbEtlService.java | 25 ++++---- .../adapter/rdb/service/RdbSyncService.java | 20 ++---- .../client/adapter/rdb/support/SyncUtil.java | 39 ++++++++++++ 5 files changed, 71 insertions(+), 91 deletions(-) create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 185423ee..85f2f62c 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -1,10 +1,11 @@ package com.alibaba.otter.canal.client.adapter.rdb; -import java.io.File; import java.sql.Connection; -import java.sql.ResultSetMetaData; import java.sql.SQLException; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import javax.sql.DataSource; @@ -14,8 +15,8 @@ import org.slf4j.LoggerFactory; import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.OuterAdapter; -import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; import com.alibaba.otter.canal.client.adapter.support.*; @@ -34,10 +35,6 @@ public class RdbAdapter implements OuterAdapter { @Override public void init(OuterAdapterConfig configuration) { - System.out.println("xxxxx: " + this.getClass().getClassLoader().getResource("").getPath()); - File file = new File(this.getClass().getClassLoader().getResource("").getPath() + "rdb" + File.separator); - System.out.println(file.getAbsolutePath()); - Map rdbMappingTmp = ConfigLoader.load(); // 过滤不匹配的key的配置 rdbMappingTmp.forEach((key, mappingConfig) -> { @@ -73,55 +70,6 @@ public class RdbAdapter implements OuterAdapter { logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e); } - rdbMapping.values().forEach(config -> { - try { - MappingConfig.DbMapping dbMapping = config.getDbMapping(); - // 从源表加载所有字段名 - if (dbMapping.getAllColumns() == null) { - synchronized (RdbSyncService.class) { - if (dbMapping.getAllColumns() == null) { - DataSource srcDS = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); - Connection srcConn = srcDS.getConnection(); - String srcMetaSql = "SELECT * FROM " + dbMapping.getDatabase() + "." + dbMapping.getTable() - + " WHERE 1=2 "; - List srcColumns = new ArrayList<>(); - Util.sqlRS(srcConn, srcMetaSql, rs -> { - try { - ResultSetMetaData rmd = rs.getMetaData(); - int cnt = rmd.getColumnCount(); - for (int i = 1; i <= cnt; i++) { - srcColumns.add(rmd.getColumnName(i).toLowerCase()); - } - } catch (SQLException e) { - logger.error(e.getMessage(), e); - } - }); - Map columnsMap = new LinkedHashMap<>(); - - for (String srcColumn : srcColumns) { - String targetColumn = srcColumn; - if (dbMapping.getTargetColumns() != null) { - for (Map.Entry entry : dbMapping.getTargetColumns().entrySet()) { - String targetColumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - - if (srcColumnName != null - && srcColumnName.toLowerCase().equals(srcColumn.toUpperCase())) { - targetColumn = targetColumnName; - } - } - } - columnsMap.put(targetColumn, srcColumn); - } - dbMapping.setAllColumns(columnsMap); - } - } - } - } catch (SQLException e) { - logger.error(e.getMessage(), e); - } - }); - rdbSyncService = new RdbSyncService(dataSource); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index 95623ea4..40389f9a 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -79,7 +79,7 @@ public class MappingConfig { private int readBatch = 5000; private int commitBatch = 5000; // etl等批量提交大小 - private volatile Map allColumns; // mapAll为true,自动设置改字段 +// private volatile Map allColumns; // mapAll为true,自动设置改字段 public String getDatabase() { return database; @@ -161,12 +161,12 @@ public class MappingConfig { this.commitBatch = commitBatch; } - public Map getAllColumns() { - return allColumns; - } - - public void setAllColumns(Map allColumns) { - this.allColumns = allColumns; - } +// public Map getAllColumns() { +// return allColumns; +// } +// +// public void setAllColumns(Map allColumns) { +// this.allColumns = allColumns; +// } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java index 321c9f21..3cf4976a 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java @@ -13,6 +13,7 @@ import java.util.concurrent.atomic.AtomicLong; import javax.sql.DataSource; +import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -91,8 +92,8 @@ public class RdbEtlService { } else { sqlFinal = sql + " LIMIT " + offset + "," + cnt; } - Future future = executor.submit( - () -> executeSqlImport(params, srcDS, targetDS, sqlFinal, dbMapping, successCount, errMsg)); + Future future = executor + .submit(() -> executeSqlImport(srcDS, targetDS, sqlFinal, dbMapping, successCount, errMsg)); futures.add(future); } @@ -102,7 +103,7 @@ public class RdbEtlService { executor.shutdown(); } else { - executeSqlImport(params, srcDS, targetDS, sql.toString(), dbMapping, successCount, errMsg); + executeSqlImport(srcDS, targetDS, sql.toString(), dbMapping, successCount, errMsg); } logger.info( @@ -160,8 +161,8 @@ public class RdbEtlService { /** * 执行导入 */ - private static boolean executeSqlImport(List params, DataSource srcDS, DataSource targetDS, String sql, - DbMapping dbMapping, AtomicLong successCount, List errMsg) { + private static boolean executeSqlImport(DataSource srcDS, DataSource targetDS, String sql, DbMapping dbMapping, + AtomicLong successCount, List errMsg) { try { Util.sqlRS(srcDS, sql, rs -> { int idx = 1; @@ -172,16 +173,18 @@ public class RdbEtlService { Map columnType = new LinkedHashMap<>(); ResultSetMetaData rsd = rs.getMetaData(); int columnCount = rsd.getColumnCount(); + List columns = new ArrayList<>(); for (int i = 1; i <= columnCount; i++) { columnType.put(rsd.getColumnName(i).toLowerCase(), rsd.getColumnType(i)); + columns.add(rsd.getColumnName(i)); } - Map columnsMap; - if (dbMapping.isMapAll()) { - columnsMap = dbMapping.getAllColumns(); - } else { - columnsMap = dbMapping.getTargetColumns(); - } + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, columns); + // if (dbMapping.isMapAll()) { + // columnsMap = dbMapping.getAllColumns(); + // } else { + // columnsMap = dbMapping.getTargetColumns(); + // } StringBuilder insertSql = new StringBuilder(); insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 9a01fbf7..def43637 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -10,11 +10,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; import javax.sql.DataSource; -import com.alibaba.otter.canal.client.adapter.support.Util; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,8 +21,9 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; -import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.Util; /** * RDB同步操作业务 @@ -84,12 +83,7 @@ public class RdbSyncService { Connection conn = dataSource.getConnection(); conn.setAutoCommit(false); try { - Map columnsMap; - if (dbMapping.isMapAll()) { - columnsMap = dbMapping.getAllColumns(); - } else { - columnsMap = dbMapping.getTargetColumns(); - } + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); StringBuilder insertSql = new StringBuilder(); insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); @@ -181,12 +175,7 @@ public class RdbSyncService { conn.setAutoCommit(false); try { - Map columnsMap; - if (dbMapping.isMapAll()) { - columnsMap = dbMapping.getAllColumns(); - } else { - columnsMap = dbMapping.getTargetColumns(); - } + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); Map ctype = getTargetColumnType(conn, config); @@ -525,4 +514,5 @@ public class RdbSyncService { logger.error(e.getMessage(), e); } } + } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java new file mode 100644 index 00000000..5e2c38bb --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java @@ -0,0 +1,39 @@ +package com.alibaba.otter.canal.client.adapter.rdb.support; + +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; + +public class SyncUtil { + + public static Map getColumnsMap(MappingConfig.DbMapping dbMapping, Map data) { + return getColumnsMap(dbMapping, data.keySet()); + } + + public static Map getColumnsMap(MappingConfig.DbMapping dbMapping, Collection columns) { + Map columnsMap; + if (dbMapping.isMapAll()) { + columnsMap = new LinkedHashMap<>(); + for (String srcColumn : columns) { + boolean flag = true; + if (dbMapping.getTargetColumns() != null) { + for (Map.Entry entry : dbMapping.getTargetColumns().entrySet()) { + if (srcColumn.equals(entry.getValue())) { + columnsMap.put(entry.getKey(), srcColumn); + flag = false; + break; + } + } + } + if (flag) { + columnsMap.put(srcColumn, srcColumn); + } + } + } else { + columnsMap = dbMapping.getTargetColumns(); + } + return columnsMap; + } +} From e7ab25f8198ca9dcad112244e9c27ab7c8dabddd Mon Sep 17 00:00:00 2001 From: mcy Date: Mon, 12 Nov 2018 18:26:34 +0800 Subject: [PATCH 311/360] =?UTF-8?q?=E5=A2=9E=E5=8A=A0rdb=20adapter?= =?UTF-8?q?=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/README.md | 18 ++++------ client-adapter/rdb/README.md | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 client-adapter/rdb/README.md diff --git a/client-adapter/README.md b/client-adapter/README.md index 0164aac0..396624f6 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -146,6 +146,11 @@ spring: canal.conf: canalServerHost: 127.0.0.1:11111 flatMessage: true + srcDataSources: + defaultDS: + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 canalInstances: - instance: example adapterGroups: @@ -155,17 +160,8 @@ canal.conf: hbase.zookeeper.quorum: slave1 hbase.zookeeper.property.clientPort: 2181 zookeeper.znode.parent: /hbase - -adapter.conf: - datasourceConfigs: - defaultDS: - url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true - username: root - password: 121212 - adapterConfigs: - - hbase/mytest_person.yml ``` -其中指定了一个HBase表映射文件: mytest_person.yml +adapter将会自动加载 conf/hbase 下的所有.yml结尾的配置文件 ### 3.2 适配器表映射文件 修改 conf/hbase/mytest_person.yml文件: ``` @@ -251,7 +247,7 @@ create 'MYTEST.PERSON', {NAME=>'CF'} ``` #### 启动canal-adapter启动器 ``` -java -jar canal-adapter-launcher.jar +bin/startup.sh ``` #### 验证 修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log diff --git a/client-adapter/rdb/README.md b/client-adapter/rdb/README.md new file mode 100644 index 00000000..b579b671 --- /dev/null +++ b/client-adapter/rdb/README.md @@ -0,0 +1,67 @@ +## RDB适配器 +RDB adapter 用于适配mysql到任意关系型数据库(需支持jdbc)的数据同步及导入 +### 1.1 修改启动器配置: application.yml, 这里以oracle目标库为例 +``` +server: + port: 8081 +logging: + level: + com.alibaba.otter.canal.client.adapter.hbase: DEBUG +spring: + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + +canal.conf: + canalServerHost: 127.0.0.1:11111 + srcDataSources: + defaultDS: + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 + canalInstances: + - instance: example + groups: + - outAdapters: + - name: rdb + key: oracle1 + properties: + jdbc.driverClassName: oracle.jdbc.OracleDriver + jdbc.url: jdbc:oracle:thin:@localhost:49161:XE + jdbc.username: mytest + jdbc.password: m121212 +``` +其中 outAdapter 的配置: name统一为rdb, key为对应的数据源的唯一标识需和下面的表映射文件中的outerAdapterKey对应!! properties为目标库jdb的相关参数 +adapter将会自动加载 conf/rdb 下的所有.yml结尾的表映射配置文件 +### 1.2 适配器表映射文件 +修改 conf/rdb/mytest_user.yml文件: +``` +dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值 +destination: example # cannal的instance或者MQ的topic +outerAdapterKey: oracle1 # adapter key, 对应上面配置outAdapters中的key +dbMapping: + database: mytest # 源数据源的database/shcema + table: user # 源数据源表名 + targetTable: mytest.tb_user # 目标数据源的库名.表名 + targetPk: # 主键映射 + id: id # 如果是复合主键可以换行映射多个 + mapAll: true # 是否整表映射, 要求源表和目标表字段名一模一样 (如果targetColumns也配置了映射,则以targetColumns配置为准) +# targetColumns: # 字段映射, 格式: 目标表字段: 源表字段, 如果字段名一样源表字段名可不填 +# id: +# name: +# role_id: +# c_time: +# test1: +``` +导入的类型以目标表的元类型为准, 将自动转换 + +### 1.3 启动RDB数据同步 +#### 将目标库的jdbc jar包放入lib文件夹, 这里放入ojdbc6.jar + +#### 启动canal-adapter启动器 +``` +bin/startup.sh +``` +#### 验证 +修改mysql mytest.user表的数据, 将会自动同步到Oracle的MYTEST.TB_USER表下面, 并会打出DML的log From 3f8cdafea367f0c15504a8ffa38104b8acbad68d Mon Sep 17 00:00:00 2001 From: wuwo Date: Tue, 13 Nov 2018 11:14:37 +0800 Subject: [PATCH 312/360] fix MemoryTableMeta#processTableElement bug --- .../otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java index c53e1a08..dcd490f8 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/MemoryTableMeta.java @@ -213,6 +213,7 @@ public class MemoryTableMeta implements TableMetaTSDB { fieldMeta.setNullable(true); } else if (constraint instanceof SQLColumnPrimaryKey) { fieldMeta.setKey(true); + fieldMeta.setNullable(false); } else if (constraint instanceof SQLColumnUniqueKey) { fieldMeta.setUnique(true); } @@ -225,6 +226,7 @@ public class MemoryTableMeta implements TableMetaTSDB { String name = getSqlName(pk.getExpr()); FieldMeta field = tableMeta.getFieldMetaByName(name); field.setKey(true); + field.setNullable(false); } } else if (element instanceof MySqlUnique) { MySqlUnique column = (MySqlUnique) element; From 03a95221911cc94bcb7c8df8ad98ca9b642addb8 Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 13 Nov 2018 13:41:19 +0800 Subject: [PATCH 313/360] fixed event --- .../otter/canal/parse/inbound/mysql/MysqlEventParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index e8ca45e3..3c06ee6b 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -651,7 +651,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE throw new CanalParseException("command : 'show master status' has an error! pls check. you need (at least one of) the SUPER,REPLICATION CLIENT privilege(s) for this operation"); } EntryPosition endPosition = new EntryPosition(fields.get(0), Long.valueOf(fields.get(1))); - if (isGTIDMode && fields.size() > 4) { + if (isGTIDMode() && fields.size() > 4) { endPosition.setGtid(fields.get(4)); } return endPosition; From 154ba422f2ae6eb5d819f06f5469e256b30f454c Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 13 Nov 2018 14:10:48 +0800 Subject: [PATCH 314/360] fixed plugin --- client-adapter/launcher/pom.xml | 2 +- .../launcher/src/main/assembly/dev.xml | 102 +++++++++--------- .../launcher/src/main/assembly/release.xml | 8 +- .../launcher/src/main/bin/startup.bat | 2 +- .../launcher/src/main/bin/startup.sh | 4 + .../inbound/mysql/dbsync/LogEventConvert.java | 7 +- .../inbound/mysql/dbsync/TableMetaCache.java | 12 +++ 7 files changed, 77 insertions(+), 60 deletions(-) diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index d48e6e71..df31b8c8 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -137,7 +137,7 @@ jar-with-dependencies - ${project.basedir}/target/canal-adapter/lib + ${project.basedir}/target/canal-adapter/plugin diff --git a/client-adapter/launcher/src/main/assembly/dev.xml b/client-adapter/launcher/src/main/assembly/dev.xml index 84962aec..1138f3f3 100644 --- a/client-adapter/launcher/src/main/assembly/dev.xml +++ b/client-adapter/launcher/src/main/assembly/dev.xml @@ -6,36 +6,36 @@ false - - . - / - - README* - - - - ./src/main/bin - bin - - **/* - - 0755 - - - ./src/main/resources - /conf - - **/* + + . + / + + README* + + + + ./src/main/bin + bin + + **/* + + 0755 + + + ./src/main/resources + /conf + + **/* - - - - ../elasticsearch/src/main/resources/es - /conf/es - - **/* - - + + + + ../elasticsearch/src/main/resources/es + /conf/es + + **/* + + ../hbase/src/main/resources/hbase /conf/hbase @@ -43,27 +43,31 @@ **/* - - ../rdb/src/main/resources/ - /conf + + ../rdb/src/main/resources/ + /conf META-INF/** - - - target - logs - - **/* - - - - - - lib - - junit:junit - - - + + + target + logs + + **/* + + + + ${project.basedir}/target/canal-adapter/plugin + /plugin/ + + + + + lib + + junit:junit + + + diff --git a/client-adapter/launcher/src/main/assembly/release.xml b/client-adapter/launcher/src/main/assembly/release.xml index bebad929..456b4a6a 100644 --- a/client-adapter/launcher/src/main/assembly/release.xml +++ b/client-adapter/launcher/src/main/assembly/release.xml @@ -58,13 +58,9 @@ **/* - - ${project.basedir}/target/canal-adapter/lib - /lib/ - - *-jar-with-dependencies.jar - + ${project.basedir}/target/canal-adapter/plugin + /plugin/ diff --git a/client-adapter/launcher/src/main/bin/startup.bat b/client-adapter/launcher/src/main/bin/startup.bat index 0c464dff..5c834a27 100755 --- a/client-adapter/launcher/src/main/bin/startup.bat +++ b/client-adapter/launcher/src/main/bin/startup.bat @@ -8,7 +8,7 @@ if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% set conf_dir=%ENV_PATH%\..\conf set CLASSPATH=%conf_dir% -set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% +set CLASSPATH=%conf_dir%\..\lib\*;%conf_dir%\..\plugin\*;%CLASSPATH% set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 diff --git a/client-adapter/launcher/src/main/bin/startup.sh b/client-adapter/launcher/src/main/bin/startup.sh index 69993483..538733e1 100644 --- a/client-adapter/launcher/src/main/bin/startup.sh +++ b/client-adapter/launcher/src/main/bin/startup.sh @@ -53,6 +53,10 @@ ADAPTER_OPTS="-DappName=canal-adapter" for i in $base/lib/*; do CLASSPATH=$i:"$CLASSPATH"; done + +for i in $base/plugin/*; + do CLASSPATH=$i:"$CLASSPATH"; +done CLASSPATH="$base/conf:$CLASSPATH"; echo "cd to $bin_abs_path for workaround relative path" diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 5c5432c6..1ba0930d 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -646,15 +646,16 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar continue; } - if (fieldMeta != null && existOptionalMetaData) { + if (fieldMeta != null && existOptionalMetaData && tableMetaCache.isOnTSDB()) { // check column info boolean check = StringUtils.equalsIgnoreCase(fieldMeta.getColumnName(), info.name); check &= (fieldMeta.isUnsigned() == info.unsigned); check &= (fieldMeta.isNullable() == info.nullable); if (!check) { - throw new CanalParseException("MySQL8.0 unmatch column metadata & pls submit issue , db : " - + fieldMeta.toString() + " , binlog : " + info.toString() + throw new CanalParseException("MySQL8.0 unmatch column metadata & pls submit issue , table : " + + tableMeta.getFullName() + ", db fieldMeta : " + + fieldMeta.toString() + " , binlog fieldMeta : " + info.toString() + " , on : " + event.getHeader().getLogFileName() + ":" + event.getHeader().getLogPos()); } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java index 4c1cef3c..69b8a3e9 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/TableMetaCache.java @@ -39,6 +39,7 @@ public class TableMetaCache { public static final String EXTRA = "EXTRA"; private MysqlConnection connection; private boolean isOnRDS = false; + private boolean isOnTSDB = false; private TableMetaTSDB tableMetaTSDB; // 第一层tableId,第二层schema.table,解决tableId重复,对应多张表 @@ -67,6 +68,8 @@ public class TableMetaCache { } }); + } else { + isOnTSDB = true; } try { @@ -244,6 +247,15 @@ public class TableMetaCache { .toString(); } + + public boolean isOnTSDB() { + return isOnTSDB; + } + + public void setOnTSDB(boolean isOnTSDB) { + this.isOnTSDB = isOnTSDB; + } + public boolean isOnRDS() { return isOnRDS; } From 8c3ade2d50cc02b4e1e582a2518716ed3a726dce Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 13 Nov 2018 17:40:12 +0800 Subject: [PATCH 315/360] =?UTF-8?q?=E5=A4=96=E9=83=A8adapter=20plugin?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/ExtensionLoader.java | 96 +++---------------- .../support/URLClassExtensionLoader.java | 88 +++++++++++++++++ .../launcher/src/main/assembly/dev.xml | 4 - .../launcher/src/main/bin/startup.bat | 2 +- .../launcher/src/main/bin/startup.sh | 3 - 5 files changed, 100 insertions(+), 93 deletions(-) create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/URLClassExtensionLoader.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index 62ea78fd..b29bff4a 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -43,8 +43,6 @@ public class ExtensionLoader { private static final ConcurrentMap EXTENSION_KEY_INSTANCE = new ConcurrentHashMap<>(); - private static final ConcurrentMap> EXTENSION_KEY_INSTANCES = new ConcurrentHashMap<>(); - private final Class type; private final String classLoaderPolicy; @@ -180,6 +178,8 @@ public class ExtensionLoader { @SuppressWarnings("unchecked") private T createExtension(String name, String key) { + System.out.println("xxxxxxxxxxxxx"); + getExtensionClasses().forEach((k, v) -> logger.info("fffff: " + k + " " + v.getName())); Class clazz = getExtensionClasses().get(name); if (clazz == null) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type @@ -210,6 +210,7 @@ public class ExtensionLoader { } } } + return classes; } @@ -255,13 +256,13 @@ public class ExtensionLoader { Map> extensionClasses = new HashMap>(); - // 1. lib folder,customized extension classLoader (jar_dir/lib) - String dir = File.separator + this.getJarDirectoryPath() + File.separator + "lib"; + // 1. plugin folder,customized extension classLoader (jar_dir/plugin) + String dir = File.separator + this.getJarDirectoryPath() + File.separator + "plugin"; File externalLibDir = new File(dir); if (!externalLibDir.exists()) { externalLibDir = new File(File.separator + this.getJarDirectoryPath() + File.separator + "canal-adapter" - + File.separator + "lib"); + + File.separator + "plugin"); } logger.info("extension classpath dir: " + externalLibDir.getAbsolutePath()); if (externalLibDir.exists()) { @@ -279,49 +280,7 @@ public class ExtensionLoader { URLClassLoader localClassLoader; if (classLoaderPolicy == null || "".equals(classLoaderPolicy) || DEFAULT_CLASSLOADER_POLICY.equalsIgnoreCase(classLoaderPolicy)) { - localClassLoader = new URLClassLoader(new URL[] { url }, parent) { - - @Override - public Class loadClass(String name) throws ClassNotFoundException { - Class c = findLoadedClass(name); - if (c != null) { - return c; - } - - if (name.startsWith("java.") || name.startsWith("org.slf4j.") - || name.startsWith("org.apache.logging") - || name.startsWith("org.apache.commons.logging.")) { - // || name.startsWith("org.apache.hadoop.")) - // { - c = super.loadClass(name); - } - if (c != null) return c; - - try { - // 先加载jar内的class,可避免jar冲突 - c = findClass(name); - } catch (ClassNotFoundException e) { - c = null; - } - if (c != null) { - return c; - } - - return super.loadClass(name); - } - - @Override - public Enumeration getResources(String name) throws IOException { - @SuppressWarnings("unchecked") - Enumeration[] tmp = (Enumeration[]) new Enumeration[2]; - - tmp[0] = findResources(name); // local class - // path first - // tmp[1] = super.getResources(name); - - return new CompoundEnumeration<>(tmp); - } - }; + localClassLoader = new URLClassExtensionLoader(new URL[] { url }); } else { localClassLoader = new URLClassLoader(new URL[] { url }, parent); } @@ -331,48 +290,15 @@ public class ExtensionLoader { } } } + // 只加载外部spi, 不加载classpath // 2. load inner extension class with default classLoader - ClassLoader classLoader = findClassLoader(); - loadFile(extensionClasses, CANAL_DIRECTORY, classLoader); - loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader); + // ClassLoader classLoader = findClassLoader(); + // loadFile(extensionClasses, CANAL_DIRECTORY, classLoader); + // loadFile(extensionClasses, SERVICES_DIRECTORY, classLoader); return extensionClasses; } - public static class CompoundEnumeration implements Enumeration { - - private Enumeration[] enums; - private int index = 0; - - public CompoundEnumeration(Enumeration[] enums){ - this.enums = enums; - } - - private boolean next() { - while (this.index < this.enums.length) { - if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) { - return true; - } - - ++this.index; - } - - return false; - } - - public boolean hasMoreElements() { - return this.next(); - } - - public E nextElement() { - if (!this.next()) { - throw new NoSuchElementException(); - } else { - return this.enums[this.index].nextElement(); - } - } - } - private void loadFile(Map> extensionClasses, String dir, ClassLoader classLoader) { String fileName = dir + type.getName(); try { diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/URLClassExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/URLClassExtensionLoader.java new file mode 100644 index 00000000..725d23e8 --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/URLClassExtensionLoader.java @@ -0,0 +1,88 @@ +package com.alibaba.otter.canal.client.adapter.support; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.Enumeration; +import java.util.NoSuchElementException; + +public class URLClassExtensionLoader extends URLClassLoader { + public URLClassExtensionLoader(URL[] urls) { + super(urls); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + Class c = findLoadedClass(name); + if (c != null) { + return c; + } + + if (name.startsWith("java.") || name.startsWith("org.slf4j.") + || name.startsWith("org.apache.logging") + || name.startsWith("org.apache.commons.logging.")) { + // || name.startsWith("org.apache.hadoop.")) + // { + c = super.loadClass(name); + } + if (c != null) return c; + + try { + // 先加载jar内的class,可避免jar冲突 + c = findClass(name); + } catch (ClassNotFoundException e) { + c = null; + } + if (c != null) { + return c; + } + + return super.loadClass(name); + } + + @Override + public Enumeration getResources(String name) throws IOException { + @SuppressWarnings("unchecked") + Enumeration[] tmp = (Enumeration[]) new Enumeration[2]; + + tmp[0] = findResources(name); // local class + // path first + // tmp[1] = super.getResources(name); + + return new CompoundEnumeration<>(tmp); + } + + private static class CompoundEnumeration implements Enumeration { + + private Enumeration[] enums; + private int index = 0; + + public CompoundEnumeration(Enumeration[] enums){ + this.enums = enums; + } + + private boolean next() { + while (this.index < this.enums.length) { + if (this.enums[this.index] != null && this.enums[this.index].hasMoreElements()) { + return true; + } + + ++this.index; + } + + return false; + } + + public boolean hasMoreElements() { + return this.next(); + } + + public E nextElement() { + if (!this.next()) { + throw new NoSuchElementException(); + } else { + return this.enums[this.index].nextElement(); + } + } + } +} diff --git a/client-adapter/launcher/src/main/assembly/dev.xml b/client-adapter/launcher/src/main/assembly/dev.xml index 1138f3f3..7beaf269 100644 --- a/client-adapter/launcher/src/main/assembly/dev.xml +++ b/client-adapter/launcher/src/main/assembly/dev.xml @@ -57,10 +57,6 @@ **/* - - ${project.basedir}/target/canal-adapter/plugin - /plugin/ - diff --git a/client-adapter/launcher/src/main/bin/startup.bat b/client-adapter/launcher/src/main/bin/startup.bat index 5c834a27..0c464dff 100755 --- a/client-adapter/launcher/src/main/bin/startup.bat +++ b/client-adapter/launcher/src/main/bin/startup.bat @@ -8,7 +8,7 @@ if "%OS%" == "Windows_NT" set ENV_PATH=%~dp0% set conf_dir=%ENV_PATH%\..\conf set CLASSPATH=%conf_dir% -set CLASSPATH=%conf_dir%\..\lib\*;%conf_dir%\..\plugin\*;%CLASSPATH% +set CLASSPATH=%conf_dir%\..\lib\*;%CLASSPATH% set JAVA_MEM_OPTS= -Xms128m -Xmx512m -XX:PermSize=128m set JAVA_OPTS_EXT= -Djava.awt.headless=true -Djava.net.preferIPv4Stack=true -Dapplication.codeset=UTF-8 -Dfile.encoding=UTF-8 diff --git a/client-adapter/launcher/src/main/bin/startup.sh b/client-adapter/launcher/src/main/bin/startup.sh index 538733e1..650fba7d 100644 --- a/client-adapter/launcher/src/main/bin/startup.sh +++ b/client-adapter/launcher/src/main/bin/startup.sh @@ -54,9 +54,6 @@ for i in $base/lib/*; do CLASSPATH=$i:"$CLASSPATH"; done -for i in $base/plugin/*; - do CLASSPATH=$i:"$CLASSPATH"; -done CLASSPATH="$base/conf:$CLASSPATH"; echo "cd to $bin_abs_path for workaround relative path" From 8af1301062ae1b9402ed2c26279027513de2d3d7 Mon Sep 17 00:00:00 2001 From: Renkai Date: Tue, 13 Nov 2018 18:29:25 +0800 Subject: [PATCH 316/360] =?UTF-8?q?Kafka=E5=A2=9E=E5=8A=A0=E5=8F=82?= =?UTF-8?q?=E6=95=B0acks=E5=92=8CcompressionType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/deployer/CanalConstants.java | 2 ++ .../otter/canal/deployer/CanalLauncher.java | 2 ++ .../otter/canal/common/MQProperties.java | 19 +++++++++++++++++++ .../otter/canal/kafka/CanalKafkaProducer.java | 3 ++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index 2bfb32ea..aa9b2909 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -45,6 +45,8 @@ public class CanalConstants { public static final String CANAL_MQ_CANALBATCHSIZE = ROOT + "." + "mq.canalBatchSize"; public static final String CANAL_MQ_CANALGETTIMEOUT = ROOT + "." + "mq.canalGetTimeout"; public static final String CANAL_MQ_FLATMESSAGE = ROOT + "." + "mq.flatMessage"; + public static final String CANAL_MQ_COMPRESSION_TYPE = ROOT + "." + "mq.compressionType"; + public static final String CANAL_MQ_ACKS = ROOT + "." + "mq.acks"; public static String getInstanceModeKey(String destination) { return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination); diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 4b85f50c..f64e6184 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -100,6 +100,8 @@ public class CanalLauncher { CanalConstants.CANAL_MQ_CANALGETTIMEOUT))); mqProperties.setFlatMessage(Boolean.valueOf(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE))); + mqProperties.setCompressionType(CanalController.getProperty(properties,CanalConstants.CANAL_MQ_COMPRESSION_TYPE)); + mqProperties.setAcks(CanalController.getProperty(properties,CanalConstants.CANAL_MQ_ACKS)); return mqProperties; } diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index 29375e2f..3bc3394b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -20,6 +20,8 @@ public class MQProperties { private int canalBatchSize = 50; private Long canalGetTimeout = 100L; private boolean flatMessage = true; + private String compressionType = "none"; + private String acks = "all"; public static class CanalDestination { @@ -149,4 +151,21 @@ public class MQProperties { public void setProducerGroup(String producerGroup) { this.producerGroup = producerGroup; } + + public String getCompressionType() { + return compressionType; + } + + public void setCompressionType(String compressionType) { + this.compressionType = compressionType; + } + + public String getAcks() { + return acks; + } + + public void setAcks(String acks) { + this.acks = acks; + } + } diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index 78a67c13..d7cf05bb 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -37,7 +37,8 @@ public class CanalKafkaProducer implements CanalMQProducer { this.kafkaProperties = kafkaProperties; Properties properties = new Properties(); properties.put("bootstrap.servers", kafkaProperties.getServers()); - properties.put("acks", "all"); + properties.put("acks", kafkaProperties.getAcks()); + properties.put("compression.type",kafkaProperties.getCompressionType()); properties.put("retries", kafkaProperties.getRetries()); properties.put("batch.size", kafkaProperties.getBatchSize()); properties.put("linger.ms", kafkaProperties.getLingerMs()); From 20062d861d2236ffa381eecd430ff789856c0e65 Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 13 Nov 2018 19:59:18 +0800 Subject: [PATCH 317/360] fixed test code --- .../canal/client/adapter/support/ExtensionLoader.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index b29bff4a..860cb82b 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -2,13 +2,15 @@ package com.alibaba.otter.canal.client.adapter.support; import java.io.BufferedReader; import java.io.File; -import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.nio.file.Paths; -import java.util.*; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.regex.Pattern; @@ -178,8 +180,9 @@ public class ExtensionLoader { @SuppressWarnings("unchecked") private T createExtension(String name, String key) { - System.out.println("xxxxxxxxxxxxx"); - getExtensionClasses().forEach((k, v) -> logger.info("fffff: " + k + " " + v.getName())); + // System.out.println("xxxxxxxxxxxxx"); + // getExtensionClasses().forEach((k, v) -> logger.info("fffff: " + k + + // " " + v.getName())); Class clazz = getExtensionClasses().get(name); if (clazz == null) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type From d29fe5b04e96181eea29968cda116a4aedbf2c74 Mon Sep 17 00:00:00 2001 From: mcy Date: Tue, 13 Nov 2018 20:01:20 +0800 Subject: [PATCH 318/360] =?UTF-8?q?=E5=8E=BB=E6=8E=89=E8=B0=83=E8=AF=95?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../otter/canal/client/adapter/support/ExtensionLoader.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index b29bff4a..f1b08727 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -178,8 +178,6 @@ public class ExtensionLoader { @SuppressWarnings("unchecked") private T createExtension(String name, String key) { - System.out.println("xxxxxxxxxxxxx"); - getExtensionClasses().forEach((k, v) -> logger.info("fffff: " + k + " " + v.getName())); Class clazz = getExtensionClasses().get(name); if (clazz == null) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type From 5ad9cf3f163d73ceaf84f66959978f0e6d5c044f Mon Sep 17 00:00:00 2001 From: wuwo Date: Tue, 13 Nov 2018 20:54:36 +0800 Subject: [PATCH 319/360] let tsdbSnapshotInterval config work correctly --- .../manager/CanalInstanceWithManager.java | 2 +- .../inbound/mysql/LocalBinlogEventParser.java | 1 + .../parse/inbound/mysql/MysqlEventParser.java | 1 + .../inbound/mysql/tsdb/DatabaseTableMeta.java | 52 ++++++++++--------- .../mysql/tsdb/TableMetaTSDBBuilder.java | 1 - 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java index 154cd53f..11091695 100644 --- a/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java +++ b/instance/manager/src/main/java/com/alibaba/otter/canal/instance/manager/CanalInstanceWithManager.java @@ -304,7 +304,6 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { } boolean tsdbEnable = BooleanUtils.toBoolean(parameters.getTsdbEnable()); if (tsdbEnable) { - mysqlEventParser.setEnableTsdb(tsdbEnable); mysqlEventParser.setTableMetaTSDBFactory(new DefaultTableMetaTSDBFactory() { @Override @@ -327,6 +326,7 @@ public class CanalInstanceWithManager extends AbstractCanalInstance { } } }); + mysqlEventParser.setEnableTsdb(tsdbEnable); } eventParser = mysqlEventParser; } else if (type.isLocalBinlog()) { diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java index 4f6ace4f..db235d9b 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/LocalBinlogEventParser.java @@ -57,6 +57,7 @@ public class LocalBinlogEventParser extends AbstractMysqlEventParser implements ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter); ((DatabaseTableMeta) tableMetaTSDB).setSnapshotInterval(tsdbSnapshotInterval); ((DatabaseTableMeta) tableMetaTSDB).setSnapshotExpire(tsdbSnapshotExpire); + ((DatabaseTableMeta) tableMetaTSDB).init(destination); } tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index e8ca45e3..167d2460 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -124,6 +124,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE ((DatabaseTableMeta) tableMetaTSDB).setBlackFilter(eventBlackFilter); ((DatabaseTableMeta) tableMetaTSDB).setSnapshotInterval(tsdbSnapshotInterval); ((DatabaseTableMeta) tableMetaTSDB).setSnapshotExpire(tsdbSnapshotExpire); + ((DatabaseTableMeta) tableMetaTSDB).init(destination); } tableMetaCache = new TableMetaCache(metaConnection, tableMetaTSDB); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index 068db5ed..dd90ffbf 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -10,6 +10,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.regex.Pattern; @@ -62,9 +63,10 @@ public class DatabaseTableMeta implements TableMetaTSDB { } }); private ReadWriteLock lock = new ReentrantReadWriteLock(); + private AtomicBoolean initialized = new AtomicBoolean(false); private String destination; private MemoryTableMeta memoryTableMeta; - private MysqlConnection connection; // 查询meta信息的链接 + private volatile MysqlConnection connection; // 查询meta信息的链接 private CanalEventFilter filter; private CanalEventFilter blackFilter; private EntryPosition lastPosition; @@ -74,40 +76,42 @@ public class DatabaseTableMeta implements TableMetaTSDB { private int snapshotInterval = 24; private int snapshotExpire = 360; private ScheduledFuture scheduleSnapshotFuture; - + public DatabaseTableMeta(){ } @Override public boolean init(final String destination) { - this.destination = destination; - this.memoryTableMeta = new MemoryTableMeta(); + if (initialized.compareAndSet(false, true)) { + this.destination = destination; + this.memoryTableMeta = new MemoryTableMeta(); - // 24小时生成一份snapshot - if (snapshotInterval > 0) { - scheduleSnapshotFuture = scheduler.scheduleWithFixedDelay(new Runnable() { + // 24小时生成一份snapshot + if (snapshotInterval > 0) { + scheduleSnapshotFuture = scheduler.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - boolean applyResult = false; - try { - MDC.put("destination", destination); - applyResult = applySnapshotToDB(lastPosition, false); - } catch (Throwable e) { - logger.error("scheudle applySnapshotToDB faield", e); - } + @Override + public void run() { + boolean applyResult = false; + try { + MDC.put("destination", destination); + applyResult = applySnapshotToDB(lastPosition, false); + } catch (Throwable e) { + logger.error("scheudle applySnapshotToDB faield", e); + } - try { - MDC.put("destination", destination); - if (applyResult) { - snapshotExpire((int) TimeUnit.HOURS.toSeconds(snapshotExpire)); + try { + MDC.put("destination", destination); + if (applyResult) { + snapshotExpire((int) TimeUnit.HOURS.toSeconds(snapshotExpire)); + } + } catch (Throwable e) { + logger.error("scheudle snapshotExpire faield", e); } - } catch (Throwable e) { - logger.error("scheudle snapshotExpire faield", e); } - } - }, snapshotInterval, snapshotInterval, TimeUnit.HOURS); + }, snapshotInterval, snapshotInterval, TimeUnit.HOURS); + } } return true; } diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java index 107e1013..75b97f14 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/TableMetaTSDBBuilder.java @@ -36,7 +36,6 @@ public class TableMetaTSDBBuilder { } } TableMetaTSDB tableMetaTSDB = (TableMetaTSDB) applicationContext.getBean("tableMetaTSDB"); - tableMetaTSDB.init(destination); logger.info("{} init TableMetaTSDB with {}", destination, springXml); return tableMetaTSDB; } else { From 60e5554125b334569fb6df238bb48fb30eddf860 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 14 Nov 2018 11:00:55 +0800 Subject: [PATCH 320/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9bootstrapServers?= =?UTF-8?q?=E4=B8=BAmqServers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/adapter/support/CanalClientConfig.java | 10 +++++----- .../adapter/launcher/loader/CanalAdapterLoader.java | 4 ++-- .../launcher/src/main/resources/application.yml | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 97a7a426..4a93af3d 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -17,7 +17,7 @@ public class CanalClientConfig { private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! - private String bootstrapServers; // kafka or rocket mq 地址 + private String mqServers; // kafka or rocket mq 地址 private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 @@ -47,12 +47,12 @@ public class CanalClientConfig { this.zookeeperHosts = zookeeperHosts; } - public String getBootstrapServers() { - return bootstrapServers; + public String getMqServers() { + return mqServers; } - public void setBootstrapServers(String bootstrapServers) { - this.bootstrapServers = bootstrapServers; + public void setMqServers(String mqServers) { + this.mqServers = mqServers; } public List getMqTopics() { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 77567a87..b60d66f0 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -97,7 +97,7 @@ public class CanalAdapterLoader { canalOuterAdapterGroups.add(canalOuterAdapters); if (StringUtils.isBlank(topic.getMqMode()) || "rocketmq".equalsIgnoreCase(topic.getMqMode())) { CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig, - canalClientConfig.getBootstrapServers(), + canalClientConfig.getMqServers(), topic.getTopic(), group.getGroupId(), canalOuterAdapterGroups, @@ -106,7 +106,7 @@ public class CanalAdapterLoader { rocketMQWorker.start(); } else if ("kafka".equalsIgnoreCase(topic.getMqMode())) { CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig, - canalClientConfig.getBootstrapServers(), + canalClientConfig.getMqServers(), topic.getTopic(), group.getGroupId(), canalOuterAdapterGroups, diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 61866595..2943d09c 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -14,7 +14,7 @@ spring: canal.conf: canalServerHost: 127.0.0.1:11111 # zookeeperHosts: slave1:2181 -# bootstrapServers: slave1:6667 #or rocketmq +# mqServers: slave1:6667 #or rocketmq # flatMessage: true # srcDataSources: # defaultDS: From c376f0afd298ad7bea97fed0b62f771c386c9082 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 14 Nov 2018 11:14:32 +0800 Subject: [PATCH 321/360] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=B8=AD=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=20canal.mq.acks=20canal.mq.compressionType?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deployer/src/main/resources/canal.properties | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index cf2a225e..86c700f0 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -108,4 +108,6 @@ canal.mq.lingerMs=1 canal.mq.bufferMemory=33554432 canal.mq.canalBatchSize=50 canal.mq.canalGetTimeout=100 -canal.mq.flatMessage=true \ No newline at end of file +canal.mq.flatMessage=true +#canal.mq.compressionType=none +#canal.mq.acks=all \ No newline at end of file From 68d8aee6c31b03c321aeb69f77fabc519f24379c Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 14 Nov 2018 11:32:01 +0800 Subject: [PATCH 322/360] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/CanalClientConfig.java | 12 ++-- .../loader/CanalAdapterKafkaWorker.java | 2 +- .../launcher/loader/CanalAdapterLoader.java | 2 - .../loader/CanalAdapterRocketMQWorker.java | 2 +- .../launcher/loader/CanalAdapterWorker.java | 2 +- .../src/main/resources/application.yml | 2 + .../otter/canal/deployer/CanalLauncher.java | 57 +++++++++++++------ deployer/src/main/resources/canal.properties | 4 +- 8 files changed, 53 insertions(+), 30 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 4a93af3d..d2c19448 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -17,13 +17,13 @@ public class CanalClientConfig { private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! - private String mqServers; // kafka or rocket mq 地址 + private String mqServers; // kafka or rocket mq 地址 private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 private Integer batchSize; // 批大小 - private Integer retry; // 重试次数 + private Integer retries; // 重试次数 private Long timeout; // 消费超时时间 @@ -79,12 +79,12 @@ public class CanalClientConfig { this.batchSize = batchSize; } - public Integer getRetry() { - return retry; + public Integer getRetries() { + return retries; } - public void setRetry(Integer retry) { - this.retry = retry; + public void setRetries(Integer retries) { + this.retries = retries; } public Long getTimeout() { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 045fee37..8bafcf58 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -45,7 +45,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { while (!running) ; ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetry() == null ? 1 : canalClientConfig.getRetry(); + int retry = canalClientConfig.getRetries() == null ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒 while (running) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index b60d66f0..e2d9bea8 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -33,8 +33,6 @@ public class CanalAdapterLoader { private Map canalMQWorker = new HashMap<>(); - private Map outerAdapters = new HashMap<>(); // 配置文件对应adapter - private ExtensionLoader loader; public CanalAdapterLoader(CanalClientConfig canalClientConfig){ diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index 133e4919..5996ab99 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -43,7 +43,7 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { ; ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetry() == null ? 1 : canalClientConfig.getRetry(); + int retry = canalClientConfig.getRetries() == null ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒 while (running) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 52b4e1af..fabee436 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -64,7 +64,7 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { ; // waiting until running == true ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetry() == null ? 1 : canalClientConfig.getRetry(); + int retry = canalClientConfig.getRetries() == null ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 300000 : canalClientConfig.getTimeout(); // 默认超时5分钟 Integer batchSize = canalClientConfig.getBatchSize(); if (batchSize == null) { diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 2943d09c..697210f7 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -16,6 +16,8 @@ canal.conf: # zookeeperHosts: slave1:2181 # mqServers: slave1:6667 #or rocketmq # flatMessage: true + retries: + timeout: # srcDataSources: # defaultDS: # url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index f64e6184..80224380 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -85,23 +85,46 @@ public class CanalLauncher { private static MQProperties buildMQPosition(Properties properties) { MQProperties mqProperties = new MQProperties(); - mqProperties.setServers(CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS)); - mqProperties.setRetries(Integer.valueOf(CanalController.getProperty(properties, - CanalConstants.CANAL_MQ_RETRIES))); - mqProperties.setBatchSize(Integer.valueOf(CanalController.getProperty(properties, - CanalConstants.CANAL_MQ_BATCHSIZE))); - mqProperties.setLingerMs(Integer.valueOf(CanalController.getProperty(properties, - CanalConstants.CANAL_MQ_LINGERMS))); - mqProperties.setBufferMemory(Long.valueOf(CanalController.getProperty(properties, - CanalConstants.CANAL_MQ_BUFFERMEMORY))); - mqProperties.setCanalBatchSize(Integer.valueOf(CanalController.getProperty(properties, - CanalConstants.CANAL_MQ_CANALBATCHSIZE))); - mqProperties.setCanalGetTimeout(Long.valueOf(CanalController.getProperty(properties, - CanalConstants.CANAL_MQ_CANALGETTIMEOUT))); - mqProperties.setFlatMessage(Boolean.valueOf(CanalController.getProperty(properties, - CanalConstants.CANAL_MQ_FLATMESSAGE))); - mqProperties.setCompressionType(CanalController.getProperty(properties,CanalConstants.CANAL_MQ_COMPRESSION_TYPE)); - mqProperties.setAcks(CanalController.getProperty(properties,CanalConstants.CANAL_MQ_ACKS)); + String servers = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_SERVERS); + if (!StringUtils.isEmpty(servers)) { + mqProperties.setServers(servers); + } + String retires = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_RETRIES); + if (!StringUtils.isEmpty(retires)) { + mqProperties.setRetries(Integer.valueOf(retires)); + } + String batchSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BATCHSIZE); + if (!StringUtils.isEmpty(batchSize)) { + mqProperties.setBatchSize(Integer.valueOf(batchSize)); + } + String lingerMs = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_LINGERMS); + if (!StringUtils.isEmpty(lingerMs)) { + mqProperties.setLingerMs(Integer.valueOf(lingerMs)); + } + String bufferMemory = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY); + if (!StringUtils.isEmpty(bufferMemory)) { + mqProperties.setBufferMemory(Long.valueOf(bufferMemory)); + } + String canalBatchSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALBATCHSIZE); + if (!StringUtils.isEmpty(canalBatchSize)) { + mqProperties.setCanalBatchSize(Integer.valueOf(canalBatchSize)); + } + String canalGetTimeout = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_CANALGETTIMEOUT); + if (!StringUtils.isEmpty(canalGetTimeout)) { + mqProperties.setCanalGetTimeout(Long.valueOf(canalGetTimeout)); + } + String flatMessage = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_FLATMESSAGE); + if (!StringUtils.isEmpty(flatMessage)) { + mqProperties.setFlatMessage(Boolean.valueOf(flatMessage)); + } + String compressionType = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_COMPRESSION_TYPE); + if (!StringUtils.isEmpty(compressionType)) { + mqProperties.setCompressionType(compressionType); + } + String acks = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_ACKS); + if (!StringUtils.isEmpty(acks)) { + mqProperties.setAcks(acks); + } return mqProperties; } diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 86c700f0..d555c162 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -109,5 +109,5 @@ canal.mq.bufferMemory=33554432 canal.mq.canalBatchSize=50 canal.mq.canalGetTimeout=100 canal.mq.flatMessage=true -#canal.mq.compressionType=none -#canal.mq.acks=all \ No newline at end of file +canal.mq.compressionType= +canal.mq.acks= \ No newline at end of file From 31bf92ec8e5a4f909406e3a1d8eb85e0b55b7070 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 14 Nov 2018 11:49:22 +0800 Subject: [PATCH 323/360] =?UTF-8?q?=E9=85=8D=E7=BD=AE=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../launcher/loader/CanalAdapterKafkaWorker.java | 3 ++- .../launcher/loader/CanalAdapterRocketMQWorker.java | 3 ++- .../adapter/launcher/loader/CanalAdapterWorker.java | 3 ++- .../launcher/src/main/resources/application.yml | 12 ++++++------ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 8bafcf58..b93379a6 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -45,7 +45,8 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { while (!running) ; ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetries() == null ? 1 : canalClientConfig.getRetries(); + int retry = canalClientConfig.getRetries() == null + || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒 while (running) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index 5996ab99..f0ee5abc 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -43,7 +43,8 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { ; ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetries() == null ? 1 : canalClientConfig.getRetries(); + int retry = canalClientConfig.getRetries() == null + || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒 while (running) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index fabee436..95e8dd45 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -64,7 +64,8 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { ; // waiting until running == true ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetries() == null ? 1 : canalClientConfig.getRetries(); + int retry = canalClientConfig.getRetries() == null + || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 300000 : canalClientConfig.getTimeout(); // 默认超时5分钟 Integer batchSize = canalClientConfig.getBatchSize(); if (batchSize == null) { diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 697210f7..c86b43f0 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -16,18 +16,18 @@ canal.conf: # zookeeperHosts: slave1:2181 # mqServers: slave1:6667 #or rocketmq # flatMessage: true - retries: + retries: 0 timeout: # srcDataSources: # defaultDS: # url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true # username: root # password: 121212 - canalInstances: - - instance: example - groups: - - outAdapters: - - name: logger +# canalInstances: +# - instance: example +# groups: +# - outAdapters: +# - name: logger # - name: rdb # key: oracle1 # properties: From 827b872cf875e81008858e556924d62f9b7bc6ef Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 14 Nov 2018 18:05:55 +0800 Subject: [PATCH 324/360] =?UTF-8?q?rdb=20=E6=89=B9=E9=87=8F=E5=B9=B6?= =?UTF-8?q?=E8=A1=8C=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 12 +- .../canal/client/adapter/rdb/RdbAdapter.java | 12 +- .../adapter/rdb/service/BatchExecutor.java | 124 +++++++ .../adapter/rdb/service/RdbSyncService.java | 330 +++++++++--------- .../canal/client/adapter/rdb/test/DBTest.java | 71 ++-- 5 files changed, 360 insertions(+), 189 deletions(-) create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index c86b43f0..a1dcfeba 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -23,11 +23,11 @@ canal.conf: # url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true # username: root # password: 121212 -# canalInstances: -# - instance: example -# groups: -# - outAdapters: -# - name: logger + canalInstances: + - instance: example + groups: + - outAdapters: + - name: logger # - name: rdb # key: oracle1 # properties: @@ -35,6 +35,7 @@ canal.conf: # jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc.username: mytest # jdbc.password: m121212 +# threads: 5 # - name: rdb # key: postgres1 # properties: @@ -42,6 +43,7 @@ canal.conf: # jdbc.url: jdbc:postgresql://localhost:5432/postgres # jdbc.username: postgres # jdbc.password: 121212 +# threads: 5 # - name: hbase # properties: # hbase.zookeeper.quorum: 127.0.0.1 diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 85f2f62c..92bbeebc 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -59,7 +59,7 @@ public class RdbAdapter implements OuterAdapter { dataSource.setPassword(properties.get("jdbc.password")); dataSource.setInitialSize(1); dataSource.setMinIdle(1); - dataSource.setMaxActive(2); + dataSource.setMaxActive(20); dataSource.setMaxWait(60000); dataSource.setTimeBetweenEvictionRunsMillis(60000); dataSource.setMinEvictableIdleTimeMillis(300000); @@ -70,7 +70,12 @@ public class RdbAdapter implements OuterAdapter { logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e); } - rdbSyncService = new RdbSyncService(dataSource); + String threads = properties.get("threads"); + if (threads != null) { + rdbSyncService = new RdbSyncService(Integer.valueOf(threads), dataSource); + } else { + rdbSyncService = new RdbSyncService(null, dataSource); + } } @Override @@ -177,6 +182,9 @@ public class RdbAdapter implements OuterAdapter { @Override public void destroy() { + if (rdbSyncService != null) { + rdbSyncService.close(); + } if (dataSource != null) { dataSource.close(); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java new file mode 100644 index 00000000..e079c335 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java @@ -0,0 +1,124 @@ +package com.alibaba.otter.canal.client.adapter.rdb.service; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BatchExecutor { + + private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class); + + private Integer key; + private Connection conn; + private int commitSize = 3000; + private AtomicInteger idx = new AtomicInteger(0); + private ExecutorService executor = Executors.newFixedThreadPool(1); + private Lock commitLock = new ReentrantLock(); + private Condition condition = commitLock.newCondition(); + + public BatchExecutor(Integer key, Connection conn, Integer commitSize){ + this.key = key; + this.conn = conn; + if (commitSize != null) { + this.commitSize = commitSize; + } + + try { + this.conn.setAutoCommit(false); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + + public Integer getKey() { + return key; + } + + public Connection getConn() { + return conn; + } + + public static void setValue(List> values, int type, Object value) { + Map valueItem = new HashMap<>(); + valueItem.put("type", type); + valueItem.put("value", value); + values.add(valueItem); + } + + public void execute(String sql, List> values) { + try (PreparedStatement pstmt = conn.prepareStatement(sql)) { + int len = values.size(); + for (int i = 0; i < len; i++) { + int type = (Integer) values.get(i).get("type"); + Object value = values.get(i).get("value"); + RdbSyncService.setPStmt(type, pstmt, value, i + 1); + } + + pstmt.execute(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + int i = idx.incrementAndGet(); + + // 批次的第一次执行设置延时 + if (i == 1) { + executor.submit(() -> { + try { + commitLock.lock(); + if (!condition.await(5, TimeUnit.SECONDS)) { + // 超时提交 + commit(); + } + } catch (InterruptedException e) { + // ignore + } finally { + commitLock.unlock(); + } + }); + } + + if (i == commitSize) { + commit(); + } + } + + private void commit() { + try { + commitLock.lock(); + conn.commit(); + if (logger.isTraceEnabled()) { + logger.trace("Batch executor: " + key + " commit " + idx.get() + " rows"); + } + condition.signal(); + idx.set(0); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } finally { + commitLock.unlock(); + } + } + + public void close() { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } + } + executor.shutdown(); + } +} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index def43637..b4a6ecba 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -9,7 +9,7 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.*; import javax.sql.DataSource; @@ -37,10 +37,30 @@ public class RdbSyncService { private final Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); - private DataSource dataSource; + private BatchExecutor[] batchExecutors; - public RdbSyncService(DataSource dataSource){ - this.dataSource = dataSource; + private int threads = 1; + + private ExecutorService[] threadExecutors; + + public RdbSyncService(Integer threads, DataSource dataSource){ + try { + if (threads != null && threads > 1 && threads < 10) { + this.threads = threads; + } + batchExecutors = new BatchExecutor[this.threads]; + for (int i = 0; i < this.threads; i++) { + Connection conn = dataSource.getConnection(); + conn.setAutoCommit(false); + this.batchExecutors[i] = new BatchExecutor(i, conn, null); + } + threadExecutors = new ExecutorService[this.threads]; + for (int i = 0; i < this.threads; i++) { + threadExecutors[i] = Executors.newFixedThreadPool(1); + } + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } } public void sync(MappingConfig config, Dml dml) { @@ -69,7 +89,7 @@ public class RdbSyncService { * @param config 配置项 * @param dml DML数据 */ - private void insert(MappingConfig config, Dml dml) throws SQLException { + private void insert(MappingConfig config, Dml dml) { List> data = dml.getData(); if (data == null || data.isEmpty()) { return; @@ -77,75 +97,52 @@ public class RdbSyncService { DbMapping dbMapping = config.getDbMapping(); - int idx = 1; - boolean completed = false; + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); - Connection conn = dataSource.getConnection(); - conn.setAutoCommit(false); - try { - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); + StringBuilder insertSql = new StringBuilder(); + insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); - StringBuilder insertSql = new StringBuilder(); - insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); + columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); + int len = insertSql.length(); + insertSql.delete(len - 1, len).append(") VALUES ("); + int mapLen = columnsMap.size(); + for (int i = 0; i < mapLen; i++) { + insertSql.append("?,"); + } + len = insertSql.length(); + insertSql.delete(len - 1, len).append(")"); - columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); - int len = insertSql.length(); - insertSql.delete(len - 1, len).append(") VALUES ("); - int mapLen = columnsMap.size(); - for (int i = 0; i < mapLen; i++) { - insertSql.append("?,"); - } - len = insertSql.length(); - insertSql.delete(len - 1, len).append(")"); + Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); - Map ctype = getTargetColumnType(conn, config); + String sql = insertSql.toString(); + for (Map d : data) { + int pkHash = pkHash(dbMapping, d, threads); + ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[pkHash]; + checkQueue(tpe); + tpe.submit(() -> { + try { + BatchExecutor batchExecutor = batchExecutors[pkHash]; + List> values = new ArrayList<>(); - PreparedStatement pstmt = conn.prepareStatement(insertSql.toString()); - - for (Map d : data) { - pstmt.clearParameters(); - int i = 1; - for (Map.Entry entry : columnsMap.entrySet()) { - String targetClolumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetClolumnName; - } - - Integer type = ctype.get(targetClolumnName.toLowerCase()); - - Object value = d.get(srcColumnName); - if (value != null) { - if (type == null) { - throw new RuntimeException("No column: " + targetClolumnName + " found in target db"); + for (Map.Entry entry : columnsMap.entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetColumnName; } - setPStmt(type, pstmt, value, i); - } else { - pstmt.setNull(i, type); + Integer type = ctype.get(targetColumnName.toLowerCase()); + if (type == null) { + throw new RuntimeException("No column: " + targetColumnName + " found in target db"); + } + Object value = d.get(srcColumnName); + BatchExecutor.setValue(values, type, value); } - i++; + batchExecutor.execute(sql, values); + } catch (Exception e) { + logger.error(e.getMessage(), e); } - - pstmt.execute(); - if (logger.isTraceEnabled()) { - logger.trace("Insert into target table, sql: {}", insertSql); - } - - if (idx % config.getDbMapping().getCommitBatch() == 0) { - conn.commit(); - completed = true; - } - idx++; - } - if (!completed) { - conn.commit(); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - conn.rollback(); - } finally { - conn.close(); + }); } } @@ -155,7 +152,7 @@ public class RdbSyncService { * @param config 配置项 * @param dml DML数据 */ - private void update(MappingConfig config, Dml dml) throws SQLException { + private void update(MappingConfig config, Dml dml) { List> data = dml.getData(); if (data == null || data.isEmpty()) { return; @@ -169,61 +166,55 @@ public class RdbSyncService { DbMapping dbMapping = config.getDbMapping(); int idx = 1; - boolean completed = false; + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); - Connection conn = dataSource.getConnection(); - conn.setAutoCommit(false); + Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); - try { - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); + for (Map o : old) { + Map d = data.get(idx - 1); + int pkHash = pkHash(dbMapping, d, threads); - Map ctype = getTargetColumnType(conn, config); + ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[pkHash]; + checkQueue(tpe); + tpe.submit(() -> { + try { + BatchExecutor batchExecutor = batchExecutors[pkHash]; - for (Map o : old) { - Map d = data.get(idx - 1); - StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); - Map values = new LinkedHashMap<>(); - for (String srcColumnName : o.keySet()) { - List targetColumnNames = new ArrayList<>(); - columnsMap.forEach((targetColumn, srcColumn) -> { - if (srcColumnName.toLowerCase().equals(srcColumn)) { - targetColumnNames.add(targetColumn); - } - }); - if (!targetColumnNames.isEmpty()) { + StringBuilder updateSql = new StringBuilder(); + updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); - for (String targetColumnName : targetColumnNames) { - updateSql.append(targetColumnName).append("=?, "); - values.put(targetColumnName, d.get(srcColumnName)); + List> values = new ArrayList<>(); + for (String srcColumnName : o.keySet()) { + List targetColumnNames = new ArrayList<>(); + columnsMap.forEach((targetColumn, srcColumn) -> { + if (srcColumnName.toLowerCase().equals(srcColumn)) { + targetColumnNames.add(targetColumn); + } + }); + if (!targetColumnNames.isEmpty()) { + for (String targetColumnName : targetColumnNames) { + updateSql.append(targetColumnName).append("=?, "); + Integer type = ctype.get(targetColumnName.toLowerCase()); + BatchExecutor.setValue(values, type, d.get(srcColumnName)); + } } } - } - int len = updateSql.length(); - updateSql.delete(len - 2, len).append(" WHERE "); + int len = updateSql.length(); + updateSql.delete(len - 2, len).append(" WHERE "); - // 拼接主键 - appendCondition(dbMapping, updateSql, values, d); + // 拼接主键 + appendCondition(dbMapping, updateSql, ctype, values, d); - sqlExe(conn, updateSql.toString(), ctype, values); + if (logger.isTraceEnabled()) { + logger.trace("Update target table, sql: {}", updateSql); + } + batchExecutor.execute(updateSql.toString(), values); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + }); - if (logger.isTraceEnabled()) { - logger.trace("Update target table, sql: {}", updateSql); - } - if (idx % config.getDbMapping().getCommitBatch() == 0) { - conn.commit(); - completed = true; - } - idx++; - } - if (!completed) { - conn.commit(); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - conn.rollback(); - } finally { - conn.close(); + idx++; } } @@ -234,7 +225,7 @@ public class RdbSyncService { * @param dml * @throws SQLException */ - private void delete(MappingConfig config, Dml dml) throws SQLException { + private void delete(MappingConfig config, Dml dml) { List> data = dml.getData(); if (data == null || data.isEmpty()) { return; @@ -242,42 +233,32 @@ public class RdbSyncService { DbMapping dbMapping = config.getDbMapping(); - int idx = 1; - boolean completed = false; + Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); - Connection conn = dataSource.getConnection(); - conn.setAutoCommit(false); + for (Map d : data) { + int pkHash = pkHash(dbMapping, d, threads); - try { - Map ctype = getTargetColumnType(conn, config); + ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[pkHash]; + checkQueue(tpe); + tpe.submit(() -> { + try { + BatchExecutor batchExecutor = batchExecutors[pkHash]; + StringBuilder sql = new StringBuilder(); + sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); - for (Map d : data) { - StringBuilder sql = new StringBuilder(); - sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); + List> values = new ArrayList<>(); - Map values = new LinkedHashMap<>(); - // 拼接主键 - appendCondition(dbMapping, sql, values, d); + // 拼接主键 + appendCondition(dbMapping, sql, ctype, values, d); - sqlExe(conn, sql.toString(), ctype, values); - - if (logger.isTraceEnabled()) { - logger.trace("Delete from target table, sql: {}", sql); + if (logger.isTraceEnabled()) { + logger.trace("Delete from target table, sql: {}", sql); + } + batchExecutor.execute(sql.toString(), values); + } catch (Exception e) { + logger.error(e.getMessage(), e); } - if (idx % config.getDbMapping().getCommitBatch() == 0) { - conn.commit(); - completed = true; - } - idx++; - } - if (!completed) { - conn.commit(); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - conn.rollback(); - } finally { - conn.close(); + }); } } @@ -326,6 +307,9 @@ public class RdbSyncService { * @param i 索引号 */ public static void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException { + if (value == null) { + pstmt.setObject(i, type); + } switch (type) { case Types.BIT: case Types.BOOLEAN: @@ -431,7 +415,7 @@ public class RdbSyncService { break; case Types.DATE: if (value instanceof java.util.Date) { - pstmt.setDate(i, new java.sql.Date(((java.util.Date) value).getTime())); + pstmt.setDate(i, new Date(((java.util.Date) value).getTime())); } else if (value instanceof String) { String v = (String) value; if (!v.startsWith("0000-00-00")) { @@ -447,7 +431,7 @@ public class RdbSyncService { break; case Types.TIME: if (value instanceof java.util.Date) { - pstmt.setTime(i, new java.sql.Time(((java.util.Date) value).getTime())); + pstmt.setTime(i, new Time(((java.util.Date) value).getTime())); } else if (value instanceof String) { String v = (String) value; v = "T" + v; @@ -459,7 +443,7 @@ public class RdbSyncService { break; case Types.TIMESTAMP: if (value instanceof java.util.Date) { - pstmt.setTimestamp(i, new java.sql.Timestamp(((java.util.Date) value).getTime())); + pstmt.setTimestamp(i, new Timestamp(((java.util.Date) value).getTime())); } else if (value instanceof String) { String v = (String) value; if (!v.startsWith("0000-00-00")) { @@ -481,8 +465,8 @@ public class RdbSyncService { /** * 拼接主键 where条件 */ - private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map values, - Map d) { + private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map ctype, + List> values, Map d) { // 拼接主键 for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { String targetColumnName = entry.getKey(); @@ -491,28 +475,54 @@ public class RdbSyncService { srcColumnName = targetColumnName; } sql.append(targetColumnName).append("=? AND "); - values.put(targetColumnName, d.get(srcColumnName)); + Integer type = ctype.get(targetColumnName.toLowerCase()); + BatchExecutor.setValue(values, type, d.get(srcColumnName)); } int len = sql.length(); sql.delete(len - 4, len); } /** - * 执行sql + * 取主键hash */ - private static void sqlExe(Connection conn, String sql, Map ctype, Map values) { - try (PreparedStatement pstmt = conn.prepareStatement(sql)) { - int i = 1; - for (Map.Entry entry : values.entrySet()) { - String targetColumnName = entry.getKey(); - Object value = entry.getValue(); - Integer type = ctype.get(targetColumnName.toLowerCase()); - setPStmt(type, pstmt, value, i++); + private static int pkHash(DbMapping dbMapping, Map d, int threads) { + int hash = 0; + // 取主键 + for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetColumnName; } - pstmt.execute(); - } catch (SQLException e) { - logger.error(e.getMessage(), e); + Object value = d.get(srcColumnName); + if (value != null) { + hash += value.hashCode(); + } + } + hash = Math.abs(hash) % threads; + return Math.abs(hash); + } + + public void close() { + for (BatchExecutor batchExecutor : batchExecutors) { + batchExecutor.close(); + } + for (ExecutorService executorService : threadExecutors) { + executorService.shutdown(); } } + private void checkQueue(ThreadPoolExecutor tpe) { + // 防止队列过大 + while (tpe.getQueue().size() > 10000) { + try { + Thread.sleep(3000); + while (tpe.getQueue().size() > 5000) { + Thread.sleep(1000); + } + } catch (InterruptedException e) { + // ignore + } + } + } } diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java index 54e6c447..b4e4a02e 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java @@ -2,8 +2,10 @@ package com.alibaba.otter.canal.client.adapter.rdb.test; import java.io.BufferedReader; import java.io.Reader; -import java.math.BigDecimal; -import java.sql.*; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; import org.junit.Test; @@ -34,30 +36,55 @@ public class DBTest { dataSource.init(); Connection conn = dataSource.getConnection(); - Statement stmt = conn.createStatement(); - ResultSet rs = stmt.executeQuery("select * from user t where 1=2"); - ResultSetMetaData rsm = rs.getMetaData(); - int cnt = rsm.getColumnCount(); - for (int i = 1; i <= cnt; i++) { - System.out.println(rsm.getColumnName(i) + " " + rsm.getColumnType(i)); + conn.setAutoCommit(false); + PreparedStatement pstmt = conn + .prepareStatement("insert into user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)"); + + java.util.Date now = new java.util.Date(); + for (int i = 1; i <= 100000; i++) { + pstmt.clearParameters(); + pstmt.setLong(1, (long) i); + pstmt.setString(2, "test_" + i); + pstmt.setLong(3, (long) i % 4 + 1); + pstmt.setDate(4, new java.sql.Date(now.getTime())); + pstmt.setString(5, "tttt"); + pstmt.setBytes(6, null); + + pstmt.execute(); + if (i % 5000 == 0) { + conn.commit(); + } } + conn.commit(); - rs.close(); - stmt.close(); + pstmt.close(); -// PreparedStatement pstmt = conn -// .prepareStatement("insert into tb_user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)"); -// pstmt.setBigDecimal(1, new BigDecimal("5")); -// pstmt.setString(2, "test"); -// pstmt.setBigDecimal(3, new BigDecimal("1")); -// pstmt.setDate(4, new Date(new java.util.Date().getTime())); -// byte[] a = { (byte) 1, (byte) 2 }; -// pstmt.setBytes(5, a); -// pstmt.setBytes(6, a); -// pstmt.execute(); -// -// pstmt.close(); + // Statement stmt = conn.createStatement(); + // ResultSet rs = stmt.executeQuery("select * from user t where 1=2"); + // + // ResultSetMetaData rsm = rs.getMetaData(); + // int cnt = rsm.getColumnCount(); + // for (int i = 1; i <= cnt; i++) { + // System.out.println(rsm.getColumnName(i) + " " + rsm.getColumnType(i)); + // } + + // rs.close(); + // stmt.close(); + + // PreparedStatement pstmt = conn + // .prepareStatement("insert into tb_user (id,name,role_id,c_time,test1,test2) + // values (?,?,?,?,?,?)"); + // pstmt.setBigDecimal(1, new BigDecimal("5")); + // pstmt.setString(2, "test"); + // pstmt.setBigDecimal(3, new BigDecimal("1")); + // pstmt.setDate(4, new Date(new java.util.Date().getTime())); + // byte[] a = { (byte) 1, (byte) 2 }; + // pstmt.setBytes(5, a); + // pstmt.setBytes(6, a); + // pstmt.execute(); + // + // pstmt.close(); conn.close(); dataSource.close(); From d396a0b65c4879e7bca31785a81fd22b2d81bd9b Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 14 Nov 2018 18:21:50 +0800 Subject: [PATCH 325/360] =?UTF-8?q?rdb=20=E6=89=B9=E9=87=8F=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../launcher/src/main/resources/application.yml | 1 + .../otter/canal/client/adapter/rdb/RdbAdapter.java | 9 ++++----- .../canal/client/adapter/rdb/service/RdbSyncService.java | 9 ++++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index a1dcfeba..60356a87 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -36,6 +36,7 @@ canal.conf: # jdbc.username: mytest # jdbc.password: m121212 # threads: 5 +# commitSize: 3000 # - name: rdb # key: postgres1 # properties: diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 92bbeebc..4431e407 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -71,11 +71,10 @@ public class RdbAdapter implements OuterAdapter { } String threads = properties.get("threads"); - if (threads != null) { - rdbSyncService = new RdbSyncService(Integer.valueOf(threads), dataSource); - } else { - rdbSyncService = new RdbSyncService(null, dataSource); - } + String commitSize = properties.get("commitSize"); + rdbSyncService = new RdbSyncService(commitSize != null ? Integer.valueOf(commitSize) : null, + threads != null ? Integer.valueOf(threads) : null, + dataSource); } @Override diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index b4a6ecba..276724f9 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -9,7 +9,10 @@ import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; import javax.sql.DataSource; @@ -43,7 +46,7 @@ public class RdbSyncService { private ExecutorService[] threadExecutors; - public RdbSyncService(Integer threads, DataSource dataSource){ + public RdbSyncService(Integer commitSize, Integer threads, DataSource dataSource){ try { if (threads != null && threads > 1 && threads < 10) { this.threads = threads; @@ -52,7 +55,7 @@ public class RdbSyncService { for (int i = 0; i < this.threads; i++) { Connection conn = dataSource.getConnection(); conn.setAutoCommit(false); - this.batchExecutors[i] = new BatchExecutor(i, conn, null); + this.batchExecutors[i] = new BatchExecutor(i, conn, commitSize); } threadExecutors = new ExecutorService[this.threads]; for (int i = 0; i < this.threads; i++) { From aa68408c538cf4840eead881279b3ebe2dc896c9 Mon Sep 17 00:00:00 2001 From: wuwo Date: Wed, 14 Nov 2018 19:21:34 +0800 Subject: [PATCH 326/360] filter views when dumpTableMeta --- .../canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index dd90ffbf..e9bdc2ad 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -203,9 +203,13 @@ public class DatabaseTableMeta implements TableMetaTSDB { } for (String schema : schemas) { - packet = connection.query("show tables from `" + schema + "`"); + // filter views + packet = connection.query("show full tables from `" + schema + "` where Table_type = 'BASE TABLE'"); List tables = new ArrayList(); for (String table : packet.getFieldValues()) { + if("BASE TABLE".equalsIgnoreCase(table)){ + continue; + } String fullName = schema + "." + table; if (blackFilter == null || !blackFilter.filter(fullName)) { if (filter == null || filter.filter(fullName)) { From 915849b83ac8aa1c142cc06c7dbc388525afdcbb Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 14 Nov 2018 20:26:38 +0800 Subject: [PATCH 327/360] =?UTF-8?q?=E9=A2=84=E7=95=99sync(MappingConfig=20?= =?UTF-8?q?config,=20List=20dmlList)=E6=96=B9=E6=B3=95=20pk=20hash?= =?UTF-8?q?=E4=BB=A5old=20pk=E5=80=BC=E4=B8=BA=E5=87=86=E8=8B=A5=E5=AD=98?= =?UTF-8?q?=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 3 -- .../adapter/rdb/service/RdbSyncService.java | 30 ++++++++++++++++--- .../otter/canal/protocol/FlatMessage.java | 16 +++++++--- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 60356a87..24cff2b6 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -35,8 +35,6 @@ canal.conf: # jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc.username: mytest # jdbc.password: m121212 -# threads: 5 -# commitSize: 3000 # - name: rdb # key: postgres1 # properties: @@ -44,7 +42,6 @@ canal.conf: # jdbc.url: jdbc:postgresql://localhost:5432/postgres # jdbc.username: postgres # jdbc.password: 121212 -# threads: 5 # - name: hbase # properties: # hbase.zookeeper.quorum: 127.0.0.1 diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 276724f9..3755179b 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -66,6 +66,9 @@ public class RdbSyncService { } } + public void sync(MappingConfig config, List dmlList) { + } + public void sync(MappingConfig config, Dml dml) { try { if (config != null) { @@ -175,7 +178,7 @@ public class RdbSyncService { for (Map o : old) { Map d = data.get(idx - 1); - int pkHash = pkHash(dbMapping, d, threads); + int pkHash = pkHash(dbMapping, d, o, threads); ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[pkHash]; checkQueue(tpe); @@ -206,7 +209,7 @@ public class RdbSyncService { updateSql.delete(len - 2, len).append(" WHERE "); // 拼接主键 - appendCondition(dbMapping, updateSql, ctype, values, d); + appendCondition(dbMapping, updateSql, ctype, values, d, o); if (logger.isTraceEnabled()) { logger.trace("Update target table, sql: {}", updateSql); @@ -470,6 +473,11 @@ public class RdbSyncService { */ private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map ctype, List> values, Map d) { + appendCondition(dbMapping, sql, ctype, values, d, null); + } + + private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map ctype, + List> values, Map d, Map o) { // 拼接主键 for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { String targetColumnName = entry.getKey(); @@ -479,7 +487,12 @@ public class RdbSyncService { } sql.append(targetColumnName).append("=? AND "); Integer type = ctype.get(targetColumnName.toLowerCase()); - BatchExecutor.setValue(values, type, d.get(srcColumnName)); + // 如果有修改主键的情况 + if (o != null && o.containsKey(srcColumnName)) { + BatchExecutor.setValue(values, type, o.get(srcColumnName)); + } else { + BatchExecutor.setValue(values, type, d.get(srcColumnName)); + } } int len = sql.length(); sql.delete(len - 4, len); @@ -489,6 +502,10 @@ public class RdbSyncService { * 取主键hash */ private static int pkHash(DbMapping dbMapping, Map d, int threads) { + return pkHash(dbMapping, d, null, threads); + } + + private static int pkHash(DbMapping dbMapping, Map d, Map o, int threads) { int hash = 0; // 取主键 for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { @@ -497,7 +514,12 @@ public class RdbSyncService { if (srcColumnName == null) { srcColumnName = targetColumnName; } - Object value = d.get(srcColumnName); + Object value; + if (o != null && o.containsKey(srcColumnName)) { + value = o.get(srcColumnName); + } else { + value = d.get(srcColumnName); + } if (value != null) { hash += value.hashCode(); } diff --git a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java index 7a529f28..592db497 100644 --- a/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java +++ b/protocol/src/main/java/com/alibaba/otter/canal/protocol/FlatMessage.java @@ -171,8 +171,9 @@ public class FlatMessage implements Serializable { try { rowChange = CanalEntry.RowChange.parseFrom(entry.getStoreValue()); } catch (Exception e) { - throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" - + entry.toString(), e); + throw new RuntimeException( + "ERROR ## parser of eromanga-event has an error , data:" + entry.toString(), + e); } CanalEntry.EventType eventType = rowChange.getEventType(); @@ -214,7 +215,7 @@ public class FlatMessage implements Serializable { mysqlType.put(column.getName(), column.getMysqlType()); if (column.getIsNull()) { row.put(column.getName(), null); - } else { + } else { row.put(column.getName(), column.getValue()); } // 获取update为true的字段 @@ -284,7 +285,14 @@ public class FlatMessage implements Serializable { if (flatMessage.getData() != null) { int idx = 0; for (Map row : flatMessage.getData()) { - String value = row.get(pk); + Map o = flatMessage.getOld().get(idx); + String value; + // 如果old中有pk值说明主键有修改, 以旧的主键值hash为准 + if (o != null && o.containsKey(pk)) { + value = o.get(pk); + } else { + value = row.get(pk); + } if (value == null) { value = ""; } From 997646b2db5860e0750568ed1094f61025fb6f04 Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 15 Nov 2018 10:39:34 +0800 Subject: [PATCH 328/360] =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=98=AF=E5=90=A6?= =?UTF-8?q?=E6=8C=89=E4=B8=BB=E9=94=AEhash=E5=B9=B6=E8=A1=8C=E5=90=8C?= =?UTF-8?q?=E6=AD=A5,=20=E5=90=A6=E5=88=99=E6=8C=89=E8=A1=A8=E5=90=8Dhash?= =?UTF-8?q?=E8=A1=A8=E9=97=B4=E5=B9=B6=E8=A1=8C=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/README.md | 71 +++++++++++++++++++ .../src/main/resources/application.yml | 4 ++ client-adapter/rdb/README.md | 67 ----------------- .../canal/client/adapter/rdb/RdbAdapter.java | 5 +- .../adapter/rdb/config/MappingConfig.java | 46 +++++++----- .../adapter/rdb/service/BatchExecutor.java | 5 +- .../adapter/rdb/service/RdbSyncService.java | 66 ++++++++++++----- .../src/main/resources/rdb/mytest_user.yml | 1 + .../client/adapter/rdb/test/sync/Common.java | 9 +-- .../src/test/resources/rdb/mytest_user.yml | 2 + 10 files changed, 164 insertions(+), 112 deletions(-) delete mode 100644 client-adapter/rdb/README.md diff --git a/client-adapter/README.md b/client-adapter/README.md index 396624f6..7415b47b 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -251,3 +251,74 @@ bin/startup.sh ``` #### 验证 修改mysql mytest.person表的数据, 将会自动同步到HBase的MYTEST.PERSON表下面, 并会打出DML的log + + +## 四、关系型数据库适配器 + +RDB adapter 用于适配mysql到任意关系型数据库(需支持jdbc)的数据同步及导入 + +### 4.1 修改启动器配置: application.yml, 这里以oracle目标库为例 +``` +server: + port: 8081 +logging: + level: + com.alibaba.otter.canal.client.adapter.rdb: DEBUG +...... + +canal.conf: + canalServerHost: 127.0.0.1:11111 + srcDataSources: + defaultDS: + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 + canalInstances: + - instance: example + groups: + - outAdapters: + - name: rdb # 指定为rdb类型同步 + key: oracle1 # 指定adapter的唯一key, 与表映射配置中outerAdapterKey对应 + properties: + jdbc.driverClassName: oracle.jdbc.OracleDriver # jdbc驱动名, jdbc的jar包需要自行放致lib目录下 + jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc url + jdbc.username: mytest # jdbc username + jdbc.password: m121212 # jdbc password + threads: 5 # 并行执行的线程数, 默认为1 + commitSize: 3000 # 批次提交的最大行数 +``` +其中 outAdapter 的配置: name统一为rdb, key为对应的数据源的唯一标识需和下面的表映射文件中的outerAdapterKey对应, properties为目标库jdb的相关参数 +adapter将会自动加载 conf/rdb 下的所有.yml结尾的表映射配置文件 + +### 4.2 适配器表映射文件 +修改 conf/rdb/mytest_user.yml文件: +``` +dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值 +destination: example # cannal的instance或者MQ的topic +outerAdapterKey: oracle1 # adapter key, 对应上面配置outAdapters中的key +concurrent: true # 是否按主键hase并行同步, 并行同步的表必须保证主键不会更改及主键不能为其他同步表的外键!! +dbMapping: + database: mytest # 源数据源的database/shcema + table: user # 源数据源表名 + targetTable: mytest.tb_user # 目标数据源的库名.表名 + targetPk: # 主键映射 + id: id # 如果是复合主键可以换行映射多个 +# mapAll: true # 是否整表映射, 要求源表和目标表字段名一模一样 (如果targetColumns也配置了映射,则以targetColumns配置为准) + targetColumns: # 字段映射, 格式: 目标表字段: 源表字段, 如果字段名一样源表字段名可不填 + id: + name: + role_id: + c_time: + test1: +``` +导入的类型以目标表的元类型为准, 将自动转换 + +### 4.3 启动RDB数据同步 +#### 将目标库的jdbc jar包放入lib文件夹, 这里放入ojdbc6.jar + +#### 启动canal-adapter启动器 +``` +bin/startup.sh +``` +#### 验证 +修改mysql mytest.user表的数据, 将会自动同步到Oracle的MYTEST.TB_USER表下面, 并会打出DML的log diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 24cff2b6..e5d4f507 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -35,6 +35,8 @@ canal.conf: # jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc.username: mytest # jdbc.password: m121212 +# threads: 5 +# commitSize: 3000 # - name: rdb # key: postgres1 # properties: @@ -42,6 +44,8 @@ canal.conf: # jdbc.url: jdbc:postgresql://localhost:5432/postgres # jdbc.username: postgres # jdbc.password: 121212 +# threads: 1 +# commitSize: 3000 # - name: hbase # properties: # hbase.zookeeper.quorum: 127.0.0.1 diff --git a/client-adapter/rdb/README.md b/client-adapter/rdb/README.md deleted file mode 100644 index b579b671..00000000 --- a/client-adapter/rdb/README.md +++ /dev/null @@ -1,67 +0,0 @@ -## RDB适配器 -RDB adapter 用于适配mysql到任意关系型数据库(需支持jdbc)的数据同步及导入 -### 1.1 修改启动器配置: application.yml, 这里以oracle目标库为例 -``` -server: - port: 8081 -logging: - level: - com.alibaba.otter.canal.client.adapter.hbase: DEBUG -spring: - jackson: - date-format: yyyy-MM-dd HH:mm:ss - time-zone: GMT+8 - default-property-inclusion: non_null - -canal.conf: - canalServerHost: 127.0.0.1:11111 - srcDataSources: - defaultDS: - url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true - username: root - password: 121212 - canalInstances: - - instance: example - groups: - - outAdapters: - - name: rdb - key: oracle1 - properties: - jdbc.driverClassName: oracle.jdbc.OracleDriver - jdbc.url: jdbc:oracle:thin:@localhost:49161:XE - jdbc.username: mytest - jdbc.password: m121212 -``` -其中 outAdapter 的配置: name统一为rdb, key为对应的数据源的唯一标识需和下面的表映射文件中的outerAdapterKey对应!! properties为目标库jdb的相关参数 -adapter将会自动加载 conf/rdb 下的所有.yml结尾的表映射配置文件 -### 1.2 适配器表映射文件 -修改 conf/rdb/mytest_user.yml文件: -``` -dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值 -destination: example # cannal的instance或者MQ的topic -outerAdapterKey: oracle1 # adapter key, 对应上面配置outAdapters中的key -dbMapping: - database: mytest # 源数据源的database/shcema - table: user # 源数据源表名 - targetTable: mytest.tb_user # 目标数据源的库名.表名 - targetPk: # 主键映射 - id: id # 如果是复合主键可以换行映射多个 - mapAll: true # 是否整表映射, 要求源表和目标表字段名一模一样 (如果targetColumns也配置了映射,则以targetColumns配置为准) -# targetColumns: # 字段映射, 格式: 目标表字段: 源表字段, 如果字段名一样源表字段名可不填 -# id: -# name: -# role_id: -# c_time: -# test1: -``` -导入的类型以目标表的元类型为准, 将自动转换 - -### 1.3 启动RDB数据同步 -#### 将目标库的jdbc jar包放入lib文件夹, 这里放入ojdbc6.jar - -#### 启动canal-adapter启动器 -``` -bin/startup.sh -``` -#### 验证 -修改mysql mytest.user表的数据, 将会自动同步到Oracle的MYTEST.TB_USER表下面, 并会打出DML的log diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 4431e407..68f740a7 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -2,10 +2,7 @@ package com.alibaba.otter.canal.client.adapter.rdb; import java.sql.Connection; import java.sql.SQLException; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import javax.sql.DataSource; diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java index 40389f9a..1d32147a 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/config/MappingConfig.java @@ -18,6 +18,8 @@ public class MappingConfig { private String outerAdapterKey; // 对应适配器的key + private Boolean concurrent; // 是否并行同步 + private DbMapping dbMapping; // db映射配置 public String getDataSourceKey() { @@ -36,6 +38,14 @@ public class MappingConfig { this.outerAdapterKey = outerAdapterKey; } + public Boolean getConcurrent() { + return concurrent == null ? false : concurrent; + } + + public void setConcurrent(Boolean concurrent) { + this.concurrent = concurrent; + } + public DbMapping getDbMapping() { return dbMapping; } @@ -66,20 +76,20 @@ public class MappingConfig { public static class DbMapping { - private String database; // 数据库名或schema名 - private String table; // 表面名 - private Map targetPk; // 目标表主键字段 - private boolean mapAll = false; // 映射所有字段 - private String targetTable; // 目标表名 - private Map targetColumns; // 目标表字段映射 + private String database; // 数据库名或schema名 + private String table; // 表面名 + private Map targetPk; // 目标表主键字段 + private boolean mapAll = false; // 映射所有字段 + private String targetTable; // 目标表名 + private Map targetColumns; // 目标表字段映射 - private String etlCondition; // etl条件sql + private String etlCondition; // etl条件sql - private Set families = new LinkedHashSet<>(); // column family列表 - private int readBatch = 5000; - private int commitBatch = 5000; // etl等批量提交大小 + private Set families = new LinkedHashSet<>(); // column family列表 + private int readBatch = 5000; + private int commitBatch = 5000; // etl等批量提交大小 -// private volatile Map allColumns; // mapAll为true,自动设置改字段 + // private volatile Map allColumns; // mapAll为true,自动设置改字段 public String getDatabase() { return database; @@ -161,12 +171,12 @@ public class MappingConfig { this.commitBatch = commitBatch; } -// public Map getAllColumns() { -// return allColumns; -// } -// -// public void setAllColumns(Map allColumns) { -// this.allColumns = allColumns; -// } + // public Map getAllColumns() { + // return allColumns; + // } + // + // public void setAllColumns(Map allColumns) { + // this.allColumns = allColumns; + // } } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java index e079c335..ef0ec222 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java @@ -78,12 +78,13 @@ public class BatchExecutor { executor.submit(() -> { try { commitLock.lock(); + conn.commit(); //直接提交一次 if (!condition.await(5, TimeUnit.SECONDS)) { // 超时提交 commit(); } - } catch (InterruptedException e) { - // ignore + } catch (Exception e) { + logger.error(e.getMessage(), e); } finally { commitLock.unlock(); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 3755179b..0eff385b 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -48,7 +48,7 @@ public class RdbSyncService { public RdbSyncService(Integer commitSize, Integer threads, DataSource dataSource){ try { - if (threads != null && threads > 1 && threads < 10) { + if (threads != null && threads > 1 && threads <= 10) { this.threads = threads; } batchExecutors = new BatchExecutor[this.threads]; @@ -121,13 +121,24 @@ public class RdbSyncService { Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); String sql = insertSql.toString(); + Integer tbHash = null; + // 如果不是并行同步的表 + if (!config.getConcurrent()) { + // 按表名hash到一个线程 + tbHash = Math.abs(Math.abs(dbMapping.getTargetTable().hashCode()) % threads); + } for (Map d : data) { - int pkHash = pkHash(dbMapping, d, threads); - ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[pkHash]; + int hash; + if (tbHash != null) { + hash = tbHash; + } else { + hash = pkHash(dbMapping, d, threads); + } + ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; checkQueue(tpe); tpe.submit(() -> { try { - BatchExecutor batchExecutor = batchExecutors[pkHash]; + BatchExecutor batchExecutor = batchExecutors[hash]; List> values = new ArrayList<>(); for (Map.Entry entry : columnsMap.entrySet()) { @@ -176,15 +187,26 @@ public class RdbSyncService { Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); + Integer tbHash = null; + if (!config.getConcurrent()) { + // 按表名hash到一个线程 + tbHash = Math.abs(Math.abs(dbMapping.getTargetTable().hashCode()) % threads); + } for (Map o : old) { Map d = data.get(idx - 1); - int pkHash = pkHash(dbMapping, d, o, threads); - ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[pkHash]; + int hash; + if (tbHash != null) { + hash = tbHash; + } else { + hash = pkHash(dbMapping, d, o, threads); + } + + ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; checkQueue(tpe); tpe.submit(() -> { try { - BatchExecutor batchExecutor = batchExecutors[pkHash]; + BatchExecutor batchExecutor = batchExecutors[hash]; StringBuilder updateSql = new StringBuilder(); updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); @@ -211,10 +233,11 @@ public class RdbSyncService { // 拼接主键 appendCondition(dbMapping, updateSql, ctype, values, d, o); - if (logger.isTraceEnabled()) { - logger.trace("Update target table, sql: {}", updateSql); - } batchExecutor.execute(updateSql.toString(), values); + + if (logger.isDebugEnabled()) { + logger.debug("Execute sql: {}", updateSql); + } } catch (Exception e) { logger.error(e.getMessage(), e); } @@ -240,15 +263,24 @@ public class RdbSyncService { DbMapping dbMapping = config.getDbMapping(); Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); - + Integer tbHash = null; + if (!config.getConcurrent()) { + // 按表名hash到一个线程 + tbHash = Math.abs(Math.abs(dbMapping.getTargetTable().hashCode()) % threads); + } for (Map d : data) { - int pkHash = pkHash(dbMapping, d, threads); + int hash; + if (tbHash != null) { + hash = tbHash; + } else { + hash = pkHash(dbMapping, d, threads); + } - ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[pkHash]; + ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; checkQueue(tpe); tpe.submit(() -> { try { - BatchExecutor batchExecutor = batchExecutors[pkHash]; + BatchExecutor batchExecutor = batchExecutors[hash]; StringBuilder sql = new StringBuilder(); sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); @@ -257,10 +289,10 @@ public class RdbSyncService { // 拼接主键 appendCondition(dbMapping, sql, ctype, values, d); - if (logger.isTraceEnabled()) { - logger.trace("Delete from target table, sql: {}", sql); - } batchExecutor.execute(sql.toString(), values); + if (logger.isDebugEnabled()) { + logger.debug("Execute sql: {}", sql); + } } catch (Exception e) { logger.error(e.getMessage(), e); } diff --git a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml index 56d8e518..9b40dd75 100644 --- a/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/main/resources/rdb/mytest_user.yml @@ -1,6 +1,7 @@ dataSourceKey: defaultDS destination: example outerAdapterKey: oracle1 +concurrent: true dbMapping: database: mytest table: user diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java index 5af9fdcb..9d2d117d 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/Common.java @@ -1,20 +1,21 @@ package com.alibaba.otter.canal.client.adapter.rdb.test.sync; +import java.util.HashMap; +import java.util.Map; + import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; import com.alibaba.otter.canal.client.adapter.rdb.test.TestConstant; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; -import java.util.HashMap; -import java.util.Map; - public class Common { + public static RdbAdapter init() { DatasourceConfig.DATA_SOURCES.put("defaultDS", TestConstant.dataSource); OuterAdapterConfig outerAdapterConfig = new OuterAdapterConfig(); outerAdapterConfig.setName("rdb"); - //outerAdapterConfig.setKey("oralce1"); + outerAdapterConfig.setKey("oracle1"); Map properties = new HashMap<>(); properties.put("jdbc.driveClassName", "oracle.jdbc.OracleDriver"); properties.put("jdbc.url", "jdbc:oracle:thin:@127.0.0.1:49161:XE"); diff --git a/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml b/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml index a7bede03..9b40dd75 100644 --- a/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml +++ b/client-adapter/rdb/src/test/resources/rdb/mytest_user.yml @@ -1,5 +1,7 @@ dataSourceKey: defaultDS destination: example +outerAdapterKey: oracle1 +concurrent: true dbMapping: database: mytest table: user From b457ecd387d19c54250dc69cccc5c931cf4ce80d Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 15 Nov 2018 17:09:53 +0800 Subject: [PATCH 329/360] =?UTF-8?q?adapter=20=E4=B8=BB=E9=85=8D=E7=BD=AEap?= =?UTF-8?q?plication.yml=E5=8A=A8=E6=80=81=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/adapter/support/Constant.java | 6 ++ .../adapter/support/MappingConfigsLoader.java | 2 +- client-adapter/launcher/pom.xml | 5 + .../launcher/CanalAdapterApplication.java | 6 +- .../launcher/config/RefresherConfig.java | 22 +++++ .../launcher/loader/CanalAdapterLoader.java | 33 ++++++- .../launcher/loader/CanalAdapterService.java | 49 ++++++---- .../monitor/ApplicationRunningMonitor.java | 94 +++++++++++++++++++ .../src/main/resources/application.yml | 1 + 9 files changed, 192 insertions(+), 26 deletions(-) create mode 100644 client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Constant.java create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java create mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Constant.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Constant.java new file mode 100644 index 00000000..9a75f765 --- /dev/null +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Constant.java @@ -0,0 +1,6 @@ +package com.alibaba.otter.canal.client.adapter.support; + +public class Constant { + + public static final String CONF_DIR = "conf"; +} diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java index 711a52b6..c437731b 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java @@ -15,7 +15,7 @@ public class MappingConfigsLoader { Map configContentMap = new HashMap<>(); // 先取本地文件,再取类路径 - File configDir = new File("../conf/" + name); + File configDir = new File(".." + File.separator + Constant.CONF_DIR + File.separator + name); if (!configDir.exists()) { URL url = MappingConfigsLoader.class.getClassLoader().getResource(""); if (url != null) { diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index df31b8c8..6a313934 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -49,6 +49,11 @@ spring-boot-configuration-processor provided + + org.springframework.cloud + spring-cloud-context + 2.0.0.RELEASE + org.apache.curator curator-recipes diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java index 99140b0c..83f4b350 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.adapter.launcher; +import org.springframework.boot.Banner; +import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; @@ -13,6 +15,8 @@ import org.springframework.boot.builder.SpringApplicationBuilder; public class CanalAdapterApplication { public static void main(String[] args) { - new SpringApplicationBuilder(CanalAdapterApplication.class).run(args); + SpringApplication application = new SpringApplication(CanalAdapterApplication.class); + application.setBannerMode(Banner.Mode.OFF); + application.run(args); } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java new file mode 100644 index 00000000..127b7ceb --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java @@ -0,0 +1,22 @@ +package com.alibaba.otter.canal.adapter.launcher.config; + +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.cloud.context.scope.refresh.RefreshScope; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class RefresherConfig { + + @Bean + public RefreshScope refreshScope() { + return new RefreshScope(); + } + + @Bean + public ContextRefresher contextRefresher(ConfigurableApplicationContext configurableApplicationContext, + RefreshScope refreshScope) { + return new ContextRefresher(configurableApplicationContext, refreshScope); + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index e2d9bea8..900d1207 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -6,8 +6,10 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -136,7 +138,6 @@ public class CanalAdapterLoader { logger.info("Load canal adapter: {} succeed", config.getName()); } catch (Exception e) { logger.error("Load canal adapter: {} failed", config.getName(), e); - System.exit(0); } } @@ -144,19 +145,41 @@ public class CanalAdapterLoader { * 销毁所有适配器 为防止canal实例太多造成销毁阻塞, 并行销毁 */ public void destroy() { - if (canalWorkers.size() > 0) { + if (!canalWorkers.isEmpty()) { ExecutorService stopExecutorService = Executors.newFixedThreadPool(canalWorkers.size()); + List> futures = new ArrayList<>(); for (CanalAdapterWorker canalAdapterWorker : canalWorkers.values()) { - stopExecutorService.submit(canalAdapterWorker::stop); + futures.add(stopExecutorService.submit(() -> { + canalAdapterWorker.stop(); + return true; + })); } + futures.forEach(future -> { + try { + future.get(); + } catch (Exception e) { + // ignore + } + }); stopExecutorService.shutdown(); } - if (canalMQWorker.size() > 0) { + if (!canalMQWorker.isEmpty()) { ExecutorService stopMQWorkerService = Executors.newFixedThreadPool(canalMQWorker.size()); + List> futures = new ArrayList<>(); for (AbstractCanalAdapterWorker canalAdapterMQWorker : canalMQWorker.values()) { - stopMQWorkerService.submit(canalAdapterMQWorker::stop); + futures.add(stopMQWorkerService.submit(() -> { + canalAdapterMQWorker.stop(); + return true; + })); } + futures.forEach(future -> { + try { + future.get(); + } catch (Exception e) { + // ignore + } + }); stopMQWorkerService.shutdown(); } logger.info("All canal adapters destroyed"); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java index 15dafa79..79eb0be1 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterService.java @@ -4,14 +4,15 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; -import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; -import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.stereotype.Component; import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; import com.alibaba.otter.canal.adapter.launcher.config.AdapterCanalConfig; +import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; /** @@ -21,42 +22,51 @@ import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; * @version 1.0.0 */ @Component +@RefreshScope public class CanalAdapterService { - private static final Logger logger = LoggerFactory.getLogger(CanalAdapterService.class); + private static final Logger logger = LoggerFactory.getLogger(CanalAdapterService.class); - private static CanalAdapterLoader adapterLoader; + private CanalAdapterLoader adapterLoader; @Resource - private AdapterCanalConfig adapterCanalConfig; + private AdapterCanalConfig adapterCanalConfig; // 注入bean保证优先注册 @Resource - private SpringContext springContext; + private SpringContext springContext; @Resource - private SyncSwitch syncSwitch; + private SyncSwitch syncSwitch; + + private volatile boolean running = false; @PostConstruct - public void init() { - if (adapterLoader == null) { - try { - logger.info("## start the canal client adapters."); - adapterLoader = new CanalAdapterLoader(adapterCanalConfig); - adapterLoader.init(); - logger.info("## the canal client adapters are running now ......"); - } catch (Throwable e) { - logger.error("## something goes wrong when starting up the canal client adapters:", e); - System.exit(0); - } + public synchronized void init() { + if (running) { + return; + } + try { + logger.info("## start the canal client adapters."); + adapterLoader = new CanalAdapterLoader(adapterCanalConfig); + adapterLoader.init(); + running = true; + logger.info("## the canal client adapters are running now ......"); + } catch (Exception e) { + logger.error("## something goes wrong when starting up the canal client adapters:", e); } } @PreDestroy - public void destroy() { + public synchronized void destroy() { + if (!running) { + return; + } try { + running = false; logger.info("## stop the canal client adapters"); if (adapterLoader != null) { adapterLoader.destroy(); + adapterLoader = null; } for (DruidDataSource druidDataSource : DatasourceConfig.DATA_SOURCES.values()) { try { @@ -65,6 +75,7 @@ public class CanalAdapterService { logger.error(e.getMessage(), e); } } + DatasourceConfig.DATA_SOURCES.clear(); } catch (Throwable e) { logger.warn("## something goes wrong when stopping canal client adapters:", e); } finally { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java new file mode 100644 index 00000000..b1be1b76 --- /dev/null +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java @@ -0,0 +1,94 @@ +package com.alibaba.otter.canal.adapter.launcher.monitor; + +import java.io.File; +import java.io.FileReader; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.annotation.Resource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cloud.context.refresh.ContextRefresher; +import org.springframework.stereotype.Component; +import org.yaml.snakeyaml.Yaml; + +import com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterService; +import com.alibaba.otter.canal.client.adapter.support.Constant; + +@Component +public class ApplicationRunningMonitor { + + private static final Logger logger = LoggerFactory.getLogger(ApplicationRunningMonitor.class); + + @Resource + private ContextRefresher contextRefresher; + + @Resource + private CanalAdapterService canalAdapterService; + + private ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1); + + private static volatile long applicationLastModify = -1L; + + @PostConstruct + public void init() { + URL url = this.getClass().getClassLoader().getResource(""); + String path; + if (url != null) { + path = url.getPath(); + } else { + path = new File("").getAbsolutePath(); + } + File file = null; + if (path != null) { + file = new File(path + ".." + File.separator + Constant.CONF_DIR + File.separator + "application.yml"); + if (!file.exists()) { + file = new File(path + "application.yml"); + } + } + if (file == null || !file.exists()) { + throw new RuntimeException("application.yml config file not found."); + } + File configFile = file; + scheduled.scheduleWithFixedDelay(() -> { + if (applicationLastModify == -1L) { + applicationLastModify = configFile.lastModified(); + } else { + if (configFile.lastModified() != applicationLastModify) { + applicationLastModify = configFile.lastModified(); + + try { + // 检查yml格式 + new Yaml().loadAs(new FileReader(configFile), Map.class); + + canalAdapterService.destroy(); + + // refresh context + contextRefresher.refresh(); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + // ignore + } + canalAdapterService.init(); + logger.info("## adapter application config reloaded."); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + } + }, 5, 5, TimeUnit.SECONDS); + } + + @PreDestroy + public void destroy() { + scheduled.shutdown(); + } +} diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index e5d4f507..f862bb0e 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -2,6 +2,7 @@ server: port: 8081 logging: level: +# org.springframework: WARN com.alibaba.otter.canal.client.adapter.hbase: DEBUG com.alibaba.otter.canal.client.adapter.es: DEBUG com.alibaba.otter.canal.client.adapter.rdb: DEBUG From 7992b883d2e88aa19aaed88f0108512355c23353 Mon Sep 17 00:00:00 2001 From: agapple Date: Thu, 15 Nov 2018 19:40:23 +0800 Subject: [PATCH 330/360] fixed binlog checksum --- .../otter/canal/parse/inbound/mysql/MysqlConnection.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index e3056cb9..c12ca1a3 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -406,7 +406,8 @@ public class MysqlConnection implements ErosaConnection { // 如果不设置会出现错误: Slave can not handle replication events with the // checksum that master is configured to log // 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码 - update("set @master_binlog_checksum= '@@global.binlog_checksum'"); + // '@@global.binlog_checksum'需要去掉单引号,在mysql 5.6.29下导致master退出 + update("set @master_binlog_checksum= @@global.binlog_checksum"); } catch (Exception e) { logger.warn("update master_binlog_checksum failed", e); } From 7ee7ba0e05ae2c5f294670b46a5bcb92f34e5f0b Mon Sep 17 00:00:00 2001 From: mcy Date: Thu, 15 Nov 2018 22:47:08 +0800 Subject: [PATCH 331/360] =?UTF-8?q?rdb=20=E6=89=B9=E9=87=8F=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/support/Util.java | 30 +++ .../client/adapter/hbase/HbaseAdapter.java | 5 +- .../monitor/ApplicationRunningMonitor.java | 112 ++++----- .../src/main/resources/application.yml | 22 +- client-adapter/rdb/pom.xml | 10 +- .../canal/client/adapter/rdb/RdbAdapter.java | 63 ++++- .../adapter/rdb/service/BatchExecutor.java | 48 ++-- .../adapter/rdb/service/RdbSyncService.java | 238 +++++++++++------- 8 files changed, 338 insertions(+), 190 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java index ac2c32c3..e3c8032c 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/Util.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.client.adapter.support; +import java.io.File; +import java.net.URL; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; @@ -9,6 +11,7 @@ import java.util.function.Function; import javax.sql.DataSource; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -87,4 +90,31 @@ public class Util { } } } + + public static File getConfDirPath() { + return getConfDirPath(""); + } + + public static File getConfDirPath(String subConf) { + URL url = Util.class.getClassLoader().getResource(""); + String path; + if (url != null) { + path = url.getPath(); + } else { + path = new File("").getAbsolutePath(); + } + File file = null; + if (path != null) { + file = new File( + path + ".." + File.separator + Constant.CONF_DIR + File.separator + StringUtils.trimToEmpty(subConf)); + if (!file.exists()) { + file = new File(path + StringUtils.trimToEmpty(subConf)); + } + } + if (file == null || !file.exists()) { + throw new RuntimeException("Config dir not found."); + } + + return file; + } } diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index a64ca3dc..9c0395f3 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; @@ -37,8 +38,8 @@ public class HbaseAdapter implements OuterAdapter { private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class); - private Map hbaseMapping = new HashMap<>(); // 文件名对应配置 - private Map mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 + private Map hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置 + private Map mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置 private Connection conn; private HbaseSyncService hbaseSyncService; diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java index b1be1b76..0397464d 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java @@ -2,16 +2,16 @@ package com.alibaba.otter.canal.adapter.launcher.monitor; import java.io.File; import java.io.FileReader; -import java.net.URL; import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.annotation.Resource; +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.cloud.context.refresh.ContextRefresher; @@ -19,76 +19,72 @@ import org.springframework.stereotype.Component; import org.yaml.snakeyaml.Yaml; import com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterService; -import com.alibaba.otter.canal.client.adapter.support.Constant; +import com.alibaba.otter.canal.client.adapter.support.Util; @Component public class ApplicationRunningMonitor { - private static final Logger logger = LoggerFactory.getLogger(ApplicationRunningMonitor.class); + private static final Logger logger = LoggerFactory.getLogger(ApplicationRunningMonitor.class); @Resource - private ContextRefresher contextRefresher; + private ContextRefresher contextRefresher; @Resource - private CanalAdapterService canalAdapterService; + private CanalAdapterService canalAdapterService; - private ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1); - - private static volatile long applicationLastModify = -1L; + private FileAlterationMonitor fileMonitor; @PostConstruct public void init() { - URL url = this.getClass().getClassLoader().getResource(""); - String path; - if (url != null) { - path = url.getPath(); - } else { - path = new File("").getAbsolutePath(); + File confDir = Util.getConfDirPath(); + try { + FileAlterationObserver observer = new FileAlterationObserver(confDir, + FileFilterUtils.and(FileFilterUtils.fileFileFilter(), + FileFilterUtils.prefixFileFilter("application"), + FileFilterUtils.suffixFileFilter("yml"))); + FileListener listener = new FileListener(); + observer.addListener(listener); + fileMonitor = new FileAlterationMonitor(3000, observer); + fileMonitor.start(); + + } catch (Exception e) { + logger.error(e.getMessage(), e); } - File file = null; - if (path != null) { - file = new File(path + ".." + File.separator + Constant.CONF_DIR + File.separator + "application.yml"); - if (!file.exists()) { - file = new File(path + "application.yml"); - } - } - if (file == null || !file.exists()) { - throw new RuntimeException("application.yml config file not found."); - } - File configFile = file; - scheduled.scheduleWithFixedDelay(() -> { - if (applicationLastModify == -1L) { - applicationLastModify = configFile.lastModified(); - } else { - if (configFile.lastModified() != applicationLastModify) { - applicationLastModify = configFile.lastModified(); - - try { - // 检查yml格式 - new Yaml().loadAs(new FileReader(configFile), Map.class); - - canalAdapterService.destroy(); - - // refresh context - contextRefresher.refresh(); - - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - // ignore - } - canalAdapterService.init(); - logger.info("## adapter application config reloaded."); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - } - } - }, 5, 5, TimeUnit.SECONDS); } @PreDestroy public void destroy() { - scheduled.shutdown(); + try { + fileMonitor.stop(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private class FileListener extends FileAlterationListenerAdaptor { + + @Override + public void onFileChange(File file) { + super.onFileChange(file); + try { + // 检查yml格式 + new Yaml().loadAs(new FileReader(file), Map.class); + + canalAdapterService.destroy(); + + // refresh context + contextRefresher.refresh(); + + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + // ignore + } + canalAdapterService.init(); + logger.info("## adapter application config reloaded."); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index f862bb0e..1d78aead 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -2,7 +2,7 @@ server: port: 8081 logging: level: -# org.springframework: WARN + org.springframework: WARN com.alibaba.otter.canal.client.adapter.hbase: DEBUG com.alibaba.otter.canal.client.adapter.es: DEBUG com.alibaba.otter.canal.client.adapter.rdb: DEBUG @@ -28,16 +28,16 @@ canal.conf: - instance: example groups: - outAdapters: - - name: logger -# - name: rdb -# key: oracle1 -# properties: -# jdbc.driverClassName: oracle.jdbc.OracleDriver -# jdbc.url: jdbc:oracle:thin:@localhost:49161:XE -# jdbc.username: mytest -# jdbc.password: m121212 -# threads: 5 -# commitSize: 3000 +# - name: logger + - name: rdb + key: oracle1 + properties: + jdbc.driverClassName: oracle.jdbc.OracleDriver + jdbc.url: jdbc:oracle:thin:@localhost:49161:XE + jdbc.username: mytest + jdbc.password: m121212 + threads: 5 + commitSize: 5000 # - name: rdb # key: postgres1 # properties: diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml index 120aa5e1..e467cfca 100644 --- a/client-adapter/rdb/pom.xml +++ b/client-adapter/rdb/pom.xml @@ -39,12 +39,12 @@ - - - - + + com.oracle + ojdbc6 + 11.2.0.4.0-atlassian-hosted - + diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 68f740a7..729b8959 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -3,6 +3,13 @@ package com.alibaba.otter.canal.client.adapter.rdb; import java.sql.Connection; import java.sql.SQLException; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; @@ -30,6 +37,8 @@ public class RdbAdapter implements OuterAdapter { private RdbSyncService rdbSyncService; + private int commitSize = 3000; + @Override public void init(OuterAdapterConfig configuration) { Map rdbMappingTmp = ConfigLoader.load(); @@ -69,19 +78,58 @@ public class RdbAdapter implements OuterAdapter { String threads = properties.get("threads"); String commitSize = properties.get("commitSize"); - rdbSyncService = new RdbSyncService(commitSize != null ? Integer.valueOf(commitSize) : null, + if (commitSize != null) { + this.commitSize = Integer.valueOf(commitSize); + } + rdbSyncService = new RdbSyncService(this.commitSize, threads != null ? Integer.valueOf(threads) : null, dataSource); } + private AtomicInteger batchRowNum = new AtomicInteger(0); + private List dmlList = Collections.synchronizedList(new ArrayList<>()); + private Lock syncLock = new ReentrantLock(); + private Condition condition = syncLock.newCondition(); + private ExecutorService executor = Executors.newFixedThreadPool(1); + @Override public void sync(Dml dml) { - String destination = StringUtils.trimToEmpty(dml.getDestination()); - String database = dml.getDatabase(); - String table = dml.getTable(); - MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); + boolean first = batchRowNum.get() == 0; + int currentSize = batchRowNum.addAndGet(dml.getData().size()); + dmlList.add(dml); - rdbSyncService.sync(config, dml); + if (first) { + // 开启超时判断 + executor.submit(() -> { + try { + syncLock.lock(); + if (!condition.await(5, TimeUnit.SECONDS)) { + // 批量超时 + sync(); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } finally { + syncLock.unlock(); + } + }); + } + + if (currentSize > commitSize) { + sync(); + } + } + + private void sync() { + try { + syncLock.lock(); + rdbSyncService.sync(mappingConfigCache, dmlList); + batchRowNum.set(0); + dmlList.clear(); + condition.signal(); + } finally { + syncLock.unlock(); + } } @Override @@ -178,9 +226,12 @@ public class RdbAdapter implements OuterAdapter { @Override public void destroy() { + executor.shutdown(); + if (rdbSyncService != null) { rdbSyncService.close(); } + if (dataSource != null) { dataSource.close(); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java index ef0ec222..5e8c65e8 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java @@ -71,32 +71,32 @@ public class BatchExecutor { } catch (SQLException e) { logger.error(e.getMessage(), e); } - int i = idx.incrementAndGet(); - - // 批次的第一次执行设置延时 - if (i == 1) { - executor.submit(() -> { - try { - commitLock.lock(); - conn.commit(); //直接提交一次 - if (!condition.await(5, TimeUnit.SECONDS)) { - // 超时提交 - commit(); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - commitLock.unlock(); - } - }); - } - - if (i == commitSize) { - commit(); - } + // int i = idx.incrementAndGet(); + // + // // 批次的第一次执行设置延时 + // if (i == 1) { + // executor.submit(() -> { + // try { + // commitLock.lock(); + // conn.commit(); //直接提交一次 + // if (!condition.await(5, TimeUnit.SECONDS)) { + // // 超时提交 + // commit(); + // } + // } catch (Exception e) { + // logger.error(e.getMessage(), e); + // } finally { + // commitLock.unlock(); + // } + // }); + // } + // + // if (i == commitSize) { + // commit(); + // } } - private void commit() { + public void commit() { try { commitLock.lock(); conn.commit(); diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 0eff385b..04bf79da 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -5,17 +5,13 @@ import java.io.StringReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.sql.*; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ThreadPoolExecutor; +import java.sql.Date; +import java.util.*; +import java.util.concurrent.*; import javax.sql.DataSource; +import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,7 +62,81 @@ public class RdbSyncService { } } - public void sync(MappingConfig config, List dmlList) { + @SuppressWarnings("unchecked") + public void sync(Map mappingConfigCache, List dmlList) { + try { + List>[] dmlPartition = new ArrayList[threads]; + for (int i = 0; i < threads; i++) { + dmlPartition[i] = new ArrayList<>(); + } + // 根据hash拆分 + dmlList.forEach(dml -> { + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + String table = dml.getTable(); + MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); + + Dml[] dmls = new Dml[threads]; + for (int i = 0; i < threads; i++) { + Dml dmlTmp = new Dml(); + dmlTmp.setDestination(dml.getDestination()); + dmlTmp.setDatabase(dml.getDatabase()); + dmlTmp.setTable(dml.getTable()); + dmlTmp.setType(dml.getType()); + dmlTmp.setTs(dml.getTs()); + dmlTmp.setEs(dml.getEs()); + dmlTmp.setSql(dml.getSql()); + dmlTmp.setData(new ArrayList<>()); + dmlTmp.setOld(new ArrayList<>()); + dmls[i] = dmlTmp; + } + int idx = 0; + for (Map data : dml.getData()) { + int hash; + if (config.getConcurrent()) { + hash = pkHash(config.getDbMapping(), data, threads); + } else { + hash = Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % threads); + } + Dml dmlTmp = dmls[hash]; + dmlTmp.getData().add(data); + if (dml.getOld() != null) { + dmlTmp.getOld().add(dml.getOld().get(idx)); + } + + idx++; + } + for (int i = 0; i < threads; i++) { + Map item = new HashMap<>(); + item.put("dml", dmls[i]); + item.put("config", config); + dmlPartition[i].add(item); + } + + }); + List> futures = new ArrayList<>(); + for (int i = 0; i < threads; i++) { + int j = i; + futures.add(threadExecutors[i].submit(() -> { + dmlPartition[j].forEach(item -> { + MappingConfig config = (MappingConfig) item.get("config"); + Dml dml = (Dml) item.get("dml"); + sync(config, dml); + }); + batchExecutors[j].commit(); + return true; + })); + } + for (int i = 0; i < threads; i++) { + try { + futures.get(i).get(); + } catch (Exception e) { + // ignore + } + } + } catch (Exception e) { + logger.error("Error rdb sync for batch", e); + } } public void sync(MappingConfig config, Dml dml) { @@ -134,32 +204,32 @@ public class RdbSyncService { } else { hash = pkHash(dbMapping, d, threads); } - ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; - checkQueue(tpe); - tpe.submit(() -> { - try { - BatchExecutor batchExecutor = batchExecutors[hash]; - List> values = new ArrayList<>(); + // ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; + // checkQueue(tpe); + // tpe.submit(() -> { + try { + BatchExecutor batchExecutor = batchExecutors[hash]; + List> values = new ArrayList<>(); - for (Map.Entry entry : columnsMap.entrySet()) { - String targetColumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetColumnName; - } - - Integer type = ctype.get(targetColumnName.toLowerCase()); - if (type == null) { - throw new RuntimeException("No column: " + targetColumnName + " found in target db"); - } - Object value = d.get(srcColumnName); - BatchExecutor.setValue(values, type, value); + for (Map.Entry entry : columnsMap.entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetColumnName; } - batchExecutor.execute(sql, values); - } catch (Exception e) { - logger.error(e.getMessage(), e); + + Integer type = ctype.get(targetColumnName.toLowerCase()); + if (type == null) { + throw new RuntimeException("No column: " + targetColumnName + " found in target db"); + } + Object value = d.get(srcColumnName); + BatchExecutor.setValue(values, type, value); } - }); + batchExecutor.execute(sql, values); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + // }); } } @@ -202,46 +272,46 @@ public class RdbSyncService { hash = pkHash(dbMapping, d, o, threads); } - ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; - checkQueue(tpe); - tpe.submit(() -> { - try { - BatchExecutor batchExecutor = batchExecutors[hash]; + // ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; + // checkQueue(tpe); + // tpe.submit(() -> { + try { + BatchExecutor batchExecutor = batchExecutors[hash]; - StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); + StringBuilder updateSql = new StringBuilder(); + updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); - List> values = new ArrayList<>(); - for (String srcColumnName : o.keySet()) { - List targetColumnNames = new ArrayList<>(); - columnsMap.forEach((targetColumn, srcColumn) -> { - if (srcColumnName.toLowerCase().equals(srcColumn)) { - targetColumnNames.add(targetColumn); - } - }); - if (!targetColumnNames.isEmpty()) { - for (String targetColumnName : targetColumnNames) { - updateSql.append(targetColumnName).append("=?, "); - Integer type = ctype.get(targetColumnName.toLowerCase()); - BatchExecutor.setValue(values, type, d.get(srcColumnName)); - } + List> values = new ArrayList<>(); + for (String srcColumnName : o.keySet()) { + List targetColumnNames = new ArrayList<>(); + columnsMap.forEach((targetColumn, srcColumn) -> { + if (srcColumnName.toLowerCase().equals(srcColumn)) { + targetColumnNames.add(targetColumn); + } + }); + if (!targetColumnNames.isEmpty()) { + for (String targetColumnName : targetColumnNames) { + updateSql.append(targetColumnName).append("=?, "); + Integer type = ctype.get(targetColumnName.toLowerCase()); + BatchExecutor.setValue(values, type, d.get(srcColumnName)); } } - int len = updateSql.length(); - updateSql.delete(len - 2, len).append(" WHERE "); - - // 拼接主键 - appendCondition(dbMapping, updateSql, ctype, values, d, o); - - batchExecutor.execute(updateSql.toString(), values); - - if (logger.isDebugEnabled()) { - logger.debug("Execute sql: {}", updateSql); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); } - }); + int len = updateSql.length(); + updateSql.delete(len - 2, len).append(" WHERE "); + + // 拼接主键 + appendCondition(dbMapping, updateSql, ctype, values, d, o); + + batchExecutor.execute(updateSql.toString(), values); + + if (logger.isTraceEnabled()) { + logger.trace("Execute sql: {}", updateSql); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + // }); idx++; } @@ -276,27 +346,27 @@ public class RdbSyncService { hash = pkHash(dbMapping, d, threads); } - ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; - checkQueue(tpe); - tpe.submit(() -> { - try { - BatchExecutor batchExecutor = batchExecutors[hash]; - StringBuilder sql = new StringBuilder(); - sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); + // ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; + // checkQueue(tpe); + // tpe.submit(() -> { + try { + BatchExecutor batchExecutor = batchExecutors[hash]; + StringBuilder sql = new StringBuilder(); + sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); - List> values = new ArrayList<>(); + List> values = new ArrayList<>(); - // 拼接主键 - appendCondition(dbMapping, sql, ctype, values, d); + // 拼接主键 + appendCondition(dbMapping, sql, ctype, values, d); - batchExecutor.execute(sql.toString(), values); - if (logger.isDebugEnabled()) { - logger.debug("Execute sql: {}", sql); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); + batchExecutor.execute(sql.toString(), values); + if (logger.isTraceEnabled()) { + logger.trace("Execute sql: {}", sql); } - }); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + // }); } } From 14c0e4535adf71e28245bdbff9977cc9df8a644c Mon Sep 17 00:00:00 2001 From: machey Date: Thu, 15 Nov 2018 23:59:16 +0800 Subject: [PATCH 332/360] =?UTF-8?q?=E5=B0=86oracle=20mssql=20pgsql=20?= =?UTF-8?q?=E7=9A=84jdbc=E6=89=93=E5=8C=85=E8=BF=9Blib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client-adapter/launcher/pom.xml | 17 +++++++++++++ client-adapter/pom.xml | 13 ++++++++++ client-adapter/rdb/pom.xml | 42 ++++++++++++++------------------- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml index df31b8c8..6df096e8 100644 --- a/client-adapter/launcher/pom.xml +++ b/client-adapter/launcher/pom.xml @@ -67,6 +67,23 @@ 1.1.1 + + + org.postgresql + postgresql + 42.1.4 + + + com.oracle + ojdbc6 + 11.2.0.3 + + + com.microsoft.sqlserver + mssql-jdbc + 7.0.0.jre8 + + com.alibaba.otter diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml index 3c1dcb55..2f12209c 100644 --- a/client-adapter/pom.xml +++ b/client-adapter/pom.xml @@ -25,6 +25,19 @@ rdb + + + central + http://maven.aliyun.com/nexus/content/groups/public/ + + true + + + false + + + + diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml index 120aa5e1..36881768 100644 --- a/client-adapter/rdb/pom.xml +++ b/client-adapter/rdb/pom.xml @@ -27,30 +27,24 @@ provided - - - - - - - - - - - - - - - - - - - - - - - - + + org.postgresql + postgresql + 42.1.4 + test + + + com.oracle + ojdbc6 + 11.2.0.3 + test + + + com.microsoft.sqlserver + mssql-jdbc + 7.0.0.jre8 + test + junit junit From bc056288d4454aca2616dd8f74c03e6c7bee9a74 Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 16 Nov 2018 13:12:50 +0800 Subject: [PATCH 333/360] =?UTF-8?q?rdb=20=E6=89=B9=E9=87=8F=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 20 +- .../canal/client/adapter/rdb/RdbAdapter.java | 79 ++-- .../adapter/rdb/service/BatchExecutor.java | 29 +- .../adapter/rdb/service/RdbSyncService.java | 365 ++++++------------ .../client/adapter/rdb/support/SimpleDml.java | 102 +++++ 5 files changed, 282 insertions(+), 313 deletions(-) create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 1d78aead..3df3f4be 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -28,16 +28,16 @@ canal.conf: - instance: example groups: - outAdapters: -# - name: logger - - name: rdb - key: oracle1 - properties: - jdbc.driverClassName: oracle.jdbc.OracleDriver - jdbc.url: jdbc:oracle:thin:@localhost:49161:XE - jdbc.username: mytest - jdbc.password: m121212 - threads: 5 - commitSize: 5000 + - name: logger +# - name: rdb +# key: oracle1 +# properties: +# jdbc.driverClassName: oracle.jdbc.OracleDriver +# jdbc.url: jdbc:oracle:thin:@localhost:49161:XE +# jdbc.username: mytest +# jdbc.password: m121212 +# threads: 5 +# commitSize: 5000 # - name: rdb # key: postgres1 # properties: diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 729b8959..4e7e2504 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -5,9 +5,6 @@ import java.sql.SQLException; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -18,11 +15,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; +import com.alibaba.otter.canal.client.adapter.rdb.support.SimpleDml; import com.alibaba.otter.canal.client.adapter.support.*; @SPI("rdb") @@ -30,8 +30,8 @@ public class RdbAdapter implements OuterAdapter { private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); - private Map rdbMapping = new HashMap<>(); // 文件名对应配置 - private Map mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 + private Map rdbMapping = new HashMap<>(); // 文件名对应配置 + private Map mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 private DruidDataSource dataSource; @@ -39,6 +39,12 @@ public class RdbAdapter implements OuterAdapter { private int commitSize = 3000; + private volatile boolean running = false; + + private List dmlList = Collections.synchronizedList(new ArrayList<>()); + private Lock syncLock = new ReentrantLock(); + private ExecutorService executor = Executors.newFixedThreadPool(1); + @Override public void init(OuterAdapterConfig configuration) { Map rdbMappingTmp = ConfigLoader.load(); @@ -81,52 +87,54 @@ public class RdbAdapter implements OuterAdapter { if (commitSize != null) { this.commitSize = Integer.valueOf(commitSize); } - rdbSyncService = new RdbSyncService(this.commitSize, - threads != null ? Integer.valueOf(threads) : null, - dataSource); - } + rdbSyncService = new RdbSyncService(threads != null ? Integer.valueOf(threads) : null, dataSource); - private AtomicInteger batchRowNum = new AtomicInteger(0); - private List dmlList = Collections.synchronizedList(new ArrayList<>()); - private Lock syncLock = new ReentrantLock(); - private Condition condition = syncLock.newCondition(); - private ExecutorService executor = Executors.newFixedThreadPool(1); + running = true; - @Override - public void sync(Dml dml) { - boolean first = batchRowNum.get() == 0; - int currentSize = batchRowNum.addAndGet(dml.getData().size()); - dmlList.add(dml); - - if (first) { - // 开启超时判断 - executor.submit(() -> { + executor.submit(() -> { + while (running) { try { - syncLock.lock(); - if (!condition.await(5, TimeUnit.SECONDS)) { - // 批量超时 + int size1 = dmlList.size(); + Thread.sleep(3000); + int size2 = dmlList.size(); + if (size1 == size2) { + // 超时提交 sync(); } } catch (Exception e) { logger.error(e.getMessage(), e); - } finally { - syncLock.unlock(); } - }); + } + }); + } + + @Override + public void sync(Dml dml) { + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + String table = dml.getTable(); + MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); + + List simpleDmlList = SimpleDml.dml2SimpleDml(dml, config); + + dmlList.addAll(simpleDmlList); + + if (dmlList.size() > commitSize) { + sync(); } - if (currentSize > commitSize) { - sync(); + if (logger.isDebugEnabled()) { + logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } } private void sync() { try { syncLock.lock(); - rdbSyncService.sync(mappingConfigCache, dmlList); - batchRowNum.set(0); - dmlList.clear(); - condition.signal(); + if (!dmlList.isEmpty()) { + rdbSyncService.sync(dmlList); + dmlList.clear(); + } } finally { syncLock.unlock(); } @@ -226,6 +234,7 @@ public class RdbAdapter implements OuterAdapter { @Override public void destroy() { + running = false; executor.shutdown(); if (rdbSyncService != null) { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java index 5e8c65e8..3e3d442c 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java @@ -23,18 +23,14 @@ public class BatchExecutor { private Integer key; private Connection conn; - private int commitSize = 3000; private AtomicInteger idx = new AtomicInteger(0); private ExecutorService executor = Executors.newFixedThreadPool(1); private Lock commitLock = new ReentrantLock(); private Condition condition = commitLock.newCondition(); - public BatchExecutor(Integer key, Connection conn, Integer commitSize){ + public BatchExecutor(Integer key, Connection conn){ this.key = key; this.conn = conn; - if (commitSize != null) { - this.commitSize = commitSize; - } try { this.conn.setAutoCommit(false); @@ -71,29 +67,6 @@ public class BatchExecutor { } catch (SQLException e) { logger.error(e.getMessage(), e); } - // int i = idx.incrementAndGet(); - // - // // 批次的第一次执行设置延时 - // if (i == 1) { - // executor.submit(() -> { - // try { - // commitLock.lock(); - // conn.commit(); //直接提交一次 - // if (!condition.await(5, TimeUnit.SECONDS)) { - // // 超时提交 - // commit(); - // } - // } catch (Exception e) { - // logger.error(e.getMessage(), e); - // } finally { - // commitLock.unlock(); - // } - // }); - // } - // - // if (i == commitSize) { - // commit(); - // } } public void commit() { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 04bf79da..9467b8b9 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -5,23 +5,22 @@ import java.io.StringReader; import java.math.BigDecimal; import java.nio.charset.StandardCharsets; import java.sql.*; -import java.sql.Date; -import java.util.*; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.*; import javax.sql.DataSource; -import org.apache.commons.lang.StringUtils; import org.joda.time.DateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; +import com.alibaba.otter.canal.client.adapter.rdb.support.SimpleDml; import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; -import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Util; /** @@ -42,7 +41,7 @@ public class RdbSyncService { private ExecutorService[] threadExecutors; - public RdbSyncService(Integer commitSize, Integer threads, DataSource dataSource){ + public RdbSyncService(Integer threads, DataSource dataSource){ try { if (threads != null && threads > 1 && threads <= 10) { this.threads = threads; @@ -51,7 +50,7 @@ public class RdbSyncService { for (int i = 0; i < this.threads; i++) { Connection conn = dataSource.getConnection(); conn.setAutoCommit(false); - this.batchExecutors[i] = new BatchExecutor(i, conn, commitSize); + this.batchExecutors[i] = new BatchExecutor(i, conn); } threadExecutors = new ExecutorService[this.threads]; for (int i = 0; i < this.threads; i++) { @@ -63,96 +62,60 @@ public class RdbSyncService { } @SuppressWarnings("unchecked") - public void sync(Map mappingConfigCache, List dmlList) { - try { - List>[] dmlPartition = new ArrayList[threads]; - for (int i = 0; i < threads; i++) { - dmlPartition[i] = new ArrayList<>(); + private List[] simpleDmls2Partition(List simpleDmlList) { + List[] simpleDmlPartition = new ArrayList[threads]; + for (int i = 0; i < threads; i++) { + simpleDmlPartition[i] = new ArrayList<>(); + } + simpleDmlList.forEach(simpleDml -> { + int hash; + if (simpleDml.getConfig().getConcurrent()) { + hash = pkHash(simpleDml.getConfig().getDbMapping(), simpleDml.getData(), threads); + } else { + hash = Math.abs(Math.abs(simpleDml.getConfig().getDbMapping().getTargetTable().hashCode()) % threads); } - // 根据hash拆分 - dmlList.forEach(dml -> { - String destination = StringUtils.trimToEmpty(dml.getDestination()); - String database = dml.getDatabase(); - String table = dml.getTable(); - MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); + simpleDmlPartition[hash].add(simpleDml); + }); + return simpleDmlPartition; + } - Dml[] dmls = new Dml[threads]; - for (int i = 0; i < threads; i++) { - Dml dmlTmp = new Dml(); - dmlTmp.setDestination(dml.getDestination()); - dmlTmp.setDatabase(dml.getDatabase()); - dmlTmp.setTable(dml.getTable()); - dmlTmp.setType(dml.getType()); - dmlTmp.setTs(dml.getTs()); - dmlTmp.setEs(dml.getEs()); - dmlTmp.setSql(dml.getSql()); - dmlTmp.setData(new ArrayList<>()); - dmlTmp.setOld(new ArrayList<>()); - dmls[i] = dmlTmp; - } - int idx = 0; - for (Map data : dml.getData()) { - int hash; - if (config.getConcurrent()) { - hash = pkHash(config.getDbMapping(), data, threads); - } else { - hash = Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % threads); - } - Dml dmlTmp = dmls[hash]; - dmlTmp.getData().add(data); - if (dml.getOld() != null) { - dmlTmp.getOld().add(dml.getOld().get(idx)); - } + public void sync(List simpleDmlList) { + try { + List[] simpleDmlsPartition = simpleDmls2Partition(simpleDmlList); - idx++; - } - for (int i = 0; i < threads; i++) { - Map item = new HashMap<>(); - item.put("dml", dmls[i]); - item.put("config", config); - dmlPartition[i].add(item); - } - - }); List> futures = new ArrayList<>(); for (int i = 0; i < threads; i++) { - int j = i; - futures.add(threadExecutors[i].submit(() -> { - dmlPartition[j].forEach(item -> { - MappingConfig config = (MappingConfig) item.get("config"); - Dml dml = (Dml) item.get("dml"); - sync(config, dml); - }); - batchExecutors[j].commit(); - return true; - })); + if (!simpleDmlsPartition[i].isEmpty()) { + int j = i; + futures.add(threadExecutors[i].submit(() -> { + simpleDmlsPartition[j].forEach(simpleDml -> sync(simpleDml, batchExecutors[j])); + batchExecutors[j].commit(); + return true; + })); + } } - for (int i = 0; i < threads; i++) { + + futures.forEach(future -> { try { - futures.get(i).get(); + future.get(); } catch (Exception e) { // ignore } - } + }); } catch (Exception e) { logger.error("Error rdb sync for batch", e); } } - public void sync(MappingConfig config, Dml dml) { + public void sync(SimpleDml dml, BatchExecutor batchExecutor) { try { - if (config != null) { - String type = dml.getType(); - if (type != null && type.equalsIgnoreCase("INSERT")) { - insert(config, dml); - } else if (type != null && type.equalsIgnoreCase("UPDATE")) { - update(config, dml); - } else if (type != null && type.equalsIgnoreCase("DELETE")) { - delete(config, dml); - } - if (logger.isDebugEnabled()) { - logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); - } + String type = dml.getType(); + if (type != null && type.equalsIgnoreCase("INSERT")) { + insert(dml, batchExecutor); + } else if (type != null && type.equalsIgnoreCase("UPDATE")) { + update(dml, batchExecutor); + } else if (type != null && type.equalsIgnoreCase("DELETE")) { + delete(dml, batchExecutor); } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -162,18 +125,17 @@ public class RdbSyncService { /** * 插入操作 * - * @param config 配置项 - * @param dml DML数据 + * @param simpleDml DML数据 */ - private void insert(MappingConfig config, Dml dml) { - List> data = dml.getData(); + private void insert(SimpleDml simpleDml, BatchExecutor batchExecutor) { + Map data = simpleDml.getData(); if (data == null || data.isEmpty()) { return; } - DbMapping dbMapping = config.getDbMapping(); + DbMapping dbMapping = simpleDml.getConfig().getDbMapping(); - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); StringBuilder insertSql = new StringBuilder(); insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); @@ -188,185 +150,121 @@ public class RdbSyncService { len = insertSql.length(); insertSql.delete(len - 1, len).append(")"); - Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); + Map ctype = getTargetColumnType(batchExecutor.getConn(), simpleDml.getConfig()); String sql = insertSql.toString(); - Integer tbHash = null; - // 如果不是并行同步的表 - if (!config.getConcurrent()) { - // 按表名hash到一个线程 - tbHash = Math.abs(Math.abs(dbMapping.getTargetTable().hashCode()) % threads); - } - for (Map d : data) { - int hash; - if (tbHash != null) { - hash = tbHash; - } else { - hash = pkHash(dbMapping, d, threads); - } - // ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; - // checkQueue(tpe); - // tpe.submit(() -> { - try { - BatchExecutor batchExecutor = batchExecutors[hash]; - List> values = new ArrayList<>(); - for (Map.Entry entry : columnsMap.entrySet()) { - String targetColumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetColumnName; - } + try { + List> values = new ArrayList<>(); - Integer type = ctype.get(targetColumnName.toLowerCase()); - if (type == null) { - throw new RuntimeException("No column: " + targetColumnName + " found in target db"); - } - Object value = d.get(srcColumnName); - BatchExecutor.setValue(values, type, value); + for (Map.Entry entry : columnsMap.entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetColumnName; } - batchExecutor.execute(sql, values); - } catch (Exception e) { - logger.error(e.getMessage(), e); + + Integer type = ctype.get(targetColumnName.toLowerCase()); + if (type == null) { + throw new RuntimeException("No column: " + targetColumnName + " found in target db"); + } + Object value = data.get(srcColumnName); + BatchExecutor.setValue(values, type, value); } - // }); + batchExecutor.execute(sql, values); + } catch (Exception e) { + logger.error(e.getMessage(), e); } } /** * 更新操作 * - * @param config 配置项 - * @param dml DML数据 + * @param simpleDml DML数据 */ - private void update(MappingConfig config, Dml dml) { - List> data = dml.getData(); + private void update(SimpleDml simpleDml, BatchExecutor batchExecutor) { + Map data = simpleDml.getData(); if (data == null || data.isEmpty()) { return; } - List> old = dml.getOld(); + Map old = simpleDml.getOld(); if (old == null || old.isEmpty()) { return; } - DbMapping dbMapping = config.getDbMapping(); + DbMapping dbMapping = simpleDml.getConfig().getDbMapping(); - int idx = 1; - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); - Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); + Map ctype = getTargetColumnType(batchExecutor.getConn(), simpleDml.getConfig()); - Integer tbHash = null; - if (!config.getConcurrent()) { - // 按表名hash到一个线程 - tbHash = Math.abs(Math.abs(dbMapping.getTargetTable().hashCode()) % threads); - } - for (Map o : old) { - Map d = data.get(idx - 1); + try { + StringBuilder updateSql = new StringBuilder(); + updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); - int hash; - if (tbHash != null) { - hash = tbHash; - } else { - hash = pkHash(dbMapping, d, o, threads); - } - - // ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; - // checkQueue(tpe); - // tpe.submit(() -> { - try { - BatchExecutor batchExecutor = batchExecutors[hash]; - - StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); - - List> values = new ArrayList<>(); - for (String srcColumnName : o.keySet()) { - List targetColumnNames = new ArrayList<>(); - columnsMap.forEach((targetColumn, srcColumn) -> { - if (srcColumnName.toLowerCase().equals(srcColumn)) { - targetColumnNames.add(targetColumn); - } - }); - if (!targetColumnNames.isEmpty()) { - for (String targetColumnName : targetColumnNames) { - updateSql.append(targetColumnName).append("=?, "); - Integer type = ctype.get(targetColumnName.toLowerCase()); - BatchExecutor.setValue(values, type, d.get(srcColumnName)); - } + List> values = new ArrayList<>(); + for (String srcColumnName : old.keySet()) { + List targetColumnNames = new ArrayList<>(); + columnsMap.forEach((targetColumn, srcColumn) -> { + if (srcColumnName.toLowerCase().equals(srcColumn)) { + targetColumnNames.add(targetColumn); + } + }); + if (!targetColumnNames.isEmpty()) { + for (String targetColumnName : targetColumnNames) { + updateSql.append(targetColumnName).append("=?, "); + Integer type = ctype.get(targetColumnName.toLowerCase()); + BatchExecutor.setValue(values, type, data.get(srcColumnName)); } } - int len = updateSql.length(); - updateSql.delete(len - 2, len).append(" WHERE "); - - // 拼接主键 - appendCondition(dbMapping, updateSql, ctype, values, d, o); - - batchExecutor.execute(updateSql.toString(), values); - - if (logger.isTraceEnabled()) { - logger.trace("Execute sql: {}", updateSql); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); } - // }); + int len = updateSql.length(); + updateSql.delete(len - 2, len).append(" WHERE "); - idx++; + // 拼接主键 + appendCondition(dbMapping, updateSql, ctype, values, data, old); + + batchExecutor.execute(updateSql.toString(), values); + + if (logger.isTraceEnabled()) { + logger.trace("Execute sql: {}", updateSql); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); } } /** * 删除操作 * - * @param config - * @param dml - * @throws SQLException + * @param simpleDml */ - private void delete(MappingConfig config, Dml dml) { - List> data = dml.getData(); + private void delete(SimpleDml simpleDml, BatchExecutor batchExecutor) { + Map data = simpleDml.getData(); if (data == null || data.isEmpty()) { return; } - DbMapping dbMapping = config.getDbMapping(); + DbMapping dbMapping = simpleDml.getConfig().getDbMapping(); - Map ctype = getTargetColumnType(batchExecutors[0].getConn(), config); - Integer tbHash = null; - if (!config.getConcurrent()) { - // 按表名hash到一个线程 - tbHash = Math.abs(Math.abs(dbMapping.getTargetTable().hashCode()) % threads); - } - for (Map d : data) { - int hash; - if (tbHash != null) { - hash = tbHash; - } else { - hash = pkHash(dbMapping, d, threads); + Map ctype = getTargetColumnType(batchExecutor.getConn(), simpleDml.getConfig()); + + try { + StringBuilder sql = new StringBuilder(); + sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); + + List> values = new ArrayList<>(); + + // 拼接主键 + appendCondition(dbMapping, sql, ctype, values, data); + + batchExecutor.execute(sql.toString(), values); + if (logger.isTraceEnabled()) { + logger.trace("Execute sql: {}", sql); } - - // ThreadPoolExecutor tpe = (ThreadPoolExecutor) threadExecutors[hash]; - // checkQueue(tpe); - // tpe.submit(() -> { - try { - BatchExecutor batchExecutor = batchExecutors[hash]; - StringBuilder sql = new StringBuilder(); - sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); - - List> values = new ArrayList<>(); - - // 拼接主键 - appendCondition(dbMapping, sql, ctype, values, d); - - batchExecutor.execute(sql.toString(), values); - if (logger.isTraceEnabled()) { - logger.trace("Execute sql: {}", sql); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } - // }); + } catch (Exception e) { + logger.error(e.getMessage(), e); } } @@ -416,7 +314,8 @@ public class RdbSyncService { */ public static void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException { if (value == null) { - pstmt.setObject(i, type); + pstmt.setNull(i, type); + return; } switch (type) { case Types.BIT: @@ -638,18 +537,4 @@ public class RdbSyncService { executorService.shutdown(); } } - - private void checkQueue(ThreadPoolExecutor tpe) { - // 防止队列过大 - while (tpe.getQueue().size() > 10000) { - try { - Thread.sleep(3000); - while (tpe.getQueue().size() > 5000) { - Thread.sleep(1000); - } - } catch (InterruptedException e) { - // ignore - } - } - } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java new file mode 100644 index 00000000..cbe45190 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java @@ -0,0 +1,102 @@ +package com.alibaba.otter.canal.client.adapter.rdb.support; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import org.apache.commons.lang.StringUtils; + +public class SimpleDml { + + private String destination; + private String database; + private String table; + private String type; + private Map data; + private Map old; + + private MappingConfig config; + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + public Map getOld() { + return old; + } + + public void setOld(Map old) { + this.old = old; + } + + public MappingConfig getConfig() { + return config; + } + + public void setConfig(MappingConfig config) { + this.config = config; + } + + public static List dml2SimpleDml(Dml dml, MappingConfig config) { + List simpleDmlList = new ArrayList<>(); + int len = dml.getData().size(); + + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + String table = dml.getTable(); + + for (int i = 0; i < len; i++) { + SimpleDml simpleDml = new SimpleDml(); + simpleDml.setDestination(dml.getDestination()); + simpleDml.setDatabase(dml.getDatabase()); + simpleDml.setTable(dml.getTable()); + simpleDml.setType(dml.getType()); + simpleDml.setData(dml.getData().get(i)); + if (dml.getOld() != null) { + simpleDml.setOld(dml.getOld().get(i)); + } + simpleDml.setConfig(config); + simpleDmlList.add(simpleDml); + } + + return simpleDmlList; + } +} From 9dc4d78b3b12ae24bec976d239f32bb81f89acfe Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 16 Nov 2018 13:24:36 +0800 Subject: [PATCH 334/360] fix --- .../otter/canal/client/adapter/rdb/support/SimpleDml.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java index cbe45190..d063843a 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java @@ -6,7 +6,6 @@ import java.util.Map; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; -import org.apache.commons.lang.StringUtils; public class SimpleDml { @@ -79,10 +78,6 @@ public class SimpleDml { List simpleDmlList = new ArrayList<>(); int len = dml.getData().size(); - String destination = StringUtils.trimToEmpty(dml.getDestination()); - String database = dml.getDatabase(); - String table = dml.getTable(); - for (int i = 0; i < len; i++) { SimpleDml simpleDml = new SimpleDml(); simpleDml.setDestination(dml.getDestination()); From d34bee33595ef975f0831fda89ac0643e89183fb Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 16 Nov 2018 18:07:32 +0800 Subject: [PATCH 335/360] =?UTF-8?q?adapter=20=E9=85=8D=E7=BD=AE=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=8A=A8=E6=80=81=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/MappingConfigsLoader.java | 25 +++ .../canal/client/adapter/es/ESAdapter.java | 40 +++-- .../adapter/es/monitor/ESConfigMonitor.java | 151 ++++++++++++++++++ .../adapter/es/service/ESSyncService.java | 3 +- .../es/test/sync/LabelSyncJoinSub2Test.java | 12 +- .../es/test/sync/LabelSyncJoinSubTest.java | 12 +- .../es/test/sync/RoleSyncJoinOne2Test.java | 8 +- .../es/test/sync/RoleSyncJoinOneTest.java | 18 +-- .../es/test/sync/UserSyncJoinOneTest.java | 10 +- .../es/test/sync/UserSyncSingleTest.java | 12 +- .../client/adapter/hbase/HbaseAdapter.java | 46 ++++-- .../hbase/monitor/HbaseConfigMonitor.java | 129 +++++++++++++++ ...tor.java => ApplicationConfigMonitor.java} | 4 +- .../canal/client/adapter/rdb/RdbAdapter.java | 58 ++++--- .../adapter/rdb/monitor/RdbConfigMonitor.java | 141 ++++++++++++++++ 15 files changed, 581 insertions(+), 88 deletions(-) create mode 100644 client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/monitor/ESConfigMonitor.java create mode 100644 client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/monitor/HbaseConfigMonitor.java rename client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/{ApplicationRunningMonitor.java => ApplicationConfigMonitor.java} (97%) create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java index c437731b..a7c8d7d6 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MappingConfigsLoader.java @@ -43,4 +43,29 @@ public class MappingConfigsLoader { return configContentMap; } + + public static String loadConfig(String name) { + // 先取本地文件,再取类路径 + File filePath = new File(".." + File.separator + Constant.CONF_DIR + File.separator + name); + if (!filePath.exists()) { + URL url = MappingConfigsLoader.class.getClassLoader().getResource(""); + if (url != null) { + filePath = new File(url.getPath() + name); + } + } + if (filePath.exists()) { + String fileName = filePath.getName(); + if (!fileName.endsWith(".yml")) { + return null; + } + try (InputStream in = new FileInputStream(filePath)) { + byte[] bytes = new byte[in.available()]; + in.read(bytes); + return new String(bytes, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException("Read mapping config: " + filePath.getAbsolutePath() + " error. ", e); + } + } + return null; + } } diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java index 24cc9029..18cc0c93 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java @@ -1,15 +1,14 @@ package com.alibaba.otter.canal.client.adapter.es; import java.net.InetAddress; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.sql.DataSource; +import com.alibaba.otter.canal.client.adapter.es.monitor.ESConfigMonitor; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; @@ -37,12 +36,14 @@ import com.alibaba.otter.canal.client.adapter.support.*; @SPI("es") public class ESAdapter implements OuterAdapter { - private Map esSyncConfig = new LinkedHashMap<>(); // 文件名对应配置 - private Map> dbTableEsSyncConfig = new LinkedHashMap<>(); // schema-table对应配置 + private Map esSyncConfig = new ConcurrentHashMap<>(); // 文件名对应配置 + private Map> dbTableEsSyncConfig = new ConcurrentHashMap<>(); // schema-table对应配置 - private TransportClient transportClient; + private TransportClient transportClient; - private ESSyncService esSyncService; + private ESSyncService esSyncService; + + private ESConfigMonitor esConfigMonitor; public TransportClient getTransportClient() { return transportClient; @@ -56,7 +57,7 @@ public class ESAdapter implements OuterAdapter { return esSyncConfig; } - public Map> getDbTableEsSyncConfig() { + public Map> getDbTableEsSyncConfig() { return dbTableEsSyncConfig; } @@ -73,7 +74,9 @@ public class ESAdapter implements OuterAdapter { } }); - for (ESSyncConfig config : esSyncConfig.values()) { + for (Map.Entry entry : esSyncConfig.entrySet()) { + String configName = entry.getKey(); + ESSyncConfig config = entry.getValue(); SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql()); config.getEsMapping().setSchemaItem(schemaItem); @@ -89,9 +92,9 @@ public class ESAdapter implements OuterAdapter { String schema = matcher.group(2); schemaItem.getAliasTableItems().values().forEach(tableItem -> { - List esSyncConfigs = dbTableEsSyncConfig - .computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new ArrayList<>()); - esSyncConfigs.add(config); + Map esSyncConfigMap = dbTableEsSyncConfig + .computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new HashMap<>()); + esSyncConfigMap.put(configName, config); }); } @@ -108,6 +111,9 @@ public class ESAdapter implements OuterAdapter { } ESTemplate esTemplate = new ESTemplate(transportClient); esSyncService = new ESSyncService(esTemplate); + + esConfigMonitor = new ESConfigMonitor(); + esConfigMonitor.init(this); } catch (Exception e) { throw new RuntimeException(e); } @@ -117,8 +123,8 @@ public class ESAdapter implements OuterAdapter { public void sync(Dml dml) { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = dbTableEsSyncConfig.get(database + "-" + table); - esSyncService.sync(esSyncConfigs, dml); + Map configMap = dbTableEsSyncConfig.get(database + "-" + table); + esSyncService.sync(configMap.values(), dml); } @Override @@ -192,6 +198,10 @@ public class ESAdapter implements OuterAdapter { @Override public String getDestination(String task) { + if (esConfigMonitor != null) { + esConfigMonitor.destroy(); + } + ESSyncConfig config = esSyncConfig.get(task); if (config != null) { return config.getDestination(); diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/monitor/ESConfigMonitor.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/monitor/ESConfigMonitor.java new file mode 100644 index 00000000..3b19b39e --- /dev/null +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/monitor/ESConfigMonitor.java @@ -0,0 +1,151 @@ +package com.alibaba.otter.canal.client.adapter.es.monitor; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.otter.canal.client.adapter.es.ESAdapter; +import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; +import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem; +import com.alibaba.otter.canal.client.adapter.es.config.SqlParser; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; +import com.alibaba.otter.canal.client.adapter.support.Util; + +public class ESConfigMonitor { + + private static final Logger logger = LoggerFactory.getLogger(ESConfigMonitor.class); + + private static final String adapterName = "es"; + + private ESAdapter esAdapter; + + private FileAlterationMonitor fileMonitor; + + public void init(ESAdapter esAdapter) { + this.esAdapter = esAdapter; + File confDir = Util.getConfDirPath(adapterName); + try { + FileAlterationObserver observer = new FileAlterationObserver(confDir, + FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml"))); + FileListener listener = new FileListener(); + observer.addListener(listener); + fileMonitor = new FileAlterationMonitor(3000, observer); + fileMonitor.start(); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + public void destroy() { + try { + fileMonitor.stop(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private class FileListener extends FileAlterationListenerAdaptor { + + @Override + public void onFileCreate(File file) { + super.onFileCreate(file); + try { + // 加载新增的配置文件 + String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName()); + ESSyncConfig config = new Yaml().loadAs(configContent, ESSyncConfig.class); + config.validate(); + addConfigToCache(file, config); + + logger.info("Add a new es mapping config: {} to canal adapter", file.getName()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + @Override + public void onFileChange(File file) { + super.onFileChange(file); + + try { + if (esAdapter.getEsSyncConfig().containsKey(file.getName())) { + // 加载配置文件 + String configContent = MappingConfigsLoader + .loadConfig(adapterName + File.separator + file.getName()); + ESSyncConfig config = new Yaml().loadAs(configContent, ESSyncConfig.class); + config.validate(); + if (esAdapter.getEsSyncConfig().containsKey(file.getName())) { + deleteConfigFromCache(file); + } + addConfigToCache(file, config); + + logger.info("Change a es mapping config: {} of canal adapter", file.getName()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + @Override + public void onFileDelete(File file) { + super.onFileDelete(file); + + try { + if (esAdapter.getEsSyncConfig().containsKey(file.getName())) { + deleteConfigFromCache(file); + + logger.info("Delete a es mapping config: {} of canal adapter", file.getName()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void addConfigToCache(File file, ESSyncConfig config) { + esAdapter.getEsSyncConfig().put(file.getName(), config); + + SchemaItem schemaItem = SqlParser.parse(config.getEsMapping().getSql()); + config.getEsMapping().setSchemaItem(schemaItem); + + DruidDataSource dataSource = DatasourceConfig.DATA_SOURCES.get(config.getDataSourceKey()); + if (dataSource == null || dataSource.getUrl() == null) { + throw new RuntimeException("No data source found: " + config.getDataSourceKey()); + } + Pattern pattern = Pattern.compile(".*:(.*)://.*/(.*)\\?.*$"); + Matcher matcher = pattern.matcher(dataSource.getUrl()); + if (!matcher.find()) { + throw new RuntimeException("Not found the schema of jdbc-url: " + config.getDataSourceKey()); + } + String schema = matcher.group(2); + + schemaItem.getAliasTableItems().values().forEach(tableItem -> { + Map esSyncConfigMap = esAdapter.getDbTableEsSyncConfig() + .computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new HashMap<>()); + esSyncConfigMap.put(file.getName(), config); + }); + + } + + private void deleteConfigFromCache(File file) { + esAdapter.getEsSyncConfig().remove(file.getName()); + for (Map configMap : esAdapter.getDbTableEsSyncConfig().values()) { + if (configMap != null) { + configMap.remove(file.getName()); + } + } + + } + } +} diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java index 73b1a6bc..397e3e5a 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java @@ -1,5 +1,6 @@ package com.alibaba.otter.canal.client.adapter.es.service; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -39,7 +40,7 @@ public class ESSyncService { this.esTemplate = esTemplate; } - public void sync(List esSyncConfigs, Dml dml) { + public void sync(Collection esSyncConfigs, Dml dml) { long begin = System.currentTimeMillis(); if (esSyncConfigs != null) { if (logger.isTraceEnabled()) { diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java index 2d59f565..966b115b 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSub2Test.java @@ -50,9 +50,9 @@ public class LabelSyncJoinSub2Test { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;a_", response.getSource().get("_labels")); @@ -88,9 +88,9 @@ public class LabelSyncJoinSub2Test { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;aa_", response.getSource().get("_labels")); @@ -120,9 +120,9 @@ public class LabelSyncJoinSub2Test { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b_", response.getSource().get("_labels")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java index eafcfe75..e8cbbe59 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/LabelSyncJoinSubTest.java @@ -50,9 +50,9 @@ public class LabelSyncJoinSubTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;a", response.getSource().get("_labels")); @@ -88,9 +88,9 @@ public class LabelSyncJoinSubTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b;aa", response.getSource().get("_labels")); @@ -120,9 +120,9 @@ public class LabelSyncJoinSubTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("b", response.getSource().get("_labels")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java index c87669ba..0fba73d3 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOne2Test.java @@ -48,9 +48,9 @@ public class RoleSyncJoinOne2Test { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin_", response.getSource().get("_role_name")); @@ -85,9 +85,9 @@ public class RoleSyncJoinOne2Test { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin3_", response.getSource().get("_role_name")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java index a7a61d5a..9739a355 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/RoleSyncJoinOneTest.java @@ -49,9 +49,9 @@ public class RoleSyncJoinOneTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin", response.getSource().get("_role_name")); @@ -86,9 +86,9 @@ public class RoleSyncJoinOneTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin2", response.getSource().get("_role_name")); @@ -124,9 +124,9 @@ public class RoleSyncJoinOneTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("operator", response.getSource().get("_role_name")); @@ -151,7 +151,7 @@ public class RoleSyncJoinOneTest { old2.put("role_id", 2L); dml2.setOld(oldList2); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml2); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml2); GetResponse response2 = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("admin2", response2.getSource().get("_role_name")); @@ -181,9 +181,9 @@ public class RoleSyncJoinOneTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertNull(response.getSource().get("_role_name")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java index 532cbc89..c0e43b61 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncJoinOneTest.java @@ -20,7 +20,7 @@ public class UserSyncJoinOneTest { @Before public void init() { -// AdapterConfigs.put("es", "mytest_user_join_one.yml"); + // AdapterConfigs.put("es", "mytest_user_join_one.yml"); esAdapter = Common.init(); } @@ -50,9 +50,9 @@ public class UserSyncJoinOneTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric_", response.getSource().get("_name")); @@ -86,9 +86,9 @@ public class UserSyncJoinOneTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric2_", response.getSource().get("_name")); diff --git a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java index bcc3b7f6..6bf4b75b 100644 --- a/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java +++ b/client-adapter/elasticsearch/src/test/java/com/alibaba/otter/canal/client/adapter/es/test/sync/UserSyncSingleTest.java @@ -43,9 +43,9 @@ public class UserSyncSingleTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric", response.getSource().get("_name")); @@ -76,9 +76,9 @@ public class UserSyncSingleTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertEquals("Eric2", response.getSource().get("_name")); @@ -106,9 +106,9 @@ public class UserSyncSingleTest { String database = dml.getDatabase(); String table = dml.getTable(); - List esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); + Map esSyncConfigs = esAdapter.getDbTableEsSyncConfig().get(database + "-" + table); - esAdapter.getEsSyncService().sync(esSyncConfigs, dml); + esAdapter.getEsSyncService().sync(esSyncConfigs.values(), dml); GetResponse response = esAdapter.getTransportClient().prepareGet("mytest_user", "_doc", "1").get(); Assert.assertNull(response.getSource()); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index 9c0395f3..a5172e75 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfigLoader; +import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService; import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate; @@ -36,14 +37,24 @@ import com.alibaba.otter.canal.client.adapter.support.*; @SPI("hbase") public class HbaseAdapter implements OuterAdapter { - private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class); + private static Logger logger = LoggerFactory.getLogger(HbaseAdapter.class); - private Map hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置 - private Map mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置 + private Map hbaseMapping = new ConcurrentHashMap<>(); // 文件名对应配置 + private Map> mappingConfigCache = new ConcurrentHashMap<>(); // 库名-表名对应配置 - private Connection conn; - private HbaseSyncService hbaseSyncService; - private HbaseTemplate hbaseTemplate; + private Connection conn; + private HbaseSyncService hbaseSyncService; + private HbaseTemplate hbaseTemplate; + + private HbaseConfigMonitor configMonitor; + + public Map getHbaseMapping() { + return hbaseMapping; + } + + public Map> getMappingConfigCache() { + return mappingConfigCache; + } @Override public void init(OuterAdapterConfig configuration) { @@ -57,11 +68,14 @@ public class HbaseAdapter implements OuterAdapter { hbaseMapping.put(key, mappingConfig); } }); - for (MappingConfig mappingConfig : hbaseMapping.values()) { - mappingConfigCache.put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." - + mappingConfig.getHbaseMapping().getDatabase() + "." - + mappingConfig.getHbaseMapping().getTable(), - mappingConfig); + for (Map.Entry entry : hbaseMapping.entrySet()) { + String configName = entry.getKey(); + MappingConfig mappingConfig = entry.getValue(); + String k = StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getHbaseMapping().getDatabase() + "." + + mappingConfig.getHbaseMapping().getTable(); + Map configMap = mappingConfigCache.computeIfAbsent(k, k1 -> new HashMap<>()); + configMap.put(configName, mappingConfig); } Map properties = configuration.getProperties(); @@ -71,6 +85,9 @@ public class HbaseAdapter implements OuterAdapter { conn = ConnectionFactory.createConnection(hbaseConfig); hbaseTemplate = new HbaseTemplate(conn); hbaseSyncService = new HbaseSyncService(hbaseTemplate); + + configMonitor = new HbaseConfigMonitor(); + configMonitor.init(this); } catch (Exception e) { throw new RuntimeException(e); } @@ -84,8 +101,8 @@ public class HbaseAdapter implements OuterAdapter { String destination = StringUtils.trimToEmpty(dml.getDestination()); String database = dml.getDatabase(); String table = dml.getTable(); - MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); - hbaseSyncService.sync(config, dml); + Map configMap = mappingConfigCache.get(destination + "." + database + "." + table); + configMap.values().forEach(config -> hbaseSyncService.sync(config, dml)); } @Override @@ -160,6 +177,9 @@ public class HbaseAdapter implements OuterAdapter { @Override public void destroy() { + if (configMonitor != null) { + configMonitor.destroy(); + } if (conn != null) { try { conn.close(); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/monitor/HbaseConfigMonitor.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/monitor/HbaseConfigMonitor.java new file mode 100644 index 00000000..ae6ddef2 --- /dev/null +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/monitor/HbaseConfigMonitor.java @@ -0,0 +1,129 @@ +package com.alibaba.otter.canal.client.adapter.hbase.monitor; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import com.alibaba.otter.canal.client.adapter.hbase.HbaseAdapter; +import com.alibaba.otter.canal.client.adapter.hbase.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; +import com.alibaba.otter.canal.client.adapter.support.Util; + +public class HbaseConfigMonitor { + + private static final Logger logger = LoggerFactory.getLogger(HbaseConfigMonitor.class); + + private static final String adapterName = "hbase"; + + private HbaseAdapter hbaseAdapter; + + private FileAlterationMonitor fileMonitor; + + public void init(HbaseAdapter hbaseAdapter) { + this.hbaseAdapter = hbaseAdapter; + File confDir = Util.getConfDirPath(adapterName); + try { + FileAlterationObserver observer = new FileAlterationObserver(confDir, + FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml"))); + FileListener listener = new FileListener(); + observer.addListener(listener); + fileMonitor = new FileAlterationMonitor(3000, observer); + fileMonitor.start(); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + public void destroy() { + try { + fileMonitor.stop(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private class FileListener extends FileAlterationListenerAdaptor { + + @Override + public void onFileCreate(File file) { + super.onFileCreate(file); + try { + // 加载新增的配置文件 + String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName()); + MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); + config.validate(); + addConfigToCache(file, config); + + logger.info("Add a new hbase mapping config: {} to canal adapter", file.getName()); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + @Override + public void onFileChange(File file) { + super.onFileChange(file); + + try { + if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) { + // 加载配置文件 + String configContent = MappingConfigsLoader + .loadConfig(adapterName + File.separator + file.getName()); + MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); + config.validate(); + if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) { + deleteConfigFromCache(file); + } + addConfigToCache(file, config); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + @Override + public void onFileDelete(File file) { + super.onFileDelete(file); + + try { + if (hbaseAdapter.getHbaseMapping().containsKey(file.getName())) { + deleteConfigFromCache(file); + + logger.info("Delete a hbase mapping config: {} of canal adapter", file.getName()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void addConfigToCache(File file, MappingConfig config) { + hbaseAdapter.getHbaseMapping().put(file.getName(), config); + Map configMap = hbaseAdapter.getMappingConfigCache() + .computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "." + + config.getHbaseMapping().getDatabase() + "." + config.getHbaseMapping().getTable(), + k1 -> new HashMap<>()); + configMap.put(file.getName(), config); + } + + private void deleteConfigFromCache(File file) { + + hbaseAdapter.getHbaseMapping().remove(file.getName()); + for (Map configMap : hbaseAdapter.getMappingConfigCache().values()) { + if (configMap != null) { + configMap.remove(file.getName()); + } + } + + } + } +} diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationConfigMonitor.java similarity index 97% rename from client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java rename to client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationConfigMonitor.java index 0397464d..8d519184 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationRunningMonitor.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/monitor/ApplicationConfigMonitor.java @@ -22,9 +22,9 @@ import com.alibaba.otter.canal.adapter.launcher.loader.CanalAdapterService; import com.alibaba.otter.canal.client.adapter.support.Util; @Component -public class ApplicationRunningMonitor { +public class ApplicationConfigMonitor { - private static final Logger logger = LoggerFactory.getLogger(ApplicationRunningMonitor.class); + private static final Logger logger = LoggerFactory.getLogger(ApplicationConfigMonitor.class); @Resource private ContextRefresher contextRefresher; diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 4e7e2504..11fe5c2f 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -28,22 +28,31 @@ import com.alibaba.otter.canal.client.adapter.support.*; @SPI("rdb") public class RdbAdapter implements OuterAdapter { - private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); + private static Logger logger = LoggerFactory.getLogger(RdbAdapter.class); - private Map rdbMapping = new HashMap<>(); // 文件名对应配置 - private Map mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 + private Map rdbMapping = new HashMap<>(); // 文件名对应配置 + private Map> mappingConfigCache = new HashMap<>(); // 库名-表名对应配置 - private DruidDataSource dataSource; + private DruidDataSource dataSource; - private RdbSyncService rdbSyncService; + private RdbSyncService rdbSyncService; - private int commitSize = 3000; + private int commitSize = 3000; - private volatile boolean running = false; + private volatile boolean running = false; - private List dmlList = Collections.synchronizedList(new ArrayList<>()); - private Lock syncLock = new ReentrantLock(); - private ExecutorService executor = Executors.newFixedThreadPool(1); + private List dmlList = Collections + .synchronizedList(new ArrayList<>()); + private Lock syncLock = new ReentrantLock(); + private ExecutorService executor = Executors.newFixedThreadPool(1); + + public Map getRdbMapping() { + return rdbMapping; + } + + public Map> getMappingConfigCache() { + return mappingConfigCache; + } @Override public void init(OuterAdapterConfig configuration) { @@ -56,11 +65,15 @@ public class RdbAdapter implements OuterAdapter { rdbMapping.put(key, mappingConfig); } }); - for (MappingConfig mappingConfig : rdbMapping.values()) { - mappingConfigCache - .put(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." - + mappingConfig.getDbMapping().getDatabase() + "." + mappingConfig.getDbMapping().getTable(), - mappingConfig); + for (Map.Entry entry : rdbMapping.entrySet()) { + String configName = entry.getKey(); + MappingConfig mappingConfig = entry.getValue(); + Map configMap = mappingConfigCache + .computeIfAbsent(StringUtils.trimToEmpty(mappingConfig.getDestination()) + "." + + mappingConfig.getDbMapping().getDatabase() + "." + + mappingConfig.getDbMapping().getTable(), + k1 -> new HashMap<>()); + configMap.put(configName, mappingConfig); } Map properties = configuration.getProperties(); @@ -113,16 +126,19 @@ public class RdbAdapter implements OuterAdapter { String destination = StringUtils.trimToEmpty(dml.getDestination()); String database = dml.getDatabase(); String table = dml.getTable(); - MappingConfig config = mappingConfigCache.get(destination + "." + database + "." + table); + Map configMap = mappingConfigCache.get(destination + "." + database + "." + table); - List simpleDmlList = SimpleDml.dml2SimpleDml(dml, config); + if (configMap != null) { + configMap.values().forEach(config -> { + List simpleDmlList = SimpleDml.dml2SimpleDml(dml, config); - dmlList.addAll(simpleDmlList); + dmlList.addAll(simpleDmlList); - if (dmlList.size() > commitSize) { - sync(); + if (dmlList.size() > commitSize) { + sync(); + } + }); } - if (logger.isDebugEnabled()) { logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java new file mode 100644 index 00000000..d74cd884 --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/monitor/RdbConfigMonitor.java @@ -0,0 +1,141 @@ +package com.alibaba.otter.canal.client.adapter.rdb.monitor; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.io.filefilter.FileFilterUtils; +import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +import com.alibaba.otter.canal.client.adapter.rdb.RdbAdapter; +import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.support.MappingConfigsLoader; +import com.alibaba.otter.canal.client.adapter.support.Util; + +public class RdbConfigMonitor { + + private static final Logger logger = LoggerFactory.getLogger(RdbConfigMonitor.class); + + private static final String adapterName = "rdb"; + + private String key; + + private RdbAdapter rdbAdapter; + + private FileAlterationMonitor fileMonitor; + + public void init(String key, RdbAdapter rdbAdapter) { + this.key = key; + this.rdbAdapter = rdbAdapter; + File confDir = Util.getConfDirPath(adapterName); + try { + FileAlterationObserver observer = new FileAlterationObserver(confDir, + FileFilterUtils.and(FileFilterUtils.fileFileFilter(), FileFilterUtils.suffixFileFilter("yml"))); + FileListener listener = new FileListener(); + observer.addListener(listener); + fileMonitor = new FileAlterationMonitor(3000, observer); + fileMonitor.start(); + + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + public void destroy() { + try { + fileMonitor.stop(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private class FileListener extends FileAlterationListenerAdaptor { + + @Override + public void onFileCreate(File file) { + super.onFileCreate(file); + try { + // 加载新增的配置文件 + String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName()); + MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); + config.validate(); + if ((key == null && config.getOuterAdapterKey() == null) + || (key != null && key.equals(config.getOuterAdapterKey()))) { + addConfigToCache(file, config); + + logger.info("Add a new rdb mapping config: {} to canal adapter", file.getName()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + @Override + public void onFileChange(File file) { + super.onFileChange(file); + + try { + if (rdbAdapter.getRdbMapping().containsKey(file.getName())) { + // 加载配置文件 + String configContent = MappingConfigsLoader.loadConfig(adapterName + File.separator + file.getName()); + MappingConfig config = new Yaml().loadAs(configContent, MappingConfig.class); + config.validate(); + if ((key == null && config.getOuterAdapterKey() == null) + || (key != null && key.equals(config.getOuterAdapterKey()))) { + if (rdbAdapter.getRdbMapping().containsKey(file.getName())) { + deleteConfigFromCache(file); + } + addConfigToCache(file, config); + } else { + // 不能修改outerAdapterKey + throw new RuntimeException("Outer adapter key not allowed modify"); + } + logger.info("Change a rdb mapping config: {} of canal adapter", file.getName()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + @Override + public void onFileDelete(File file) { + super.onFileDelete(file); + + try { + if (rdbAdapter.getRdbMapping().containsKey(file.getName())) { + deleteConfigFromCache(file); + + logger.info("Delete a rdb mapping config: {} of canal adapter", file.getName()); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + } + + private void addConfigToCache(File file, MappingConfig config) { + rdbAdapter.getRdbMapping().put(file.getName(), config); + Map configMap = rdbAdapter.getMappingConfigCache() + .computeIfAbsent(StringUtils.trimToEmpty(config.getDestination()) + "." + + config.getDbMapping().getDatabase() + "." + config.getDbMapping().getTable(), + k1 -> new HashMap<>()); + configMap.put(file.getName(), config); + } + + private void deleteConfigFromCache(File file) { + + rdbAdapter.getRdbMapping().remove(file.getName()); + for (Map configMap : rdbAdapter.getMappingConfigCache().values()) { + if (configMap != null) { + configMap.remove(file.getName()); + } + } + + } + } +} From 991418ce6ef9f7edce57d85c691dc45b0c6695ed Mon Sep 17 00:00:00 2001 From: mcy Date: Fri, 16 Nov 2018 22:53:10 +0800 Subject: [PATCH 336/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9rdb=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=8F=90=E4=BA=A4=E5=88=86=E6=89=B9=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../launcher/config/RefresherConfig.java | 22 ---------------- .../canal/client/adapter/rdb/RdbAdapter.java | 25 ++++++++++++++----- .../adapter/rdb/service/RdbSyncService.java | 1 + 3 files changed, 20 insertions(+), 28 deletions(-) delete mode 100644 client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java deleted file mode 100644 index 127b7ceb..00000000 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/RefresherConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.alibaba.otter.canal.adapter.launcher.config; - -import org.springframework.cloud.context.refresh.ContextRefresher; -import org.springframework.cloud.context.scope.refresh.RefreshScope; -import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class RefresherConfig { - - @Bean - public RefreshScope refreshScope() { - return new RefreshScope(); - } - - @Bean - public ContextRefresher contextRefresher(ConfigurableApplicationContext configurableApplicationContext, - RefreshScope refreshScope) { - return new ContextRefresher(configurableApplicationContext, refreshScope); - } -} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 11fe5c2f..78001c03 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -5,6 +5,8 @@ import java.sql.SQLException; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -20,6 +22,7 @@ import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; +import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; import com.alibaba.otter.canal.client.adapter.rdb.support.SimpleDml; @@ -44,8 +47,11 @@ public class RdbAdapter implements OuterAdapter { private List dmlList = Collections .synchronizedList(new ArrayList<>()); private Lock syncLock = new ReentrantLock(); + private Condition condition = syncLock.newCondition(); private ExecutorService executor = Executors.newFixedThreadPool(1); + private RdbConfigMonitor rdbConfigMonitor; + public Map getRdbMapping() { return rdbMapping; } @@ -107,18 +113,21 @@ public class RdbAdapter implements OuterAdapter { executor.submit(() -> { while (running) { try { - int size1 = dmlList.size(); - Thread.sleep(3000); - int size2 = dmlList.size(); - if (size1 == size2) { + syncLock.lock(); + if (!condition.await(3, TimeUnit.SECONDS)) { // 超时提交 sync(); } } catch (Exception e) { logger.error(e.getMessage(), e); + } finally { + syncLock.unlock(); } } }); + + rdbConfigMonitor = new RdbConfigMonitor(); + rdbConfigMonitor.init(configuration.getKey(), this); } @Override @@ -131,10 +140,9 @@ public class RdbAdapter implements OuterAdapter { if (configMap != null) { configMap.values().forEach(config -> { List simpleDmlList = SimpleDml.dml2SimpleDml(dml, config); - dmlList.addAll(simpleDmlList); - if (dmlList.size() > commitSize) { + if (dmlList.size() >= commitSize) { sync(); } }); @@ -148,6 +156,7 @@ public class RdbAdapter implements OuterAdapter { try { syncLock.lock(); if (!dmlList.isEmpty()) { + condition.signal(); rdbSyncService.sync(dmlList); dmlList.clear(); } @@ -251,6 +260,10 @@ public class RdbAdapter implements OuterAdapter { @Override public void destroy() { running = false; + if (rdbConfigMonitor != null) { + rdbConfigMonitor.destroy(); + } + executor.shutdown(); if (rdbSyncService != null) { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 9467b8b9..9f1f5469 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -10,6 +10,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import javax.sql.DataSource; From fcdf95b5ecc3dc59e0d52f3de5fb594162cc6215 Mon Sep 17 00:00:00 2001 From: mcy Date: Sat, 17 Nov 2018 04:37:46 +0800 Subject: [PATCH 337/360] =?UTF-8?q?rdb=20=E6=89=B9=E9=87=8F=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E8=B6=85=E6=97=B6=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/es/ESAdapter.java | 7 +++++-- .../adapter/es/service/ESSyncService.java | 1 - .../canal/client/adapter/rdb/RdbAdapter.java | 21 ++++++++----------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java index 18cc0c93..35df8a76 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java @@ -1,14 +1,16 @@ package com.alibaba.otter.canal.client.adapter.es; import java.net.InetAddress; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.sql.DataSource; -import com.alibaba.otter.canal.client.adapter.es.monitor.ESConfigMonitor; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.settings.Settings; @@ -22,6 +24,7 @@ import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader; import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem; import com.alibaba.otter.canal.client.adapter.es.config.SqlParser; +import com.alibaba.otter.canal.client.adapter.es.monitor.ESConfigMonitor; import com.alibaba.otter.canal.client.adapter.es.service.ESEtlService; import com.alibaba.otter.canal.client.adapter.es.service.ESSyncService; import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate; diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java index 397e3e5a..823a9d4c 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/service/ESSyncService.java @@ -14,7 +14,6 @@ import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig; import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfig.ESMapping; -import com.alibaba.otter.canal.client.adapter.es.config.ESSyncConfigLoader; import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem; import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.ColumnItem; import com.alibaba.otter.canal.client.adapter.es.config.SchemaItem.FieldItem; diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 78001c03..291ecbab 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -5,7 +5,6 @@ import java.sql.SQLException; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -47,7 +46,6 @@ public class RdbAdapter implements OuterAdapter { private List dmlList = Collections .synchronizedList(new ArrayList<>()); private Lock syncLock = new ReentrantLock(); - private Condition condition = syncLock.newCondition(); private ExecutorService executor = Executors.newFixedThreadPool(1); private RdbConfigMonitor rdbConfigMonitor; @@ -112,16 +110,16 @@ public class RdbAdapter implements OuterAdapter { executor.submit(() -> { while (running) { + int beginSize = dmlList.size(); try { - syncLock.lock(); - if (!condition.await(3, TimeUnit.SECONDS)) { - // 超时提交 - sync(); - } - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - syncLock.unlock(); + Thread.sleep(3000); + } catch (InterruptedException e) { + // ignore + } + int endSize = dmlList.size(); + + if (endSize - beginSize < 300) { + sync(); } } }); @@ -156,7 +154,6 @@ public class RdbAdapter implements OuterAdapter { try { syncLock.lock(); if (!dmlList.isEmpty()) { - condition.signal(); rdbSyncService.sync(dmlList); dmlList.clear(); } From affd598470776f461f54cdc3f40c92fd72fbe931 Mon Sep 17 00:00:00 2001 From: mcy Date: Sat, 17 Nov 2018 21:18:48 +0800 Subject: [PATCH 338/360] es readme.md --- client-adapter/README.md | 221 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) diff --git a/client-adapter/README.md b/client-adapter/README.md index 7415b47b..5d9ab5ce 100644 --- a/client-adapter/README.md +++ b/client-adapter/README.md @@ -322,3 +322,224 @@ bin/startup.sh ``` #### 验证 修改mysql mytest.user表的数据, 将会自动同步到Oracle的MYTEST.TB_USER表下面, 并会打出DML的log + + +## 五、ElasticSearch适配器 +### 5.1 修改启动器配置: application.yml +``` +server: + port: 8081 +logging: + level: + com.alibaba.otter.canal.client.adapter.es: DEBUG +spring: + jackson: + date-format: yyyy-MM-dd HH:mm:ss + time-zone: GMT+8 + default-property-inclusion: non_null + +canal.conf: + canalServerHost: 127.0.0.1:11111 + flatMessage: true + srcDataSources: + defaultDS: + url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true + username: root + password: 121212 + canalInstances: + - instance: example + adapterGroups: + - outAdapters: + - name: es + hosts: 127.0.0.1:9300 # es 集群地址, 逗号分隔 + properties: + cluster.name: elasticsearch # es cluster name +``` +adapter将会自动加载 conf/es 下的所有.yml结尾的配置文件 +### 5.2 适配器表映射文件 +修改 conf/es/mytest_user.yml文件: +``` +dataSourceKey: defaultDS # 源数据源的key, 对应上面配置的srcDataSources中的值 +destination: example # cannal的instance或者MQ的topic +esMapping: + _index: mytest_user # es 的索引名称 + _type: _doc # es 的doc名称 + _id: _id # es 的_id, 如果不配置该项必须配置下面的pk项_id则会由es自动分配 +# pk: id # 如果不需要_id, 则需要指定一个属性为主键属性 + # sql映射 + sql: "select a.id as _id, a.name as _name, a.role_id as _role_id, b.role_name as _role_name, + a.c_time as _c_time, c.labels as _labels from user a + left join role b on b.id=a.role_id + left join (select user_id, group_concat(label order by id desc separator ';') as labels from label + group by user_id) c on c.user_id=a.id" +# objFields: +# _labels: array:; # 数组或者对象属性, array:; 代表以;字段里面是以;分隔的 +# _obj: obj:{"test":"123"} + etlCondition: "where a.c_time>='{0}'" # etl 的条件参数 + commitBatch: 3000 # 提交批大小 +``` +sql映射说明: + +sql支持多表关联自由组合, 但是有一定的限制: +1. 主表不能为子查询语句 +2. 只能使用left outer join即最左表一定要是主表 +3. 关联从表如果是子查询不能有多张表 +4. 主sql中不能有where查询条件(从表子查询中可以有where条件但是不推荐, 可能会造成数据同步的不一致, 比如修改了where条件中的字段内容) +5. 关联条件只允许主外键的'='操作不能出现其他常量判断比如: on a.role_id=b.id and b.statues=1 +6. 关联条件必须要有一个字段出现在主查询语句中比如: on a.role_id=b.id 其中的 a.role_id 或者 b.id 必须出现在主select语句中 + + +Elastic Search的mapping 属性与sql的查询值将一一对应(不支持 select *), 比如: select a.id as _id, a.name, a.email as _email from user, 其中name将映射到es mapping的name field, _email将 +映射到mapping的_email field, 这里以别名(如果有别名)作为最终的映射字段. 这里的_id可以填写到配置文件的 _id: _id映射. + +#### 5.2.1.单表映射索引示例sql: +``` +select a.id as _id, a.name, a.role_id, a.c_time from user a +``` +该sql对应的es mapping示例: +``` +{ + "mytest_user": { + "mappings": { + "_doc": { + "properties": { + "name": { + "type": "text" + }, + "role_id": { + "type": "long" + }, + "c_time": { + "type": "date" + } + } + } + } + } +} +``` + +#### 5.2.2.单表映射索引示例sql带函数或运算操作: +``` +select a.id as _id, concat(a.name,'_test') as name, a.role_id+10000 as role_id, a.c_time from user a +``` +函数字段后必须跟上别名, 该sql对应的es mapping示例: +``` +{ + "mytest_user": { + "mappings": { + "_doc": { + "properties": { + "name": { + "type": "text" + }, + "role_id": { + "type": "long" + }, + "c_time": { + "type": "date" + } + } + } + } + } +} +``` + +#### 5.2.3.多表映射(一对一, 多对一)索引示例sql: +``` +select a.id as _id, a.name, a.role_id, b.role_name, a.c_time from user a +left join role b on b.id = a.role_id +``` +注:这里join操作只能是left outer join, 第一张表必须为主表!! + +该sql对应的es mapping示例: +``` +{ + "mytest_user": { + "mappings": { + "_doc": { + "properties": { + "name": { + "type": "text" + }, + "role_id": { + "type": "long" + }, + "role_name": { + "type": "text" + }, + "c_time": { + "type": "date" + } + } + } + } + } +} +``` + +#### 5.2.4.多表映射(一对多)索引示例sql: +``` +select a.id as _id, a.name, a.role_id, c.labels, a.c_time from user a +left join (select user_id, group_concat(label order by id desc separator ';') as labels from label + group by user_id) c on c.user_id=a.id +``` +注:left join 后的子查询只允许一张表, 即子查询中不能再包含子查询或者关联!! + +该sql对应的es mapping示例: +``` +{ + "mytest_user": { + "mappings": { + "_doc": { + "properties": { + "name": { + "type": "text" + }, + "role_id": { + "type": "long" + }, + "c_time": { + "type": "date" + }, + "labels": { + "type": "text" + } + } + } + } + } +} +``` + +#### 5.2.5.其它类型的sql示例: +- geo type +``` +select ... concat(IFNULL(a.latitude, 0), ',', IFNULL(a.longitude, 0)) AS location, ... +``` +- 复合主键 +``` +select concat(a.id,'_',b.type) as _id, ... from user a left join role b on b.id=a.role_id +``` +- 数组字段 +``` +select a.id as _id, a.name, a.role_id, c.labels, a.c_time from user a +left join (select user_id, group_concat(label order by id desc separator ';') as labels from label + group by user_id) c on c.user_id=a.id +``` +配置中使用: +``` +objFields: + labels: array:; +``` + +### 5.3 启动ES数据同步 +#### 启动canal-adapter启动器 +``` +bin/startup.sh +``` +#### 验证 +1. 新增mysql mytest.user表的数据, 将会自动同步到es的mytest_user索引下面, 并会打出DML的log +2. 修改mysql mytest.role表的role_name, 将会自动同步es的mytest_user索引中的role_name数据 +3. 新增或者修改mysql mytest.label表的label, 将会自动同步es的mytest_user索引中的labels数据 \ No newline at end of file From 50630cc9344d966cb4daaa3ca33d6d2b8abbbdc6 Mon Sep 17 00:00:00 2001 From: "maowei.ymw" Date: Mon, 19 Nov 2018 14:33:59 +0800 Subject: [PATCH 339/360] Support client connect rocketmq in aliyun --- client/pom.xml | 8 ++++++ .../rocketmq/RocketMQCanalConnector.java | 26 ++++++++++++++++++- pom.xml | 10 +++++++ server/pom.xml | 5 +++- .../otter/canal/common/MQProperties.java | 17 ++++++++++++ .../canal/rocketmq/CanalRocketMQProducer.java | 16 ++++++++++-- 6 files changed, 78 insertions(+), 4 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index aa31390a..5994c860 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -122,6 +122,14 @@ junit test + + com.aliyun.openservices + aliware-apache-rocketmq-cloud + + + org.apache.rocketmq + rocketmq-client + diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java index da21ead8..bdb654de 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.client.rocketmq; +import com.aliyun.openservices.apache.api.impl.authority.SessionCredentials; +import com.aliyun.openservices.apache.api.impl.rocketmq.ClientRPCHook; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -10,8 +12,10 @@ import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; +import org.apache.rocketmq.client.consumer.rebalance.AllocateMessageQueueAveragely; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.remoting.RPCHook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +51,9 @@ public class RocketMQCanalConnector implements CanalMQConnector { private long batchProcessTimeout = 60 * 1000; private boolean flatMessage; private volatile ConsumerBatchMessage lastGetBatchMessage = null; + private String accessKey; + private String secretKey; + public RocketMQCanalConnector(String nameServer, String topic, String groupName, boolean flatMessage){ this.nameServer = nameServer; @@ -56,8 +63,25 @@ public class RocketMQCanalConnector implements CanalMQConnector { this.messageBlockingQueue = new LinkedBlockingQueue<>(1024); } + public RocketMQCanalConnector(String nameServer, String topic, String groupName, + String accessKey, String secretKey, boolean flatMessage){ + this(nameServer, topic, groupName, flatMessage); + this.accessKey = accessKey; + this.secretKey = secretKey; + } + public void connect() throws CanalClientException { - rocketMQConsumer = new DefaultMQPushConsumer(groupName); + + RPCHook rpcHook = null; + if(null != accessKey && accessKey.length() > 0 + && null != secretKey && secretKey.length() > 0){ + SessionCredentials sessionCredentials = new SessionCredentials(); + sessionCredentials.setAccessKey(accessKey); + sessionCredentials.setSecretKey(secretKey); + rpcHook = new ClientRPCHook(sessionCredentials); + } + rocketMQConsumer = new DefaultMQPushConsumer(groupName, rpcHook, new AllocateMessageQueueAveragely()); + rocketMQConsumer.setVipChannelEnabled(false); if (!StringUtils.isBlank(nameServer)) { rocketMQConsumer.setNamesrvAddr(nameServer); } diff --git a/pom.xml b/pom.xml index 0bb4bc11..2c2e8c8e 100644 --- a/pom.xml +++ b/pom.xml @@ -302,6 +302,16 @@ jsr305 3.0.2 + + com.aliyun.openservices + aliware-apache-rocketmq-cloud + 1.0-SNAPSHOT + + + org.apache.rocketmq + rocketmq-client + 4.3.0 + diff --git a/server/pom.xml b/server/pom.xml index 3d3084e7..fd1225f4 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -40,7 +40,6 @@ org.apache.rocketmq rocketmq-client - 4.3.0 @@ -55,5 +54,9 @@ junit test + + com.aliyun.openservices + aliware-apache-rocketmq-cloud + diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index 3bc3394b..2e6fbe69 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -22,6 +22,8 @@ public class MQProperties { private boolean flatMessage = true; private String compressionType = "none"; private String acks = "all"; + private String aliyunAccessKey = ""; + private String aliyunSecretKey = ""; public static class CanalDestination { @@ -168,4 +170,19 @@ public class MQProperties { this.acks = acks; } + public String getAliyunAccessKey() { + return aliyunAccessKey; + } + + public void setAliyunAccessKey(String aliyunAccessKey) { + this.aliyunAccessKey = aliyunAccessKey; + } + + public String getAliyunSecretKey() { + return aliyunSecretKey; + } + + public void setAliyunSecretKey(String aliyunSecretKey) { + this.aliyunSecretKey = aliyunSecretKey; + } } diff --git a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java index 3501930b..74e3920b 100644 --- a/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/rocketmq/CanalRocketMQProducer.java @@ -7,12 +7,15 @@ import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.protocol.FlatMessage; import com.alibaba.otter.canal.server.exception.CanalServerException; import com.alibaba.otter.canal.spi.CanalMQProducer; +import com.aliyun.openservices.apache.api.impl.authority.SessionCredentials; +import com.aliyun.openservices.apache.api.impl.rocketmq.ClientRPCHook; import org.apache.rocketmq.client.exception.MQBrokerException; import org.apache.rocketmq.client.exception.MQClientException; import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.client.producer.MessageQueueSelector; import org.apache.rocketmq.common.message.Message; import org.apache.rocketmq.common.message.MessageQueue; +import org.apache.rocketmq.remoting.RPCHook; import org.apache.rocketmq.remoting.exception.RemotingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,10 +33,19 @@ public class CanalRocketMQProducer implements CanalMQProducer { @Override public void init(MQProperties rocketMQProperties) { this.mqProperties = rocketMQProperties; - defaultMQProducer = new DefaultMQProducer(); + RPCHook rpcHook = null; + if(rocketMQProperties.getAliyunAccessKey().length() > 0 + && rocketMQProperties.getAliyunSecretKey().length() > 0){ + SessionCredentials sessionCredentials = new SessionCredentials(); + sessionCredentials.setAccessKey(rocketMQProperties.getAliyunAccessKey()); + sessionCredentials.setSecretKey(rocketMQProperties.getAliyunSecretKey()); + rpcHook = new ClientRPCHook(sessionCredentials); + } + + defaultMQProducer = new DefaultMQProducer(rocketMQProperties.getProducerGroup(), rpcHook); defaultMQProducer.setNamesrvAddr(rocketMQProperties.getServers()); - defaultMQProducer.setProducerGroup(rocketMQProperties.getProducerGroup()); defaultMQProducer.setRetryTimesWhenSendFailed(rocketMQProperties.getRetries()); + defaultMQProducer.setVipChannelEnabled(false); logger.info("##Start RocketMQ producer##"); try { defaultMQProducer.start(); From b6f1e0d13c4feb3e93a200688008dad4e5cf6b07 Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 20 Nov 2018 17:14:29 +0800 Subject: [PATCH 340/360] fixed rocketmq ack npe --- .../client/rocketmq/RocketMQCanalConnector.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java index da21ead8..2cf92a36 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/rocketmq/RocketMQCanalConnector.java @@ -214,9 +214,13 @@ public class RocketMQCanalConnector implements CanalMQConnector { @Override public void ack() throws CanalClientException { try { - this.lastGetBatchMessage.ack(); + if (this.lastGetBatchMessage != null) { + this.lastGetBatchMessage.ack(); + } } catch (Throwable e) { - this.lastGetBatchMessage.fail(); + if (this.lastGetBatchMessage != null) { + this.lastGetBatchMessage.fail(); + } } finally { this.lastGetBatchMessage = null; } @@ -225,7 +229,9 @@ public class RocketMQCanalConnector implements CanalMQConnector { @Override public void rollback() throws CanalClientException { try { - this.lastGetBatchMessage.fail(); + if (this.lastGetBatchMessage != null) { + this.lastGetBatchMessage.fail(); + } } finally { this.lastGetBatchMessage = null; } From a7be9c75f97bca9227d19f48e7fa721811b23a54 Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 20 Nov 2018 18:29:01 +0800 Subject: [PATCH 341/360] fixed mysql rotate checksum --- .../parse/inbound/mysql/MysqlConnection.java | 31 +++--------------- .../canal/parse/DirectLogFetcherTest.java | 32 ++++--------------- .../parse/inbound/mysql/MysqlDumpTest.java | 4 +-- 3 files changed, 14 insertions(+), 53 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java index c12ca1a3..0ec5e7aa 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java @@ -503,40 +503,19 @@ public class MysqlConnection implements ErosaConnection { * */ private void loadBinlogChecksum() { - if (checkMariaDB()) { - ResultSetPacket rs = null; - try { - rs = query("select @@global.binlog_checksum"); - } catch (IOException e) { - throw new CanalParseException(e); - } - - List columnValues = rs.getFieldValues(); - if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0).toUpperCase().equals("CRC32")) { - binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_CRC32; - } else { - binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF; - } - } - } - - /** - * 获取是否为mariadb - */ - private boolean checkMariaDB() { ResultSetPacket rs = null; try { - rs = query("SELECT @@version"); + rs = query("select @@global.binlog_checksum"); } catch (IOException e) { throw new CanalParseException(e); } List columnValues = rs.getFieldValues(); - if (columnValues != null && columnValues.size() >= 1) { - return StringUtils.containsIgnoreCase(columnValues.get(0), "MariaDB"); + if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0).toUpperCase().equals("CRC32")) { + binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_CRC32; + } else { + binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF; } - - return false; } private void accumulateReceivedBytes(long x) { diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java index fd01e6a8..719e86dd 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java @@ -44,10 +44,9 @@ import com.taobao.tddl.dbsync.binlog.event.mariadb.AnnotateRowsEvent; public class DirectLogFetcherTest { - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected String binlogFileName = "mysql-bin.000001"; protected Charset charset = Charset.forName("utf-8"); - private boolean isMariaDB; private int binlogChecksum; @Test @@ -193,7 +192,7 @@ public class DirectLogFetcherTest { // 如果不设置会出现错误: Slave can not handle replication events with the // checksum that master is configured to log // 但也不能乱设置,需要和mysql server的checksum配置一致,不然RotateLogEvent会出现乱码 - update("set @master_binlog_checksum= '@@global.binlog_checksum'", connector); + update("set @master_binlog_checksum= @@global.binlog_checksum", connector); } catch (Exception e) { logger.warn("update master_binlog_checksum failed", e); } @@ -217,35 +216,18 @@ public class DirectLogFetcherTest { } private void loadBinlogChecksum(MysqlConnector connector) { - checkMariaDB(connector); - if (isMariaDB) { - ResultSetPacket rs = null; - try { - rs = query("select @@global.binlog_checksum", connector); - } catch (IOException e) { - throw new CanalParseException(e); - } - - List columnValues = rs.getFieldValues(); - if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0).toUpperCase().equals("CRC32")) { - binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_CRC32; - } else { - binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF; - } - } - } - - private void checkMariaDB(MysqlConnector connector) { ResultSetPacket rs = null; try { - rs = query("SELECT @@version", connector); + rs = query("select @@global.binlog_checksum", connector); } catch (IOException e) { throw new CanalParseException(e); } List columnValues = rs.getFieldValues(); - if (columnValues != null && columnValues.size() >= 1) { - isMariaDB = StringUtils.containsIgnoreCase(columnValues.get(0), "MariaDB"); + if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0).toUpperCase().equals("CRC32")) { + binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_CRC32; + } else { + binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF; } } diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java index 6c11c637..8c749891 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlDumpTest.java @@ -29,12 +29,12 @@ public class MysqlDumpTest { @Test public void testSimple() { final MysqlEventParser controller = new MysqlEventParser(); - final EntryPosition startPosition = new EntryPosition("mysql-bin.000012", 34051L, 100L); + final EntryPosition startPosition = new EntryPosition("mysql-bin.000001", 4L); // startPosition.setGtid("f1ceb61a-a5d5-11e7-bdee-107c3dbcf8a7:1-17"); controller.setConnectionCharset(Charset.forName("UTF-8")); controller.setSlaveId(3344L); controller.setDetectingEnable(false); - controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3306), "canal", "canal")); + controller.setMasterInfo(new AuthenticationInfo(new InetSocketAddress("127.0.0.1", 3306), "root", "hello")); controller.setMasterPosition(startPosition); controller.setEnableTsdb(true); controller.setDestination("example"); From 26700f30056ec7e1faee0d7107e3af58e0f5d685 Mon Sep 17 00:00:00 2001 From: agapple Date: Tue, 20 Nov 2018 19:01:07 +0800 Subject: [PATCH 342/360] fixed batch sync dml --- .../canal/client/adapter/OuterAdapter.java | 2 +- .../client/adapter/support/MessageUtil.java | 30 ++++++++++++------ .../canal/client/adapter/es/ESAdapter.java | 31 +++++++++++++------ .../client/adapter/hbase/HbaseAdapter.java | 19 ++++++++++-- .../loader/AbstractCanalAdapterWorker.java | 27 ++++++++++------ .../adapter/logger/LoggerAdapterExample.java | 9 +++++- .../canal/client/adapter/rdb/RdbAdapter.java | 22 ++++++++++--- 7 files changed, 104 insertions(+), 36 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index edb34049..8042a376 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -29,7 +29,7 @@ public interface OuterAdapter { * * @param dml 数据包 */ - void sync(Dml dml); + void sync(List dmls); /** * 外部适配器销毁接口 diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index 847b9328..bf2bcfd1 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -19,12 +19,12 @@ import com.alibaba.otter.canal.protocol.Message; */ public class MessageUtil { - public static void parse4Dml(String destination, Message message, Consumer consumer) { + public static void parse4Dml(String destination, Message message, Consumer> consumer) { if (message == null) { return; } List entries = message.getEntries(); - + List dmls = new ArrayList(entries.size()); for (CanalEntry.Entry entry : entries) { if (entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONBEGIN || entry.getEntryType() == CanalEntry.EntryType.TRANSACTIONEND) { @@ -49,6 +49,7 @@ public class MessageUtil { dml.setEs(entry.getHeader().getExecuteTime()); dml.setTs(System.currentTimeMillis()); dml.setSql(rowChange.getSql()); + dmls.add(dml); List> data = new ArrayList<>(); List> old = new ArrayList<>(); @@ -88,11 +89,10 @@ public class MessageUtil { Map rowOld = new LinkedHashMap<>(); for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { if (updateSet.contains(column.getName())) { - rowOld.put(column.getName(), - JdbcTypeUtil.typeConvert(column.getName(), - column.getValue(), - column.getSqlType(), - column.getMysqlType())); + rowOld.put(column.getName(), JdbcTypeUtil.typeConvert(column.getName(), + column.getValue(), + column.getSqlType(), + column.getMysqlType())); } } // update操作将记录修改前的值 @@ -108,9 +108,21 @@ public class MessageUtil { dml.setOld(old); } } - - consumer.accept(dml); } + + consumer.accept(dmls); + } + + public static List flatMessage2Dml(String destination, List flatMessages) { + List dmls = new ArrayList(flatMessages.size()); + for (FlatMessage flatMessage : flatMessages) { + Dml dml = flatMessage2Dml(destination, flatMessage); + if (dml != null) { + dmls.add(dml); + } + } + + return dmls; } public static Dml flatMessage2Dml(String destination, FlatMessage flatMessage) { diff --git a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java index 35df8a76..9bb56792 100644 --- a/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java +++ b/client-adapter/elasticsearch/src/main/java/com/alibaba/otter/canal/client/adapter/es/ESAdapter.java @@ -28,7 +28,11 @@ import com.alibaba.otter.canal.client.adapter.es.monitor.ESConfigMonitor; import com.alibaba.otter.canal.client.adapter.es.service.ESEtlService; import com.alibaba.otter.canal.client.adapter.es.service.ESSyncService; import com.alibaba.otter.canal.client.adapter.es.support.ESTemplate; -import com.alibaba.otter.canal.client.adapter.support.*; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.EtlResult; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; +import com.alibaba.otter.canal.client.adapter.support.SPI; /** * ES外部适配器 @@ -71,8 +75,8 @@ public class ESAdapter implements OuterAdapter { // 过滤不匹配的key的配置 esSyncConfigTmp.forEach((key, config) -> { if ((config.getOuterAdapterKey() == null && configuration.getKey() == null) - || (config.getOuterAdapterKey() != null - && config.getOuterAdapterKey().equalsIgnoreCase(configuration.getKey()))) { + || (config.getOuterAdapterKey() != null && config.getOuterAdapterKey() + .equalsIgnoreCase(configuration.getKey()))) { esSyncConfig.put(key, config); } }); @@ -94,11 +98,15 @@ public class ESAdapter implements OuterAdapter { } String schema = matcher.group(2); - schemaItem.getAliasTableItems().values().forEach(tableItem -> { - Map esSyncConfigMap = dbTableEsSyncConfig - .computeIfAbsent(schema + "-" + tableItem.getTableName(), k -> new HashMap<>()); - esSyncConfigMap.put(configName, config); - }); + schemaItem.getAliasTableItems() + .values() + .forEach(tableItem -> { + Map esSyncConfigMap = dbTableEsSyncConfig.computeIfAbsent(schema + + "-" + + tableItem.getTableName(), + k -> new HashMap<>()); + esSyncConfigMap.put(configName, config); + }); } Map properties = configuration.getProperties(); @@ -122,7 +130,12 @@ public class ESAdapter implements OuterAdapter { } } - @Override + public void sync(List dmls) { + for (Dml dml : dmls) { + sync(dml); + } + } + public void sync(Dml dml) { String database = dml.getDatabase(); String table = dml.getTable(); diff --git a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java index a5172e75..e586bfe0 100644 --- a/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java +++ b/client-adapter/hbase/src/main/java/com/alibaba/otter/canal/client/adapter/hbase/HbaseAdapter.java @@ -13,8 +13,12 @@ import org.apache.commons.lang.StringUtils; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.hbase.TableName; -import org.apache.hadoop.hbase.client.*; +import org.apache.hadoop.hbase.client.Connection; +import org.apache.hadoop.hbase.client.ConnectionFactory; +import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Result; +import org.apache.hadoop.hbase.client.ResultScanner; +import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.filter.FirstKeyOnlyFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,7 +30,11 @@ import com.alibaba.otter.canal.client.adapter.hbase.monitor.HbaseConfigMonitor; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseEtlService; import com.alibaba.otter.canal.client.adapter.hbase.service.HbaseSyncService; import com.alibaba.otter.canal.client.adapter.hbase.support.HbaseTemplate; -import com.alibaba.otter.canal.client.adapter.support.*; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.EtlResult; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; +import com.alibaba.otter.canal.client.adapter.support.SPI; /** * HBase外部适配器 @@ -93,7 +101,12 @@ public class HbaseAdapter implements OuterAdapter { } } - @Override + public void sync(List dmls) { + for (Dml dml : dmls) { + sync(dml); + } + } + public void sync(Dml dml) { if (dml == null) { return; diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index e5dce8ae..d0c6bc70 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -2,15 +2,18 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; -import com.alibaba.otter.canal.client.CanalConnector; -import com.alibaba.otter.canal.client.CanalMQConnector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; +import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.MessageUtil; @@ -79,7 +82,7 @@ public abstract class AbstractCanalAdapterWorker { }); } - protected void writeOut(final FlatMessage flatMessage) { + protected void writeOut(final List flatMessages) { List> futures = new ArrayList<>(); // 组间适配器并行运行 canalOuterAdapters.forEach(outerAdapters -> { @@ -88,8 +91,8 @@ public abstract class AbstractCanalAdapterWorker { // 组内适配器穿行运行,尽量不要配置组内适配器 outerAdapters.forEach(adapter -> { long begin = System.currentTimeMillis(); - Dml dml = MessageUtil.flatMessage2Dml(canalDestination, flatMessage); - adapter.sync(dml); + List dmls = MessageUtil.flatMessage2Dml(canalDestination, flatMessages); + adapter.sync(dmls); if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", adapter.getClass().getName(), @@ -116,8 +119,8 @@ public abstract class AbstractCanalAdapterWorker { }); } - protected void mqWriteOutData(int retry, long timeout, boolean flatMessage, CanalMQConnector connector, - ExecutorService workerExecutor) { + protected void mqWriteOutData(int retry, long timeout, final boolean flatMessage, CanalMQConnector connector, + ExecutorService workerExecutor) { for (int i = 0; i < retry; i++) { try { List messages; @@ -128,12 +131,18 @@ public abstract class AbstractCanalAdapterWorker { } if (messages != null) { Future future = workerExecutor.submit(() -> { + List flatMessages = new ArrayList(messages.size()); for (final Object message : messages) { if (message instanceof FlatMessage) { - writeOut((FlatMessage) message); + flatMessages.add((FlatMessage) message); } else { writeOut((Message) message); } + + if (flatMessage) { + // batch write + writeOut(flatMessages); + } } return true; }); diff --git a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java index e9c693b7..bd7c87ab 100644 --- a/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java +++ b/client-adapter/logger/src/main/java/com/alibaba/otter/canal/client/adapter/logger/LoggerAdapterExample.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.client.adapter.logger; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,7 +29,12 @@ public class LoggerAdapterExample implements OuterAdapter { } - @Override + public void sync(List dmls) { + for (Dml dml : dmls) { + sync(dml); + } + } + public void sync(Dml dml) { logger.info("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 291ecbab..9ad20272 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -2,10 +2,14 @@ package com.alibaba.otter.canal.client.adapter.rdb; import java.sql.Connection; import java.sql.SQLException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -25,7 +29,12 @@ import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; import com.alibaba.otter.canal.client.adapter.rdb.support.SimpleDml; -import com.alibaba.otter.canal.client.adapter.support.*; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; +import com.alibaba.otter.canal.client.adapter.support.Dml; +import com.alibaba.otter.canal.client.adapter.support.EtlResult; +import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; +import com.alibaba.otter.canal.client.adapter.support.SPI; +import com.alibaba.otter.canal.client.adapter.support.Util; @SPI("rdb") public class RdbAdapter implements OuterAdapter { @@ -128,7 +137,12 @@ public class RdbAdapter implements OuterAdapter { rdbConfigMonitor.init(configuration.getKey(), this); } - @Override + public void sync(List dmls) { + for (Dml dml : dmls) { + sync(dml); + } + } + public void sync(Dml dml) { String destination = StringUtils.trimToEmpty(dml.getDestination()); String database = dml.getDatabase(); From 4bfc476e08b6ab41f33921ab0a25ef1b1eb1ebc4 Mon Sep 17 00:00:00 2001 From: "maowei.ymw" Date: Wed, 21 Nov 2018 10:44:58 +0800 Subject: [PATCH 343/360] Modify rocketMQ related pom --- client/pom.xml | 7 ------- pom.xml | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/client/pom.xml b/client/pom.xml index 5994c860..28ca8d27 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -101,12 +101,9 @@ ${spring_version} test - org.apache.rocketmq rocketmq-client - 4.3.0 - provided @@ -126,10 +123,6 @@ com.aliyun.openservices aliware-apache-rocketmq-cloud - - org.apache.rocketmq - rocketmq-client - diff --git a/pom.xml b/pom.xml index 2c2e8c8e..92d8a530 100644 --- a/pom.xml +++ b/pom.xml @@ -305,7 +305,7 @@ com.aliyun.openservices aliware-apache-rocketmq-cloud - 1.0-SNAPSHOT + 1.0 org.apache.rocketmq From 56ec66f90ecb00684a568304f5d7e6de5592e449 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 21 Nov 2018 13:46:30 +0800 Subject: [PATCH 344/360] =?UTF-8?q?adapter=20=E5=88=86=E6=89=B9=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/OuterAdapter.java | 2 +- .../adapter/support/CanalClientConfig.java | 28 +- .../client/adapter/support/MessageUtil.java | 20 +- .../loader/AbstractCanalAdapterWorker.java | 65 ++- .../loader/CanalAdapterKafkaWorker.java | 1 - .../loader/CanalAdapterRocketMQWorker.java | 1 - .../launcher/loader/CanalAdapterWorker.java | 2 - .../canal/client/adapter/rdb/RdbAdapter.java | 90 +-- .../adapter/rdb/service/RdbEtlService.java | 2 +- .../adapter/rdb/service/RdbSyncService.java | 513 +++++------------- .../{service => support}/BatchExecutor.java | 31 +- .../client/adapter/rdb/support/SimpleDml.java | 97 ---- .../client/adapter/rdb/support/SyncUtil.java | 213 ++++++++ .../canal/client/adapter/rdb/test/DBTest.java | 4 +- .../adapter/rdb/test/sync/OracleSyncTest.java | 4 +- .../client/kafka/KafkaCanalConnector.java | 2 +- .../otter/canal/kafka/CanalKafkaProducer.java | 42 +- 17 files changed, 477 insertions(+), 640 deletions(-) rename client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/{service => support}/BatchExecutor.java (68%) delete mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java index 8042a376..37786222 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/OuterAdapter.java @@ -27,7 +27,7 @@ public interface OuterAdapter { /** * 往适配器中同步数据 * - * @param dml 数据包 + * @param dmls 数据包 */ void sync(List dmls); diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index d2c19448..5edee911 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -13,23 +13,25 @@ import java.util.Map; */ public class CanalClientConfig { - private String canalServerHost; // 单机模式下canal server的 ip:port + private String canalServerHost; // 单机模式下canal server的 ip:port - private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! + private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! - private String mqServers; // kafka or rocket mq 地址 + private String mqServers; // kafka or rocket mq 地址 - private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 + private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 - private Integer batchSize; // 批大小 + private Integer batchSize; // 批大小 - private Integer retries; // 重试次数 + private Integer syncBatchSize = 1000; // 同步分批提交大小 - private Long timeout; // 消费超时时间 + private Integer retries; // 重试次数 - private List mqTopics; // mq topic 列表 + private Long timeout; // 消费超时时间 - private List canalInstances; // tcp 模式下 canal 实例列表, 与mq模式不能共存!! + private List mqTopics; // mq topic 列表 + + private List canalInstances; // tcp 模式下 canal 实例列表, 与mq模式不能共存!! public String getCanalServerHost() { return canalServerHost; @@ -83,6 +85,14 @@ public class CanalClientConfig { return retries; } + public Integer getSyncBatchSize() { + return syncBatchSize; + } + + public void setSyncBatchSize(Integer syncBatchSize) { + this.syncBatchSize = syncBatchSize; + } + public void setRetries(Integer retries) { this.retries = retries; } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java index bf2bcfd1..e40c9204 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/MessageUtil.java @@ -19,9 +19,9 @@ import com.alibaba.otter.canal.protocol.Message; */ public class MessageUtil { - public static void parse4Dml(String destination, Message message, Consumer> consumer) { + public static List parse4Dml(String destination, Message message) { if (message == null) { - return; + return null; } List entries = message.getEntries(); List dmls = new ArrayList(entries.size()); @@ -89,10 +89,11 @@ public class MessageUtil { Map rowOld = new LinkedHashMap<>(); for (CanalEntry.Column column : rowData.getBeforeColumnsList()) { if (updateSet.contains(column.getName())) { - rowOld.put(column.getName(), JdbcTypeUtil.typeConvert(column.getName(), - column.getValue(), - column.getSqlType(), - column.getMysqlType())); + rowOld.put(column.getName(), + JdbcTypeUtil.typeConvert(column.getName(), + column.getValue(), + column.getSqlType(), + column.getMysqlType())); } } // update操作将记录修改前的值 @@ -110,7 +111,7 @@ public class MessageUtil { } } - consumer.accept(dmls); + return dmls; } public static List flatMessage2Dml(String destination, List flatMessages) { @@ -177,9 +178,4 @@ public class MessageUtil { } return result; } - - public interface Consumer { - - void accept(T t); - } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index d0c6bc70..3702fd1e 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -2,12 +2,9 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; +import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +29,7 @@ public abstract class AbstractCanalAdapterWorker { protected String canalDestination; // canal实例 protected List> canalOuterAdapters; // 外部适配器 + protected CanalClientConfig canalClientConfig; // 配置 protected ExecutorService groupInnerExecutorService; // 组内工作线程池 protected volatile boolean running = false; // 是否运行中 protected Thread thread = null; @@ -55,11 +53,15 @@ public abstract class AbstractCanalAdapterWorker { // 组内适配器穿行运行,尽量不要配置组内适配器 adapters.forEach(adapter -> { long begin = System.currentTimeMillis(); - MessageUtil.parse4Dml(canalDestination, message, adapter::sync); - if (logger.isDebugEnabled()) { - logger.debug("{} elapsed time: {}", - adapter.getClass().getName(), - (System.currentTimeMillis() - begin)); + List dmls = MessageUtil.parse4Dml(canalDestination, message); + if (dmls != null) { + batchSync(dmls, adapter); + + if (logger.isDebugEnabled()) { + logger.debug("{} elapsed time: {}", + adapter.getClass().getName(), + (System.currentTimeMillis() - begin)); + } } }); return true; @@ -82,7 +84,7 @@ public abstract class AbstractCanalAdapterWorker { }); } - protected void writeOut(final List flatMessages) { + private void writeOut(final List flatMessages) { List> futures = new ArrayList<>(); // 组间适配器并行运行 canalOuterAdapters.forEach(outerAdapters -> { @@ -92,7 +94,8 @@ public abstract class AbstractCanalAdapterWorker { outerAdapters.forEach(adapter -> { long begin = System.currentTimeMillis(); List dmls = MessageUtil.flatMessage2Dml(canalDestination, flatMessages); - adapter.sync(dmls); + batchSync(dmls, adapter); + if (logger.isDebugEnabled()) { logger.debug("{} elapsed time: {}", adapter.getClass().getName(), @@ -119,6 +122,7 @@ public abstract class AbstractCanalAdapterWorker { }); } + @SuppressWarnings("unchecked") protected void mqWriteOutData(int retry, long timeout, final boolean flatMessage, CanalMQConnector connector, ExecutorService workerExecutor) { for (int i = 0; i < retry; i++) { @@ -131,18 +135,13 @@ public abstract class AbstractCanalAdapterWorker { } if (messages != null) { Future future = workerExecutor.submit(() -> { - List flatMessages = new ArrayList(messages.size()); - for (final Object message : messages) { - if (message instanceof FlatMessage) { - flatMessages.add((FlatMessage) message); - } else { + if (flatMessage) { + // batch write + writeOut((List) messages); + } else { + for (final Object message : messages) { writeOut((Message) message); } - - if (flatMessage) { - // batch write - writeOut(flatMessages); - } } return true; }); @@ -173,6 +172,28 @@ public abstract class AbstractCanalAdapterWorker { } } + /** + * 分批同步 + * + * @param dmls + * @param adapter + */ + private void batchSync(List dmls, OuterAdapter adapter) { + // 分批同步 + int len = 0; + List dmlsBatch = new ArrayList<>(); + for (Dml dml : dmls) { + dmlsBatch.add(dml); + len += dml.getData().size(); + if (len >= canalClientConfig.getSyncBatchSize()) { + adapter.sync(dmlsBatch); + dmlsBatch.clear(); + len = 0; + } + } + adapter.sync(dmlsBatch); + } + public void start() { if (!running) { thread = new Thread(this::process); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index b93379a6..631154c2 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -19,7 +19,6 @@ import com.alibaba.otter.canal.client.kafka.KafkaCanalConnector; */ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { - private CanalClientConfig canalClientConfig; private KafkaCanalConnector connector; private String topic; private boolean flatMessage; diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index f0ee5abc..7f2e3b56 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -21,7 +21,6 @@ import com.alibaba.otter.canal.protocol.Message; */ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { - private CanalClientConfig canalClientConfig; private RocketMQCanalConnector connector; private String topic; private boolean flatMessage; diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java index 95e8dd45..15207d84 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterWorker.java @@ -25,8 +25,6 @@ public class CanalAdapterWorker extends AbstractCanalAdapterWorker { private CanalConnector connector; - private CanalClientConfig canalClientConfig; - /** * 单台client适配器worker的构造方法 * diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index 9ad20272..cf99615f 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -2,16 +2,12 @@ package com.alibaba.otter.canal.client.adapter.rdb; import java.sql.Connection; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import javax.sql.DataSource; @@ -20,21 +16,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.OuterAdapter; import com.alibaba.otter.canal.client.adapter.rdb.config.ConfigLoader; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.monitor.RdbConfigMonitor; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbEtlService; import com.alibaba.otter.canal.client.adapter.rdb.service.RdbSyncService; -import com.alibaba.otter.canal.client.adapter.rdb.support.SimpleDml; -import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; -import com.alibaba.otter.canal.client.adapter.support.Dml; -import com.alibaba.otter.canal.client.adapter.support.EtlResult; -import com.alibaba.otter.canal.client.adapter.support.OuterAdapterConfig; -import com.alibaba.otter.canal.client.adapter.support.SPI; -import com.alibaba.otter.canal.client.adapter.support.Util; +import com.alibaba.otter.canal.client.adapter.support.*; @SPI("rdb") public class RdbAdapter implements OuterAdapter { @@ -48,13 +36,6 @@ public class RdbAdapter implements OuterAdapter { private RdbSyncService rdbSyncService; - private int commitSize = 3000; - - private volatile boolean running = false; - - private List dmlList = Collections - .synchronizedList(new ArrayList<>()); - private Lock syncLock = new ReentrantLock(); private ExecutorService executor = Executors.newFixedThreadPool(1); private RdbConfigMonitor rdbConfigMonitor; @@ -108,72 +89,18 @@ public class RdbAdapter implements OuterAdapter { logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e); } - String threads = properties.get("threads"); - String commitSize = properties.get("commitSize"); - if (commitSize != null) { - this.commitSize = Integer.valueOf(commitSize); - } - rdbSyncService = new RdbSyncService(threads != null ? Integer.valueOf(threads) : null, dataSource); + // String threads = properties.get("threads"); + // String commitSize = properties.get("commitSize"); - running = true; - - executor.submit(() -> { - while (running) { - int beginSize = dmlList.size(); - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - // ignore - } - int endSize = dmlList.size(); - - if (endSize - beginSize < 300) { - sync(); - } - } - }); + rdbSyncService = new RdbSyncService(mappingConfigCache, dataSource); rdbConfigMonitor = new RdbConfigMonitor(); rdbConfigMonitor.init(configuration.getKey(), this); } + @Override public void sync(List dmls) { - for (Dml dml : dmls) { - sync(dml); - } - } - - public void sync(Dml dml) { - String destination = StringUtils.trimToEmpty(dml.getDestination()); - String database = dml.getDatabase(); - String table = dml.getTable(); - Map configMap = mappingConfigCache.get(destination + "." + database + "." + table); - - if (configMap != null) { - configMap.values().forEach(config -> { - List simpleDmlList = SimpleDml.dml2SimpleDml(dml, config); - dmlList.addAll(simpleDmlList); - - if (dmlList.size() >= commitSize) { - sync(); - } - }); - } - if (logger.isDebugEnabled()) { - logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); - } - } - - private void sync() { - try { - syncLock.lock(); - if (!dmlList.isEmpty()) { - rdbSyncService.sync(dmlList); - dmlList.clear(); - } - } finally { - syncLock.unlock(); - } + rdbSyncService.sync(dmls); } @Override @@ -270,17 +197,12 @@ public class RdbAdapter implements OuterAdapter { @Override public void destroy() { - running = false; if (rdbConfigMonitor != null) { rdbConfigMonitor.destroy(); } executor.shutdown(); - if (rdbSyncService != null) { - rdbSyncService.close(); - } - if (dataSource != null) { dataSource.close(); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java index 3cf4976a..a42e5f3c 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbEtlService.java @@ -231,7 +231,7 @@ public class RdbEtlService { Object value = rs.getObject(srcColumnName); if (value != null) { - RdbSyncService.setPStmt(type, pstmt, value, i); + SyncUtil.setPStmt(type, pstmt, value, i); } else { pstmt.setNull(i, type); } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index 9f1f5469..a23259a0 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -1,27 +1,27 @@ package com.alibaba.otter.canal.client.adapter.rdb.service; -import java.io.Reader; -import java.io.StringReader; -import java.math.BigDecimal; -import java.nio.charset.StandardCharsets; -import java.sql.*; +import java.sql.Connection; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.ConcurrentHashMap; import javax.sql.DataSource; -import org.joda.time.DateTime; +import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; -import com.alibaba.otter.canal.client.adapter.rdb.support.SimpleDml; +import com.alibaba.otter.canal.client.adapter.rdb.support.BatchExecutor; import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; +import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Util; /** @@ -36,87 +36,48 @@ public class RdbSyncService { private final Map> COLUMNS_TYPE_CACHE = new ConcurrentHashMap<>(); - private BatchExecutor[] batchExecutors; + private Map> mappingConfigCache; // 库名-表名对应配置 - private int threads = 1; + private DataSource dataSource; - private ExecutorService[] threadExecutors; + public RdbSyncService(Map> mappingConfigCache, DataSource dataSource){ + this.mappingConfigCache = mappingConfigCache; + this.dataSource = dataSource; + } - public RdbSyncService(Integer threads, DataSource dataSource){ - try { - if (threads != null && threads > 1 && threads <= 10) { - this.threads = threads; - } - batchExecutors = new BatchExecutor[this.threads]; - for (int i = 0; i < this.threads; i++) { - Connection conn = dataSource.getConnection(); - conn.setAutoCommit(false); - this.batchExecutors[i] = new BatchExecutor(i, conn); - } - threadExecutors = new ExecutorService[this.threads]; - for (int i = 0; i < this.threads; i++) { - threadExecutors[i] = Executors.newFixedThreadPool(1); + public void sync(List dmls) { + try (BatchExecutor batchExecutor = new BatchExecutor(dataSource.getConnection())) { + for (Dml dml : dmls) { + String destination = StringUtils.trimToEmpty(dml.getDestination()); + String database = dml.getDatabase(); + String table = dml.getTable(); + Map configMap = mappingConfigCache + .get(destination + "." + database + "." + table); + + for (MappingConfig config : configMap.values()) { + sync(batchExecutor, config, dml); + } } + batchExecutor.commit(); } catch (SQLException e) { - logger.error(e.getMessage(), e); + throw new RuntimeException(e); } } - @SuppressWarnings("unchecked") - private List[] simpleDmls2Partition(List simpleDmlList) { - List[] simpleDmlPartition = new ArrayList[threads]; - for (int i = 0; i < threads; i++) { - simpleDmlPartition[i] = new ArrayList<>(); - } - simpleDmlList.forEach(simpleDml -> { - int hash; - if (simpleDml.getConfig().getConcurrent()) { - hash = pkHash(simpleDml.getConfig().getDbMapping(), simpleDml.getData(), threads); - } else { - hash = Math.abs(Math.abs(simpleDml.getConfig().getDbMapping().getTargetTable().hashCode()) % threads); - } - simpleDmlPartition[hash].add(simpleDml); - }); - return simpleDmlPartition; - } - - public void sync(List simpleDmlList) { + private void sync(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { try { - List[] simpleDmlsPartition = simpleDmls2Partition(simpleDmlList); - - List> futures = new ArrayList<>(); - for (int i = 0; i < threads; i++) { - if (!simpleDmlsPartition[i].isEmpty()) { - int j = i; - futures.add(threadExecutors[i].submit(() -> { - simpleDmlsPartition[j].forEach(simpleDml -> sync(simpleDml, batchExecutors[j])); - batchExecutors[j].commit(); - return true; - })); + if (config != null) { + String type = dml.getType(); + if (type != null && type.equalsIgnoreCase("INSERT")) { + insert(batchExecutor, config, dml); + } else if (type != null && type.equalsIgnoreCase("UPDATE")) { + update(batchExecutor, config, dml); + } else if (type != null && type.equalsIgnoreCase("DELETE")) { + delete(batchExecutor, config, dml); } - } - - futures.forEach(future -> { - try { - future.get(); - } catch (Exception e) { - // ignore + if (logger.isDebugEnabled()) { + logger.debug("DML: {}", JSON.toJSONString(dml, SerializerFeature.WriteMapNullValue)); } - }); - } catch (Exception e) { - logger.error("Error rdb sync for batch", e); - } - } - - public void sync(SimpleDml dml, BatchExecutor batchExecutor) { - try { - String type = dml.getType(); - if (type != null && type.equalsIgnoreCase("INSERT")) { - insert(dml, batchExecutor); - } else if (type != null && type.equalsIgnoreCase("UPDATE")) { - update(dml, batchExecutor); - } else if (type != null && type.equalsIgnoreCase("DELETE")) { - delete(dml, batchExecutor); } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -126,53 +87,57 @@ public class RdbSyncService { /** * 插入操作 * - * @param simpleDml DML数据 + * @param config 配置项 + * @param dml DML数据 */ - private void insert(SimpleDml simpleDml, BatchExecutor batchExecutor) { - Map data = simpleDml.getData(); + private void insert(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { + List> data = dml.getData(); if (data == null || data.isEmpty()) { return; } - DbMapping dbMapping = simpleDml.getConfig().getDbMapping(); - - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); - - StringBuilder insertSql = new StringBuilder(); - insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); - - columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); - int len = insertSql.length(); - insertSql.delete(len - 1, len).append(") VALUES ("); - int mapLen = columnsMap.size(); - for (int i = 0; i < mapLen; i++) { - insertSql.append("?,"); - } - len = insertSql.length(); - insertSql.delete(len - 1, len).append(")"); - - Map ctype = getTargetColumnType(batchExecutor.getConn(), simpleDml.getConfig()); - - String sql = insertSql.toString(); + DbMapping dbMapping = config.getDbMapping(); try { - List> values = new ArrayList<>(); + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); - for (Map.Entry entry : columnsMap.entrySet()) { - String targetColumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetColumnName; + StringBuilder insertSql = new StringBuilder(); + insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); + + columnsMap.forEach((targetColumnName, srcColumnName) -> insertSql.append(targetColumnName).append(",")); + int len = insertSql.length(); + insertSql.delete(len - 1, len).append(") VALUES ("); + int mapLen = columnsMap.size(); + for (int i = 0; i < mapLen; i++) { + insertSql.append("?,"); + } + len = insertSql.length(); + insertSql.delete(len - 1, len).append(")"); + + Map ctype = getTargetColumnType(batchExecutor.getConn(), config); + + for (Map d : data) { + List> values = new ArrayList<>(); + for (Map.Entry entry : columnsMap.entrySet()) { + String targetClolumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetClolumnName; + } + + Integer type = ctype.get(targetClolumnName.toLowerCase()); + + Object value = d.get(srcColumnName); + + BatchExecutor.setValue(values, type, value); + } + + batchExecutor.execute(insertSql.toString(), values); + if (logger.isTraceEnabled()) { + logger.trace("Insert into target table, sql: {}", insertSql); } - Integer type = ctype.get(targetColumnName.toLowerCase()); - if (type == null) { - throw new RuntimeException("No column: " + targetColumnName + " found in target db"); - } - Object value = data.get(srcColumnName); - BatchExecutor.setValue(values, type, value); } - batchExecutor.execute(sql, values); } catch (Exception e) { logger.error(e.getMessage(), e); } @@ -180,56 +145,63 @@ public class RdbSyncService { /** * 更新操作 - * - * @param simpleDml DML数据 + * + * @param config 配置项 + * @param dml DML数据 */ - private void update(SimpleDml simpleDml, BatchExecutor batchExecutor) { - Map data = simpleDml.getData(); + private void update(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { + List> data = dml.getData(); if (data == null || data.isEmpty()) { return; } - Map old = simpleDml.getOld(); + List> old = dml.getOld(); if (old == null || old.isEmpty()) { return; } - DbMapping dbMapping = simpleDml.getConfig().getDbMapping(); + DbMapping dbMapping = config.getDbMapping(); - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); - - Map ctype = getTargetColumnType(batchExecutor.getConn(), simpleDml.getConfig()); + int idx = 1; try { - StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); - List> values = new ArrayList<>(); - for (String srcColumnName : old.keySet()) { - List targetColumnNames = new ArrayList<>(); - columnsMap.forEach((targetColumn, srcColumn) -> { - if (srcColumnName.toLowerCase().equals(srcColumn)) { - targetColumnNames.add(targetColumn); - } - }); - if (!targetColumnNames.isEmpty()) { - for (String targetColumnName : targetColumnNames) { - updateSql.append(targetColumnName).append("=?, "); - Integer type = ctype.get(targetColumnName.toLowerCase()); - BatchExecutor.setValue(values, type, data.get(srcColumnName)); + Map ctype = getTargetColumnType(batchExecutor.getConn(), config); + + for (Map o : old) { + Map d = data.get(idx - 1); + StringBuilder updateSql = new StringBuilder(); + updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); + List> values = new ArrayList<>(); + for (String srcColumnName : o.keySet()) { + List targetColumnNames = new ArrayList<>(); + columnsMap.forEach((targetColumn, srcColumn) -> { + if (srcColumnName.toLowerCase().equals(srcColumn)) { + targetColumnNames.add(targetColumn); + } + }); + if (!targetColumnNames.isEmpty()) { + + for (String targetColumnName : targetColumnNames) { + updateSql.append(targetColumnName).append("=?, "); + Integer type = ctype.get(targetColumnName.toLowerCase()); + BatchExecutor.setValue(values, type, d.get(srcColumnName)); + } } } - } - int len = updateSql.length(); - updateSql.delete(len - 2, len).append(" WHERE "); + int len = updateSql.length(); + updateSql.delete(len - 2, len).append(" WHERE "); - // 拼接主键 - appendCondition(dbMapping, updateSql, ctype, values, data, old); + // 拼接主键 + appendCondition(dbMapping, updateSql, ctype, values, d, o); - batchExecutor.execute(updateSql.toString(), values); + batchExecutor.execute(updateSql.toString(), values); - if (logger.isTraceEnabled()) { - logger.trace("Execute sql: {}", updateSql); + if (logger.isTraceEnabled()) { + logger.trace("Update target table, sql: {}", updateSql); + } + idx++; } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -238,31 +210,34 @@ public class RdbSyncService { /** * 删除操作 - * - * @param simpleDml + * + * @param config + * @param dml */ - private void delete(SimpleDml simpleDml, BatchExecutor batchExecutor) { - Map data = simpleDml.getData(); + private void delete(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { + List> data = dml.getData(); if (data == null || data.isEmpty()) { return; } - DbMapping dbMapping = simpleDml.getConfig().getDbMapping(); - - Map ctype = getTargetColumnType(batchExecutor.getConn(), simpleDml.getConfig()); + DbMapping dbMapping = config.getDbMapping(); try { - StringBuilder sql = new StringBuilder(); - sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); + Map ctype = getTargetColumnType(batchExecutor.getConn(), config); - List> values = new ArrayList<>(); + for (Map d : data) { + StringBuilder sql = new StringBuilder(); + sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); - // 拼接主键 - appendCondition(dbMapping, sql, ctype, values, data); + List> values = new ArrayList<>(); + // 拼接主键 + appendCondition(dbMapping, sql, ctype, values, d); - batchExecutor.execute(sql.toString(), values); - if (logger.isTraceEnabled()) { - logger.trace("Execute sql: {}", sql); + batchExecutor.execute(sql.toString(), values); + + if (logger.isTraceEnabled()) { + logger.trace("Delete from target table, sql: {}", sql); + } } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -271,7 +246,7 @@ public class RdbSyncService { /** * 获取目标字段类型 - * + * * @param conn sql connection * @param config 映射配置 * @return 字段sqlType @@ -305,181 +280,16 @@ public class RdbSyncService { return columnType; } - /** - * 设置 preparedStatement - * - * @param type sqlType - * @param pstmt 需要设置的preparedStatement - * @param value 值 - * @param i 索引号 - */ - public static void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException { - if (value == null) { - pstmt.setNull(i, type); - return; - } - switch (type) { - case Types.BIT: - case Types.BOOLEAN: - if (value instanceof Boolean) { - pstmt.setBoolean(i, (Boolean) value); - } else if (value instanceof String) { - boolean v = !value.equals("0"); - pstmt.setBoolean(i, v); - } else if (value instanceof Number) { - boolean v = ((Number) value).intValue() != 0; - pstmt.setBoolean(i, v); - } else { - pstmt.setNull(i, type); - } - break; - case Types.CHAR: - case Types.NCHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: - pstmt.setString(i, value.toString()); - break; - case Types.TINYINT: - if (value instanceof Number) { - pstmt.setByte(i, ((Number) value).byteValue()); - } else if (value instanceof String) { - pstmt.setByte(i, Byte.parseByte((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.SMALLINT: - if (value instanceof Number) { - pstmt.setShort(i, ((Number) value).shortValue()); - } else if (value instanceof String) { - pstmt.setShort(i, Short.parseShort((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.INTEGER: - if (value instanceof Number) { - pstmt.setInt(i, ((Number) value).intValue()); - } else if (value instanceof String) { - pstmt.setInt(i, Integer.parseInt((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.BIGINT: - if (value instanceof Number) { - pstmt.setLong(i, ((Number) value).longValue()); - } else if (value instanceof String) { - pstmt.setLong(i, Long.parseLong((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.DECIMAL: - case Types.NUMERIC: - pstmt.setBigDecimal(i, new BigDecimal(value.toString())); - break; - case Types.REAL: - if (value instanceof Number) { - pstmt.setFloat(i, ((Number) value).floatValue()); - } else if (value instanceof String) { - pstmt.setFloat(i, Float.parseFloat((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.FLOAT: - case Types.DOUBLE: - if (value instanceof Number) { - pstmt.setDouble(i, ((Number) value).doubleValue()); - } else if (value instanceof String) { - pstmt.setDouble(i, Double.parseDouble((String) value)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - case Types.BLOB: - - if (value instanceof byte[]) { - pstmt.setBytes(i, (byte[]) value); - } else if (value instanceof String) { - pstmt.setBytes(i, ((String) value).getBytes(StandardCharsets.ISO_8859_1)); - } else { - pstmt.setNull(i, type); - } - break; - case Types.CLOB: - if (value instanceof byte[]) { - pstmt.setBytes(i, (byte[]) value); - } else if (value instanceof String) { - Reader clobReader = new StringReader((String) value); - pstmt.setCharacterStream(i, clobReader); - } else { - pstmt.setNull(i, type); - } - break; - case Types.DATE: - if (value instanceof java.util.Date) { - pstmt.setDate(i, new Date(((java.util.Date) value).getTime())); - } else if (value instanceof String) { - String v = (String) value; - if (!v.startsWith("0000-00-00")) { - v = v.trim().replace(" ", "T"); - DateTime dt = new DateTime(v); - pstmt.setDate(i, new Date(dt.toDate().getTime())); - } else { - pstmt.setNull(i, type); - } - } else { - pstmt.setNull(i, type); - } - break; - case Types.TIME: - if (value instanceof java.util.Date) { - pstmt.setTime(i, new Time(((java.util.Date) value).getTime())); - } else if (value instanceof String) { - String v = (String) value; - v = "T" + v; - DateTime dt = new DateTime(v); - pstmt.setTime(i, new Time(dt.toDate().getTime())); - } else { - pstmt.setNull(i, type); - } - break; - case Types.TIMESTAMP: - if (value instanceof java.util.Date) { - pstmt.setTimestamp(i, new Timestamp(((java.util.Date) value).getTime())); - } else if (value instanceof String) { - String v = (String) value; - if (!v.startsWith("0000-00-00")) { - v = v.trim().replace(" ", "T"); - DateTime dt = new DateTime(v); - pstmt.setTimestamp(i, new Timestamp(dt.toDate().getTime())); - } else { - pstmt.setNull(i, type); - } - } else { - pstmt.setNull(i, type); - } - break; - default: - pstmt.setObject(i, value, type); - } - } - /** * 拼接主键 where条件 */ - private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map ctype, - List> values, Map d) { + private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map ctype, + List> values, Map d) { appendCondition(dbMapping, sql, ctype, values, d, null); } - private static void appendCondition(DbMapping dbMapping, StringBuilder sql, Map ctype, - List> values, Map d, Map o) { + private void appendCondition(MappingConfig.DbMapping dbMapping, StringBuilder sql, Map ctype, + List> values, Map d, Map o) { // 拼接主键 for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { String targetColumnName = entry.getKey(); @@ -499,43 +309,4 @@ public class RdbSyncService { int len = sql.length(); sql.delete(len - 4, len); } - - /** - * 取主键hash - */ - private static int pkHash(DbMapping dbMapping, Map d, int threads) { - return pkHash(dbMapping, d, null, threads); - } - - private static int pkHash(DbMapping dbMapping, Map d, Map o, int threads) { - int hash = 0; - // 取主键 - for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { - String targetColumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetColumnName; - } - Object value; - if (o != null && o.containsKey(srcColumnName)) { - value = o.get(srcColumnName); - } else { - value = d.get(srcColumnName); - } - if (value != null) { - hash += value.hashCode(); - } - } - hash = Math.abs(hash) % threads; - return Math.abs(hash); - } - - public void close() { - for (BatchExecutor batchExecutor : batchExecutors) { - batchExecutor.close(); - } - for (ExecutorService executorService : threadExecutors) { - executorService.shutdown(); - } - } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/BatchExecutor.java similarity index 68% rename from client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java rename to client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/BatchExecutor.java index 3e3d442c..24ac01ba 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/BatchExecutor.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/BatchExecutor.java @@ -1,32 +1,28 @@ -package com.alibaba.otter.canal.client.adapter.rdb.service; +package com.alibaba.otter.canal.client.adapter.rdb.support; +import java.io.Closeable; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BatchExecutor { +public class BatchExecutor implements Closeable { - private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class); + private static final Logger logger = LoggerFactory.getLogger(BatchExecutor.class); private Integer key; private Connection conn; - private AtomicInteger idx = new AtomicInteger(0); - private ExecutorService executor = Executors.newFixedThreadPool(1); - private Lock commitLock = new ReentrantLock(); - private Condition condition = commitLock.newCondition(); + private AtomicInteger idx = new AtomicInteger(0); + + public BatchExecutor(Connection conn){ + this(1, conn); + } public BatchExecutor(Integer key, Connection conn){ this.key = key; @@ -60,10 +56,11 @@ public class BatchExecutor { for (int i = 0; i < len; i++) { int type = (Integer) values.get(i).get("type"); Object value = values.get(i).get("value"); - RdbSyncService.setPStmt(type, pstmt, value, i + 1); + SyncUtil.setPStmt(type, pstmt, value, i + 1); } pstmt.execute(); + idx.incrementAndGet(); } catch (SQLException e) { logger.error(e.getMessage(), e); } @@ -71,20 +68,17 @@ public class BatchExecutor { public void commit() { try { - commitLock.lock(); conn.commit(); if (logger.isTraceEnabled()) { logger.trace("Batch executor: " + key + " commit " + idx.get() + " rows"); } - condition.signal(); idx.set(0); } catch (SQLException e) { logger.error(e.getMessage(), e); - } finally { - commitLock.unlock(); } } + @Override public void close() { if (conn != null) { try { @@ -93,6 +87,5 @@ public class BatchExecutor { logger.error(e.getMessage(), e); } } - executor.shutdown(); } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java deleted file mode 100644 index d063843a..00000000 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SimpleDml.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.alibaba.otter.canal.client.adapter.rdb.support; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; -import com.alibaba.otter.canal.client.adapter.support.Dml; - -public class SimpleDml { - - private String destination; - private String database; - private String table; - private String type; - private Map data; - private Map old; - - private MappingConfig config; - - public String getDestination() { - return destination; - } - - public void setDestination(String destination) { - this.destination = destination; - } - - public String getDatabase() { - return database; - } - - public void setDatabase(String database) { - this.database = database; - } - - public String getTable() { - return table; - } - - public void setTable(String table) { - this.table = table; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - public Map getData() { - return data; - } - - public void setData(Map data) { - this.data = data; - } - - public Map getOld() { - return old; - } - - public void setOld(Map old) { - this.old = old; - } - - public MappingConfig getConfig() { - return config; - } - - public void setConfig(MappingConfig config) { - this.config = config; - } - - public static List dml2SimpleDml(Dml dml, MappingConfig config) { - List simpleDmlList = new ArrayList<>(); - int len = dml.getData().size(); - - for (int i = 0; i < len; i++) { - SimpleDml simpleDml = new SimpleDml(); - simpleDml.setDestination(dml.getDestination()); - simpleDml.setDatabase(dml.getDatabase()); - simpleDml.setTable(dml.getTable()); - simpleDml.setType(dml.getType()); - simpleDml.setData(dml.getData().get(i)); - if (dml.getOld() != null) { - simpleDml.setOld(dml.getOld().get(i)); - } - simpleDml.setConfig(config); - simpleDmlList.add(simpleDml); - } - - return simpleDmlList; - } -} diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java index 5e2c38bb..1b2e8e0a 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SyncUtil.java @@ -1,9 +1,16 @@ package com.alibaba.otter.canal.client.adapter.rdb.support; +import java.io.Reader; +import java.io.StringReader; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.*; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; +import org.joda.time.DateTime; + import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; public class SyncUtil { @@ -36,4 +43,210 @@ public class SyncUtil { } return columnsMap; } + + /** + * 设置 preparedStatement + * + * @param type sqlType + * @param pstmt 需要设置的preparedStatement + * @param value 值 + * @param i 索引号 + */ + public static void setPStmt(int type, PreparedStatement pstmt, Object value, int i) throws SQLException { + switch (type) { + case Types.BIT: + case Types.BOOLEAN: + if (value instanceof Boolean) { + pstmt.setBoolean(i, (Boolean) value); + } else if (value instanceof String) { + boolean v = !value.equals("0"); + pstmt.setBoolean(i, v); + } else if (value instanceof Number) { + boolean v = ((Number) value).intValue() != 0; + pstmt.setBoolean(i, v); + } else { + pstmt.setNull(i, type); + } + break; + case Types.CHAR: + case Types.NCHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: + if (value instanceof String) { + pstmt.setString(i, (String) value); + } else { + pstmt.setString(i, value.toString()); + } + break; + case Types.TINYINT: + if (value instanceof Byte || value instanceof Short || value instanceof Integer) { + pstmt.setByte(i, (byte) value); + } else if (value instanceof Number) { + pstmt.setByte(i, ((Number) value).byteValue()); + } else if (value instanceof String) { + pstmt.setByte(i, Byte.parseByte((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.SMALLINT: + if (value instanceof Byte || value instanceof Short || value instanceof Integer) { + pstmt.setShort(i, (short) value); + } else if (value instanceof Number) { + pstmt.setShort(i, ((Number) value).shortValue()); + } else if (value instanceof String) { + pstmt.setShort(i, Short.parseShort((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.INTEGER: + if (value instanceof Byte || value instanceof Short || value instanceof Integer + || value instanceof Long) { + pstmt.setInt(i, (int) value); + } else if (value instanceof Number) { + pstmt.setInt(i, ((Number) value).intValue()); + } else if (value instanceof String) { + pstmt.setInt(i, Integer.parseInt((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.BIGINT: + if (value instanceof Byte || value instanceof Short || value instanceof Integer + || value instanceof Long) { + pstmt.setLong(i, (long) value); + } else if (value instanceof Number) { + pstmt.setLong(i, ((Number) value).longValue()); + } else if (value instanceof String) { + pstmt.setLong(i, Long.parseLong((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.DECIMAL: + case Types.NUMERIC: + if (value instanceof BigDecimal) { + pstmt.setBigDecimal(i, (BigDecimal) value); + } else if (value instanceof Byte) { + pstmt.setInt(i, (int) value); + } else if (value instanceof Short) { + pstmt.setInt(i, (int) value); + } else if (value instanceof Integer) { + pstmt.setInt(i, (int) value); + } else if (value instanceof Long) { + pstmt.setLong(i, (long) value); + } else if (value instanceof Float) { + pstmt.setBigDecimal(i, new BigDecimal((float) value)); + } else if (value instanceof Double) { + pstmt.setBigDecimal(i, new BigDecimal((double) value)); + } else { + pstmt.setBigDecimal(i, new BigDecimal(value.toString())); + } + break; + case Types.REAL: + if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long + || value instanceof Float || value instanceof Double) { + pstmt.setFloat(i, (float) value); + } else if (value instanceof Number) { + pstmt.setFloat(i, ((Number) value).floatValue()); + } else if (value instanceof String) { + pstmt.setFloat(i, Float.parseFloat((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.FLOAT: + case Types.DOUBLE: + if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long + || value instanceof Float || value instanceof Double) { + pstmt.setDouble(i, (double) value); + } else if (value instanceof Number) { + pstmt.setDouble(i, ((Number) value).doubleValue()); + } else if (value instanceof String) { + pstmt.setDouble(i, Double.parseDouble((String) value)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.BINARY: + case Types.VARBINARY: + case Types.LONGVARBINARY: + case Types.BLOB: + if (value instanceof Blob) { + pstmt.setBlob(i, (Blob) value); + } else if (value instanceof byte[]) { + pstmt.setBytes(i, (byte[]) value); + } else if (value instanceof String) { + pstmt.setBytes(i, ((String) value).getBytes(StandardCharsets.ISO_8859_1)); + } else { + pstmt.setNull(i, type); + } + break; + case Types.CLOB: + if (value instanceof Clob) { + pstmt.setClob(i, (Clob) value); + } else if (value instanceof byte[]) { + pstmt.setBytes(i, (byte[]) value); + } else if (value instanceof String) { + Reader clobReader = new StringReader((String) value); + pstmt.setCharacterStream(i, clobReader); + } else { + pstmt.setNull(i, type); + } + break; + case Types.DATE: + if (value instanceof java.sql.Date) { + pstmt.setDate(i, (java.sql.Date) value); + } else if (value instanceof java.util.Date) { + pstmt.setDate(i, new java.sql.Date(((java.util.Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + if (!v.startsWith("0000-00-00")) { + v = v.trim().replace(" ", "T"); + DateTime dt = new DateTime(v); + pstmt.setDate(i, new Date(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + } else { + pstmt.setNull(i, type); + } + break; + case Types.TIME: + if (value instanceof java.sql.Time) { + pstmt.setTime(i, (java.sql.Time) value); + } else if (value instanceof java.util.Date) { + pstmt.setTime(i, new java.sql.Time(((java.util.Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + v = "T" + v; + DateTime dt = new DateTime(v); + pstmt.setTime(i, new Time(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + break; + case Types.TIMESTAMP: + if (value instanceof java.sql.Timestamp) { + pstmt.setTimestamp(i, (java.sql.Timestamp) value); + } else if (value instanceof java.util.Date) { + pstmt.setTimestamp(i, new java.sql.Timestamp(((java.util.Date) value).getTime())); + } else if (value instanceof String) { + String v = (String) value; + if (!v.startsWith("0000-00-00")) { + v = v.trim().replace(" ", "T"); + DateTime dt = new DateTime(v); + pstmt.setTimestamp(i, new Timestamp(dt.toDate().getTime())); + } else { + pstmt.setNull(i, type); + } + } else { + pstmt.setNull(i, type); + } + break; + default: + pstmt.setObject(i, value, type); + } + } } diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java index b4e4a02e..c983d7d8 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java @@ -42,13 +42,13 @@ public class DBTest { .prepareStatement("insert into user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)"); java.util.Date now = new java.util.Date(); - for (int i = 1; i <= 100000; i++) { + for (int i = 1; i <= 10000; i++) { pstmt.clearParameters(); pstmt.setLong(1, (long) i); pstmt.setString(2, "test_" + i); pstmt.setLong(3, (long) i % 4 + 1); pstmt.setDate(4, new java.sql.Date(now.getTime())); - pstmt.setString(5, "tttt"); + pstmt.setString(5, null); pstmt.setBytes(6, null); pstmt.execute(); diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java index a00a9ce5..5ea374e2 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/sync/OracleSyncTest.java @@ -35,7 +35,7 @@ public class OracleSyncTest { data.put("test1", "sdfasdfawe中国asfwef"); dml.setData(dataList); - rdbAdapter.sync(dml); + rdbAdapter.sync(Collections.singletonList(dml)); } @Test @@ -58,7 +58,7 @@ public class OracleSyncTest { old.put("name", "Eric"); dml.setOld(oldList); - rdbAdapter.sync(dml); + rdbAdapter.sync(Collections.singletonList(dml)); } } diff --git a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java index 31f75b19..00403374 100644 --- a/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java +++ b/client/src/main/java/com/alibaba/otter/canal/client/kafka/KafkaCanalConnector.java @@ -174,7 +174,7 @@ public class KafkaCanalConnector implements CanalMQConnector { return Lists.newArrayList(); } - ConsumerRecords records = kafkaConsumer.poll(unit.toMillis(timeout)); // 基于配置,最多只能poll到一条数据 + ConsumerRecords records = kafkaConsumer.poll(unit.toMillis(timeout)); if (!records.isEmpty()) { List messages = new ArrayList<>(); diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index d7cf05bb..bf97de8e 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -1,11 +1,8 @@ package com.alibaba.otter.canal.kafka; -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.otter.canal.common.MQProperties; -import com.alibaba.otter.canal.protocol.FlatMessage; -import com.alibaba.otter.canal.protocol.Message; -import com.alibaba.otter.canal.spi.CanalMQProducer; +import java.util.List; +import java.util.Properties; + import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerRecord; @@ -13,8 +10,12 @@ import org.apache.kafka.common.serialization.StringSerializer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.Properties; +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.serializer.SerializerFeature; +import com.alibaba.otter.canal.common.MQProperties; +import com.alibaba.otter.canal.protocol.FlatMessage; +import com.alibaba.otter.canal.protocol.Message; +import com.alibaba.otter.canal.spi.CanalMQProducer; /** * kafka producer 主操作类 @@ -38,7 +39,7 @@ public class CanalKafkaProducer implements CanalMQProducer { Properties properties = new Properties(); properties.put("bootstrap.servers", kafkaProperties.getServers()); properties.put("acks", kafkaProperties.getAcks()); - properties.put("compression.type",kafkaProperties.getCompressionType()); + properties.put("compression.type", kafkaProperties.getCompressionType()); properties.put("retries", kafkaProperties.getRetries()); properties.put("batch.size", kafkaProperties.getBatchSize()); properties.put("linger.ms", kafkaProperties.getLingerMs()); @@ -89,6 +90,12 @@ public class CanalKafkaProducer implements CanalMQProducer { } producer.send(record).get(); + + if (logger.isDebugEnabled()) { + logger.debug("Send message to kafka topic: [{}], packet: {}", + canalDestination.getTopic(), + message.toString()); + } } catch (Exception e) { logger.error(e.getMessage(), e); // producer.abortTransaction(); @@ -102,7 +109,8 @@ public class CanalKafkaProducer implements CanalMQProducer { for (FlatMessage flatMessage : flatMessages) { if (canalDestination.getPartition() != null) { try { - ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord( + canalDestination.getTopic(), canalDestination.getPartition(), null, JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); @@ -124,7 +132,8 @@ public class CanalKafkaProducer implements CanalMQProducer { FlatMessage flatMessagePart = partitionFlatMessage[i]; if (flatMessagePart != null) { try { - ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord( + canalDestination.getTopic(), i, null, JSON.toJSONString(flatMessagePart, SerializerFeature.WriteMapNullValue)); @@ -139,7 +148,8 @@ public class CanalKafkaProducer implements CanalMQProducer { } } else { try { - ProducerRecord record = new ProducerRecord(canalDestination.getTopic(), + ProducerRecord record = new ProducerRecord( + canalDestination.getTopic(), 0, null, JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); @@ -152,15 +162,17 @@ public class CanalKafkaProducer implements CanalMQProducer { } } } + if (logger.isDebugEnabled()) { + logger.debug("Send flat message to kafka topic: [{}], packet: {}", + canalDestination.getTopic(), + JSON.toJSONString(flatMessage, SerializerFeature.WriteMapNullValue)); + } } } } // producer.commitTransaction(); callback.commit(); - if (logger.isDebugEnabled()) { - logger.debug("send message to kafka topic: {}", canalDestination.getTopic()); - } } From 638952fad19768cb748f666cee0e311fe598c826 Mon Sep 17 00:00:00 2001 From: agapple Date: Wed, 21 Nov 2018 13:51:34 +0800 Subject: [PATCH 345/360] fixed binlog seek & aliyun ak/sk --- .../taobao/tddl/dbsync/binlog/LogDecoder.java | 11 +- .../otter/canal/deployer/CanalConstants.java | 2 + .../otter/canal/deployer/CanalController.java | 156 +++++++++--------- .../otter/canal/deployer/CanalLauncher.java | 9 + deployer/src/main/resources/canal.properties | 10 +- .../main/resources/spring/base-instance.xml | 4 +- .../parse/inbound/mysql/MysqlEventParser.java | 2 +- .../inbound/mysql/dbsync/LogEventConvert.java | 2 +- .../otter/canal/common/MQProperties.java | 8 +- 9 files changed, 111 insertions(+), 93 deletions(-) diff --git a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java index 41739cf8..3c1bff17 100644 --- a/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java +++ b/dbsync/src/main/java/com/taobao/tddl/dbsync/binlog/LogDecoder.java @@ -109,11 +109,6 @@ public final class LogDecoder { try { /* Decoding binary-log to event */ event = decode(buffer, header, context); - if (event != null) { - // set logFileName - event.getHeader().setLogFileName(context.getLogPosition().getFileName()); - event.setSemival(buffer.semival); - } } catch (IOException e) { if (logger.isWarnEnabled()) { logger.warn("Decoding " + LogEvent.getTypeName(header.getType()) + " failed from: " @@ -128,6 +123,12 @@ public final class LogDecoder { event = new UnknownLogEvent(header); } + if (event != null) { + // set logFileName + event.getHeader().setLogFileName(context.getLogPosition().getFileName()); + event.setSemival(buffer.semival); + } + /* consume this binary-log. */ buffer.consume(len); return event; diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index aa9b2909..d21348bf 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -47,6 +47,8 @@ public class CanalConstants { public static final String CANAL_MQ_FLATMESSAGE = ROOT + "." + "mq.flatMessage"; public static final String CANAL_MQ_COMPRESSION_TYPE = ROOT + "." + "mq.compressionType"; public static final String CANAL_MQ_ACKS = ROOT + "." + "mq.acks"; + public static final String CANAL_ALIYUN_ACCESSKEY = ROOT + "." + "aliyun.accessKey"; + public static final String CANAL_ALIYUN_SECRETKEY = ROOT + "." + "aliyun.secretKey"; public static String getInstanceModeKey(String destination) { return MessageFormat.format(INSTANCE_MODE_TEMPLATE, destination); diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java index 9161bff0..dbc5938e 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalController.java @@ -17,7 +17,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.alibaba.otter.canal.common.CanalException; -import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.common.utils.AddressUtils; import com.alibaba.otter.canal.common.zookeeper.ZkClientx; import com.alibaba.otter.canal.common.zookeeper.ZookeeperPathUtils; @@ -96,6 +95,16 @@ public class CanalController { System.setProperty(CanalConstants.CANAL_SOCKETCHANNEL, socketChannel); } + // 兼容1.1.0版本的ak/sk参数名 + String accesskey = getProperty(properties, "canal.instance.rds.accesskey"); + String secretkey = getProperty(properties, "canal.instance.rds.secretkey"); + if (StringUtils.isNotEmpty(accesskey)) { + System.setProperty(CanalConstants.CANAL_ALIYUN_ACCESSKEY, accesskey); + } + if (StringUtils.isNotEmpty(secretkey)) { + System.setProperty(CanalConstants.CANAL_ALIYUN_SECRETKEY, secretkey); + } + // 准备canal server cid = Long.valueOf(getProperty(properties, CanalConstants.CANAL_ID)); ip = getProperty(properties, CanalConstants.CANAL_IP); @@ -131,81 +140,80 @@ public class CanalController { final ServerRunningData serverData = new ServerRunningData(cid, ip + ":" + port); ServerRunningMonitors.setServerData(serverData); - ServerRunningMonitors - .setRunningMonitors(MigrateMap.makeComputingMap(new Function() { + ServerRunningMonitors.setRunningMonitors(MigrateMap.makeComputingMap(new Function() { - public ServerRunningMonitor apply(final String destination) { - ServerRunningMonitor runningMonitor = new ServerRunningMonitor(serverData); - runningMonitor.setDestination(destination); - runningMonitor.setListener(new ServerRunningListener() { + public ServerRunningMonitor apply(final String destination) { + ServerRunningMonitor runningMonitor = new ServerRunningMonitor(serverData); + runningMonitor.setDestination(destination); + runningMonitor.setListener(new ServerRunningListener() { - public void processActiveEnter() { - try { - MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); - embededCanalServer.start(destination); - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); - } + public void processActiveEnter() { + try { + MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); + embededCanalServer.start(destination); + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); } - - public void processActiveExit() { - try { - MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); - embededCanalServer.stop(destination); - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); - } - } - - public void processStart() { - try { - if (zkclientx != null) { - final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, - ip + ":" + port); - initCid(path); - zkclientx.subscribeStateChanges(new IZkStateListener() { - - public void handleStateChanged(KeeperState state) throws Exception { - - } - - public void handleNewSession() throws Exception { - initCid(path); - } - - @Override - public void handleSessionEstablishmentError(Throwable error) throws Exception { - logger.error("failed to connect to zookeeper", error); - } - }); - } - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); - } - } - - public void processStop() { - try { - MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); - if (zkclientx != null) { - final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, - ip + ":" + port); - releaseCid(path); - } - } finally { - MDC.remove(CanalConstants.MDC_DESTINATION); - } - } - - }); - if (zkclientx != null) { - runningMonitor.setZkClient(zkclientx); } - // 触发创建一下cid节点 - runningMonitor.init(); - return runningMonitor; + + public void processActiveExit() { + try { + MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); + embededCanalServer.stop(destination); + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); + } + } + + public void processStart() { + try { + if (zkclientx != null) { + final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, ip + ":" + + port); + initCid(path); + zkclientx.subscribeStateChanges(new IZkStateListener() { + + public void handleStateChanged(KeeperState state) throws Exception { + + } + + public void handleNewSession() throws Exception { + initCid(path); + } + + @Override + public void handleSessionEstablishmentError(Throwable error) throws Exception { + logger.error("failed to connect to zookeeper", error); + } + }); + } + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); + } + } + + public void processStop() { + try { + MDC.put(CanalConstants.MDC_DESTINATION, String.valueOf(destination)); + if (zkclientx != null) { + final String path = ZookeeperPathUtils.getDestinationClusterNode(destination, ip + ":" + + port); + releaseCid(path); + } + } finally { + MDC.remove(CanalConstants.MDC_DESTINATION); + } + } + + }); + if (zkclientx != null) { + runningMonitor.setZkClient(zkclientx); } - })); + // 触发创建一下cid节点 + runningMonitor.init(); + return runningMonitor; + } + })); // 初始化monitor机制 autoScan = BooleanUtils.toBoolean(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN)); @@ -257,8 +265,7 @@ public class CanalController { instanceConfigMonitors = MigrateMap.makeComputingMap(new Function() { public InstanceConfigMonitor apply(InstanceMode mode) { - int scanInterval = Integer - .valueOf(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN_INTERVAL)); + int scanInterval = Integer.valueOf(getProperty(properties, CanalConstants.CANAL_AUTO_SCAN_INTERVAL)); if (mode.isSpring()) { SpringInstanceConfigMonitor monitor = new SpringInstanceConfigMonitor(); @@ -366,8 +373,7 @@ public class CanalController { InstanceConfig oldConfig = instanceConfigs.put(destination, config); if (oldConfig != null) { - logger - .warn("destination:{} old config:{} has replace by new config:{}", destination, oldConfig, config); + logger.warn("destination:{} old config:{} has replace by new config:{}", destination, oldConfig, config); } } } diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 80224380..24d0ec5b 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -125,6 +125,15 @@ public class CanalLauncher { if (!StringUtils.isEmpty(acks)) { mqProperties.setAcks(acks); } + + String aliyunAccessKey = CanalController.getProperty(properties, CanalConstants.CANAL_ALIYUN_ACCESSKEY); + if (!StringUtils.isEmpty(aliyunAccessKey)) { + mqProperties.setAliyunAccessKey(aliyunAccessKey); + } + String aliyunSecretKey = CanalController.getProperty(properties, CanalConstants.CANAL_ALIYUN_SECRETKEY); + if (!StringUtils.isEmpty(aliyunSecretKey)) { + mqProperties.setAliyunSecretKey(aliyunSecretKey); + } return mqProperties; } diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index d555c162..763dbd55 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -74,9 +74,9 @@ canal.instance.tsdb.snapshot.interval=24 # purge snapshot expire , default 360 hour(15 days) canal.instance.tsdb.snapshot.expire=360 -# rds oss binlog account -canal.instance.rds.accesskey = -canal.instance.rds.secretkey = +# aliyun ak/sk , support rds/mq +canal.aliyun.accesskey= +canal.aliyun.secretkey= ################################################# ######### destinations ############# @@ -109,5 +109,5 @@ canal.mq.bufferMemory=33554432 canal.mq.canalBatchSize=50 canal.mq.canalGetTimeout=100 canal.mq.flatMessage=true -canal.mq.compressionType= -canal.mq.acks= \ No newline at end of file +canal.mq.compressionType=none +canal.mq.acks=all \ No newline at end of file diff --git a/deployer/src/main/resources/spring/base-instance.xml b/deployer/src/main/resources/spring/base-instance.xml index e6c500b5..a89b7400 100644 --- a/deployer/src/main/resources/spring/base-instance.xml +++ b/deployer/src/main/resources/spring/base-instance.xml @@ -32,8 +32,8 @@ - - + + \ No newline at end of file diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index b9ce071b..0528d19c 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -747,7 +747,7 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE if (justForPositionTimestamp && logPosition.getPostion() == null && event.getWhen() > 0) { // 初始位点 entryPosition = new EntryPosition(searchBinlogFile, - event.getLogPos(), + event.getLogPos() - event.getEventLen(), event.getWhen() * 1000, event.getServerId()); logPosition.setPostion(entryPosition); diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java index 1ba0930d..2832ff34 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/dbsync/LogEventConvert.java @@ -657,7 +657,7 @@ public class LogEventConvert extends AbstractCanalLifeCycle implements BinlogPar + tableMeta.getFullName() + ", db fieldMeta : " + fieldMeta.toString() + " , binlog fieldMeta : " + info.toString() + " , on : " + event.getHeader().getLogFileName() + ":" - + event.getHeader().getLogPos()); + + (event.getHeader().getLogPos() - event.getHeader().getEventLen())); } } diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index 2e6fbe69..cf72a2dd 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -20,10 +20,10 @@ public class MQProperties { private int canalBatchSize = 50; private Long canalGetTimeout = 100L; private boolean flatMessage = true; - private String compressionType = "none"; - private String acks = "all"; - private String aliyunAccessKey = ""; - private String aliyunSecretKey = ""; + private String compressionType = "none"; + private String acks = "all"; + private String aliyunAccessKey = ""; + private String aliyunSecretKey = ""; public static class CanalDestination { From 67cc73b8e2c91a3901d4e5f62ce4d3cf3daa0277 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 21 Nov 2018 14:26:19 +0800 Subject: [PATCH 346/360] =?UTF-8?q?adapter=20=E9=85=8D=E7=BD=AE=E4=BF=AE?= =?UTF-8?q?=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapter/support/CanalClientConfig.java | 125 ++++++------------ .../launcher/config/AdapterCanalConfig.java | 32 ++--- .../launcher/loader/CanalAdapterLoader.java | 85 ++++++------ .../src/main/resources/application.yml | 20 ++- .../canal/client/adapter/rdb/test/DBTest.java | 2 +- 5 files changed, 108 insertions(+), 156 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 5edee911..8e1b5596 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -1,6 +1,5 @@ package com.alibaba.otter.canal.client.adapter.support; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -13,25 +12,25 @@ import java.util.Map; */ public class CanalClientConfig { - private String canalServerHost; // 单机模式下canal server的 ip:port + private String canalServerHost; // 单机模式下canal server的 ip:port - private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! + private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! - private String mqServers; // kafka or rocket mq 地址 + private String mqServers; // kafka or rocket mq 地址 - private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 + private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 - private Integer batchSize; // 批大小 + private Integer batchSize; // 批大小 - private Integer syncBatchSize = 1000; // 同步分批提交大小 + private Integer syncBatchSize = 1000; // 同步分批提交大小 - private Integer retries; // 重试次数 + private Integer retries; // 重试次数 - private Long timeout; // 消费超时时间 + private Long timeout; // 消费超时时间 - private List mqTopics; // mq topic 列表 + private String mode = "tcp"; // 模式 tcp kafka rocketMQ - private List canalInstances; // tcp 模式下 canal 实例列表, 与mq模式不能共存!! + private List canalAdapters; // canal adapters 配置 public String getCanalServerHost() { return canalServerHost; @@ -57,14 +56,6 @@ public class CanalClientConfig { this.mqServers = mqServers; } - public List getMqTopics() { - return mqTopics; - } - - public void setMqTopics(List mqTopics) { - this.mqTopics = mqTopics; - } - public Boolean getFlatMessage() { return flatMessage; } @@ -105,18 +96,28 @@ public class CanalClientConfig { this.timeout = timeout; } - public List getCanalInstances() { - return canalInstances; + public String getMode() { + return mode; } - public void setCanalInstances(List canalInstances) { - this.canalInstances = canalInstances; + public void setMode(String mode) { + this.mode = mode; } - public static class CanalInstance { + public List getCanalAdapters() { + return canalAdapters; + } + + public void setCanalAdapters(List canalAdapters) { + this.canalAdapters = canalAdapters; + } + + public static class CanalAdapter { private String instance; // 实例名 + private String topic; // mq topic + private List groups; // 适配器分组列表 public String getInstance() { @@ -137,50 +138,6 @@ public class CanalClientConfig { this.groups = groups; } - } - - public static class Group { - - private List outAdapters; // 适配器列表 - - private Map outAdaptersMap = new LinkedHashMap<>(); - - public List getOutAdapters() { - return outAdapters; - } - - public void setOutAdapters(List outAdapters) { - this.outAdapters = outAdapters; - if (outAdapters != null) { - outAdapters.forEach(outAdapter -> outAdaptersMap.put(outAdapter.getKey(), outAdapter)); - } - } - - public Map getOutAdaptersMap() { - return outAdaptersMap; - } - - public void setOutAdaptersMap(Map outAdaptersMap) { - this.outAdaptersMap = outAdaptersMap; - } - } - - public static class MQTopic { - - private String mqMode; // mq模式 kafka or rocketMQ - - private String topic; // topic名 - - private List groups = new ArrayList<>(); // 分组列表 - - public String getMqMode() { - return mqMode; - } - - public void setMqMode(String mqMode) { - this.mqMode = mqMode; - } - public String getTopic() { return topic; } @@ -188,21 +145,15 @@ public class CanalClientConfig { public void setTopic(String topic) { this.topic = topic; } - - public List getGroups() { - return groups; - } - - public void setGroups(List groups) { - this.groups = groups; - } } - public static class MQGroup { + public static class Group { - private String groupId; // group id + private String groupId; // group id - private List outAdapters; // 适配器配置列表 + private List outerAdapters; // 适配器列表 + + private Map outerAdaptersMap = new LinkedHashMap<>(); public String getGroupId() { return groupId; @@ -212,14 +163,20 @@ public class CanalClientConfig { this.groupId = groupId; } - public List getOutAdapters() { - return outAdapters; + public List getOuterAdapters() { + return outerAdapters; } - public void setOutAdapters(List outAdapters) { - this.outAdapters = outAdapters; + public void setOuterAdapters(List outerAdapters) { + this.outerAdapters = outerAdapters; } + public Map getOuterAdaptersMap() { + return outerAdaptersMap; + } + + public void setOuterAdaptersMap(Map outerAdaptersMap) { + this.outerAdaptersMap = outerAdaptersMap; + } } - } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index 9e4737d6..de27296e 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -6,12 +6,12 @@ import java.util.List; import java.util.Map; import java.util.Set; -import com.alibaba.druid.pool.DruidDataSource; -import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; +import com.alibaba.otter.canal.client.adapter.support.DatasourceConfig; /** * canal 的相关配置类 @@ -28,28 +28,18 @@ public class AdapterCanalConfig extends CanalClientConfig { private Map srcDataSources; @Override - public void setCanalInstances(List canalInstances) { - super.setCanalInstances(canalInstances); + public void setCanalAdapters(List canalAdapters) { + super.setCanalAdapters(canalAdapters); - if (canalInstances != null) { + if (canalAdapters != null) { synchronized (DESTINATIONS) { DESTINATIONS.clear(); - for (CanalInstance canalInstance : canalInstances) { - DESTINATIONS.add(canalInstance.getInstance()); - } - } - } - } - - @Override - public void setMqTopics(List mqTopics) { - super.setMqTopics(mqTopics); - - if (mqTopics != null) { - synchronized (DESTINATIONS) { - DESTINATIONS.clear(); - for (MQTopic mqTopic : mqTopics) { - DESTINATIONS.add(mqTopic.getTopic()); + for (CanalAdapter canalAdapter : canalAdapters) { + if (canalAdapter.getInstance() != null) { + DESTINATIONS.add(canalAdapter.getInstance()); + } else if (canalAdapter.getTopic() != null) { + DESTINATIONS.add(canalAdapter.getInstance()); + } } } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 900d1207..a72197fd 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -55,14 +54,14 @@ public class CanalAdapterLoader { } String zkHosts = this.canalClientConfig.getZookeeperHosts(); - if (canalClientConfig.getCanalInstances() != null) { + if ("tcp".equalsIgnoreCase(canalClientConfig.getMode())) { // 初始化canal-client的适配器 - for (CanalClientConfig.CanalInstance instance : canalClientConfig.getCanalInstances()) { + for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) { List> canalOuterAdapterGroups = new ArrayList<>(); - for (CanalClientConfig.Group connectorGroup : instance.getGroups()) { + for (CanalClientConfig.Group connectorGroup : canalAdapter.getGroups()) { List canalOutConnectors = new ArrayList<>(); - for (OuterAdapterConfig c : connectorGroup.getOutAdapters()) { + for (OuterAdapterConfig c : connectorGroup.getOuterAdapters()) { loadConnector(c, canalOutConnectors); } canalOuterAdapterGroups.add(canalOutConnectors); @@ -70,52 +69,65 @@ public class CanalAdapterLoader { CanalAdapterWorker worker; if (sa != null) { worker = new CanalAdapterWorker(canalClientConfig, - instance.getInstance(), + canalAdapter.getInstance(), sa, canalOuterAdapterGroups); } else if (zkHosts != null) { worker = new CanalAdapterWorker(canalClientConfig, - instance.getInstance(), + canalAdapter.getInstance(), zkHosts, canalOuterAdapterGroups); } else { throw new RuntimeException("No canal server connector found"); } - canalWorkers.put(instance.getInstance(), worker); + canalWorkers.put(canalAdapter.getInstance(), worker); worker.start(); - logger.info("Start adapter for canal instance: {} succeed", instance.getInstance()); + logger.info("Start adapter for canal instance: {} succeed", canalAdapter.getInstance()); } - } else if (canalClientConfig.getMqTopics() != null) { - // 初始化canal-client-mq的适配器 - for (CanalClientConfig.MQTopic topic : canalClientConfig.getMqTopics()) { - for (CanalClientConfig.MQGroup group : topic.getGroups()) { + } else if ("kafka".equalsIgnoreCase(canalClientConfig.getMode())) { + // 初始化canal-client-kafka的适配器 + for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) { + for (CanalClientConfig.Group group : canalAdapter.getGroups()) { List> canalOuterAdapterGroups = new ArrayList<>(); List canalOuterAdapters = new ArrayList<>(); - for (OuterAdapterConfig config : group.getOutAdapters()) { + for (OuterAdapterConfig config : group.getOuterAdapters()) { loadConnector(config, canalOuterAdapters); } canalOuterAdapterGroups.add(canalOuterAdapters); - if (StringUtils.isBlank(topic.getMqMode()) || "rocketmq".equalsIgnoreCase(topic.getMqMode())) { - CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig, - canalClientConfig.getMqServers(), - topic.getTopic(), - group.getGroupId(), - canalOuterAdapterGroups, - canalClientConfig.getFlatMessage()); - canalMQWorker.put(topic.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); - rocketMQWorker.start(); - } else if ("kafka".equalsIgnoreCase(topic.getMqMode())) { - CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig, - canalClientConfig.getMqServers(), - topic.getTopic(), - group.getGroupId(), - canalOuterAdapterGroups, - canalClientConfig.getFlatMessage()); - canalMQWorker.put(topic.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker); - canalKafkaWorker.start(); - } + + CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig, + canalClientConfig.getMqServers(), + canalAdapter.getTopic(), + group.getGroupId(), + canalOuterAdapterGroups, + canalClientConfig.getFlatMessage()); + canalMQWorker.put(canalAdapter.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker); + canalKafkaWorker.start(); logger.info("Start adapter for canal-client mq topic: {} succeed", - topic.getTopic() + "-" + group.getGroupId()); + canalAdapter.getTopic() + "-" + group.getGroupId()); + } + } + } else if ("rocketMQ".equalsIgnoreCase(canalClientConfig.getMode())) { + // 初始化canal-client-rocketMQ的适配器 + for (CanalClientConfig.CanalAdapter canalAdapter : canalClientConfig.getCanalAdapters()) { + for (CanalClientConfig.Group group : canalAdapter.getGroups()) { + List> canalOuterAdapterGroups = new ArrayList<>(); + List canalOuterAdapters = new ArrayList<>(); + for (OuterAdapterConfig config : group.getOuterAdapters()) { + loadConnector(config, canalOuterAdapters); + } + canalOuterAdapterGroups.add(canalOuterAdapters); + CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig, + canalClientConfig.getMqServers(), + canalAdapter.getTopic(), + group.getGroupId(), + canalOuterAdapterGroups, + canalClientConfig.getFlatMessage()); + canalMQWorker.put(canalAdapter.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); + rocketMQWorker.start(); + + logger.info("Start adapter for canal-client mq topic: {} succeed", + canalAdapter.getTopic() + "-" + group.getGroupId()); } } } @@ -124,11 +136,8 @@ public class CanalAdapterLoader { private void loadConnector(OuterAdapterConfig config, List canalOutConnectors) { try { OuterAdapter adapter; - // if ("rdb".equalsIgnoreCase(config.getName())) { adapter = loader.getExtension(config.getName(), StringUtils.trimToEmpty(config.getKey())); - // } else { - // adapter = loader.getExtension(config.getName()); - // } + ClassLoader cl = Thread.currentThread().getContextClassLoader(); // 替换ClassLoader Thread.currentThread().setContextClassLoader(adapter.getClass().getClassLoader()); diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 3df3f4be..4d120c6a 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -17,17 +17,22 @@ canal.conf: # zookeeperHosts: slave1:2181 # mqServers: slave1:6667 #or rocketmq # flatMessage: true + batchSize: 500 + syncBatchSize: 1000 retries: 0 timeout: + mode: tcp # kafka rocketMQ # srcDataSources: # defaultDS: # url: jdbc:mysql://127.0.0.1:3306/mytest?useUnicode=true # username: root # password: 121212 - canalInstances: + canalAdapters: - instance: example +# topic: example groups: - - outAdapters: + - #groupId: g1 + outerAdapters: - name: logger # - name: rdb # key: oracle1 @@ -36,8 +41,6 @@ canal.conf: # jdbc.url: jdbc:oracle:thin:@localhost:49161:XE # jdbc.username: mytest # jdbc.password: m121212 -# threads: 5 -# commitSize: 5000 # - name: rdb # key: postgres1 # properties: @@ -55,11 +58,4 @@ canal.conf: # - name: es # hosts: 127.0.0.1:9300 # properties: -# cluster.name: elasticsearch -# mqTopics: -# - mqMode: kafka # or rocketmq -# topic: example -# groups: -# - groupId: g2 -# outAdapters: -# - name: logger \ No newline at end of file +# cluster.name: elasticsearch \ No newline at end of file diff --git a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java index c983d7d8..5057765f 100644 --- a/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java +++ b/client-adapter/rdb/src/test/java/com/alibaba/otter/canal/client/adapter/rdb/test/DBTest.java @@ -42,7 +42,7 @@ public class DBTest { .prepareStatement("insert into user (id,name,role_id,c_time,test1,test2) values (?,?,?,?,?,?)"); java.util.Date now = new java.util.Date(); - for (int i = 1; i <= 10000; i++) { + for (int i = 1; i <= 100000; i++) { pstmt.clearParameters(); pstmt.setLong(1, (long) i); pstmt.setString(2, "test_" + i); From ad18e0335d738fdf856566f57ae5ff75342b9272 Mon Sep 17 00:00:00 2001 From: Alan Date: Wed, 21 Nov 2018 16:31:46 +0800 Subject: [PATCH 347/360] =?UTF-8?q?=E7=AD=89=E5=8F=B7=E4=B8=A4=E8=BE=B9?= =?UTF-8?q?=E9=83=BD=E6=9C=89=E7=A9=BA=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 格式保持一致,看起来更像大厂出品 ^_^ --- deployer/src/main/resources/canal.properties | 54 ++++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 763dbd55..78343b43 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -1,11 +1,11 @@ ################################################# ######### common argument ############# ################################################# -canal.id= 1 -canal.ip= -canal.port=11111 -canal.metrics.pull.port=11112 -canal.zkServers= +canal.id = 1 +canal.ip = +canal.port = 11111 +canal.metrics.pull.port = 11112 +canal.zkServers = # flush data to zk canal.zookeeper.flush.period = 1000 canal.withoutNetty = false @@ -64,32 +64,32 @@ canal.instance.parser.parallel = true canal.instance.parser.parallelBufferSize = 256 # table meta tsdb info -canal.instance.tsdb.enable=true -canal.instance.tsdb.dir=${canal.file.data.dir:../conf}/${canal.instance.destination:} -canal.instance.tsdb.url=jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; -canal.instance.tsdb.dbUsername=canal -canal.instance.tsdb.dbPassword=canal +canal.instance.tsdb.enable = true +canal.instance.tsdb.dir = ${canal.file.data.dir:../conf}/${canal.instance.destination:} +canal.instance.tsdb.url = jdbc:h2:${canal.instance.tsdb.dir}/h2;CACHE_SIZE=1000;MODE=MYSQL; +canal.instance.tsdb.dbUsername = canal +canal.instance.tsdb.dbPassword = canal # dump snapshot interval, default 24 hour -canal.instance.tsdb.snapshot.interval=24 +canal.instance.tsdb.snapshot.interval = 24 # purge snapshot expire , default 360 hour(15 days) -canal.instance.tsdb.snapshot.expire=360 +canal.instance.tsdb.snapshot.expire = 360 # aliyun ak/sk , support rds/mq -canal.aliyun.accesskey= -canal.aliyun.secretkey= +canal.aliyun.accesskey = +canal.aliyun.secretkey = ################################################# ######### destinations ############# ################################################# -canal.destinations= example +canal.destinations = example # conf root dir canal.conf.dir = ../conf # auto scan instance dir add/remove and start/stop instance canal.auto.scan = true canal.auto.scan.interval = 5 -canal.instance.tsdb.spring.xml=classpath:spring/tsdb/h2-tsdb.xml -#canal.instance.tsdb.spring.xml=classpath:spring/tsdb/mysql-tsdb.xml +canal.instance.tsdb.spring.xml = classpath:spring/tsdb/h2-tsdb.xml +#canal.instance.tsdb.spring.xml = classpath:spring/tsdb/mysql-tsdb.xml canal.instance.global.mode = spring canal.instance.global.lazy = false @@ -101,13 +101,13 @@ canal.instance.global.spring.xml = classpath:spring/file-instance.xml ################################################## ######### MQ ############# ################################################## -canal.mq.servers=127.0.0.1:6667 -canal.mq.retries=0 -canal.mq.batchSize=16384 -canal.mq.lingerMs=1 -canal.mq.bufferMemory=33554432 -canal.mq.canalBatchSize=50 -canal.mq.canalGetTimeout=100 -canal.mq.flatMessage=true -canal.mq.compressionType=none -canal.mq.acks=all \ No newline at end of file +canal.mq.servers = 127.0.0.1:6667 +canal.mq.retries = 0 +canal.mq.batchSize = 16384 +canal.mq.lingerMs = 1 +canal.mq.bufferMemory = 33554432 +canal.mq.canalBatchSize = 50 +canal.mq.canalGetTimeout = 100 +canal.mq.flatMessage = true +canal.mq.compressionType = none +canal.mq.acks = all From e711f1a09195d9232601048b6048287822d4e2a2 Mon Sep 17 00:00:00 2001 From: mcy Date: Wed, 21 Nov 2018 16:57:02 +0800 Subject: [PATCH 348/360] =?UTF-8?q?rdb=20=E5=B9=B6=E8=A1=8C=E6=8F=90?= =?UTF-8?q?=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../canal/client/adapter/rdb/RdbAdapter.java | 10 +- .../adapter/rdb/service/RdbSyncService.java | 249 ++++++++++++------ .../client/adapter/rdb/support/SingleDml.java | 83 ++++++ 3 files changed, 264 insertions(+), 78 deletions(-) create mode 100644 client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SingleDml.java diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java index cf99615f..ae38b124 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/RdbAdapter.java @@ -89,10 +89,12 @@ public class RdbAdapter implements OuterAdapter { logger.error("ERROR ## failed to initial datasource: " + properties.get("jdbc.url"), e); } - // String threads = properties.get("threads"); + String threads = properties.get("threads"); // String commitSize = properties.get("commitSize"); - rdbSyncService = new RdbSyncService(mappingConfigCache, dataSource); + rdbSyncService = new RdbSyncService(mappingConfigCache, + dataSource, + threads != null ? Integer.valueOf(threads) : null); rdbConfigMonitor = new RdbConfigMonitor(); rdbConfigMonitor.init(configuration.getKey(), this); @@ -201,6 +203,10 @@ public class RdbAdapter implements OuterAdapter { rdbConfigMonitor.destroy(); } + if (rdbSyncService != null) { + rdbSyncService.close(); + } + executor.shutdown(); if (dataSource != null) { diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java index a23259a0..02e87cdf 100644 --- a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/service/RdbSyncService.java @@ -8,6 +8,9 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; import javax.sql.DataSource; @@ -20,6 +23,7 @@ import com.alibaba.fastjson.serializer.SerializerFeature; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig; import com.alibaba.otter.canal.client.adapter.rdb.config.MappingConfig.DbMapping; import com.alibaba.otter.canal.client.adapter.rdb.support.BatchExecutor; +import com.alibaba.otter.canal.client.adapter.rdb.support.SingleDml; import com.alibaba.otter.canal.client.adapter.rdb.support.SyncUtil; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.Util; @@ -38,15 +42,35 @@ public class RdbSyncService { private Map> mappingConfigCache; // 库名-表名对应配置 - private DataSource dataSource; + private int threads = 3; - public RdbSyncService(Map> mappingConfigCache, DataSource dataSource){ - this.mappingConfigCache = mappingConfigCache; - this.dataSource = dataSource; + private List[] dmlsPartition; + private BatchExecutor[] batchExecutors; + private ExecutorService[] executorThreads; + + @SuppressWarnings("unchecked") + public RdbSyncService(Map> mappingConfigCache, DataSource dataSource, + Integer threads){ + try { + if (threads != null) { + this.threads = threads; + } + this.mappingConfigCache = mappingConfigCache; + this.dmlsPartition = new List[this.threads]; + this.batchExecutors = new BatchExecutor[this.threads]; + this.executorThreads = new ExecutorService[this.threads]; + for (int i = 0; i < this.threads; i++) { + dmlsPartition[i] = new ArrayList<>(); + batchExecutors[i] = new BatchExecutor(dataSource.getConnection()); + executorThreads[i] = Executors.newSingleThreadExecutor(); + } + } catch (SQLException e) { + logger.error(e.getMessage(), e); + } } public void sync(List dmls) { - try (BatchExecutor batchExecutor = new BatchExecutor(dataSource.getConnection())) { + try { for (Dml dml : dmls) { String destination = StringUtils.trimToEmpty(dml.getDestination()); String database = dml.getDatabase(); @@ -55,16 +79,51 @@ public class RdbSyncService { .get(destination + "." + database + "." + table); for (MappingConfig config : configMap.values()) { - sync(batchExecutor, config, dml); + + if (config.getConcurrent()) { + List singleDmls = SingleDml.dml2SingleDmls(dml); + singleDmls.forEach(singleDml -> { + int hash = pkHash(config.getDbMapping(), singleDml.getData()); + SyncItem syncItem = new SyncItem(config, singleDml); + dmlsPartition[hash].add(syncItem); + }); + } else { + int hash = Math.abs(Math.abs(config.getDbMapping().getTargetTable().hashCode()) % threads); + List singleDmls = SingleDml.dml2SingleDmls(dml); + singleDmls.forEach(singleDml -> { + SyncItem syncItem = new SyncItem(config, singleDml); + dmlsPartition[hash].add(syncItem); + }); + } } } - batchExecutor.commit(); - } catch (SQLException e) { - throw new RuntimeException(e); + List futures = new ArrayList<>(); + for (int i = 0; i < threads; i++) { + int j = i; + futures.add(executorThreads[i].submit(() -> { + dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j], syncItem.config, syncItem.singleDml)); + batchExecutors[j].commit(); + return true; + })); + } + + futures.forEach(future -> { + try { + future.get(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + }); + + for (int i = 0; i < threads; i++) { + dmlsPartition[i].clear(); + } + } catch (Exception e) { + logger.error(e.getMessage(), e); } } - private void sync(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { + private void sync(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) { try { if (config != null) { String type = dml.getType(); @@ -90,8 +149,8 @@ public class RdbSyncService { * @param config 配置项 * @param dml DML数据 */ - private void insert(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { - List> data = dml.getData(); + private void insert(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) { + Map data = dml.getData(); if (data == null || data.isEmpty()) { return; } @@ -99,7 +158,7 @@ public class RdbSyncService { DbMapping dbMapping = config.getDbMapping(); try { - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); StringBuilder insertSql = new StringBuilder(); insertSql.append("INSERT INTO ").append(dbMapping.getTargetTable()).append(" ("); @@ -116,28 +175,26 @@ public class RdbSyncService { Map ctype = getTargetColumnType(batchExecutor.getConn(), config); - for (Map d : data) { - List> values = new ArrayList<>(); - for (Map.Entry entry : columnsMap.entrySet()) { - String targetClolumnName = entry.getKey(); - String srcColumnName = entry.getValue(); - if (srcColumnName == null) { - srcColumnName = targetClolumnName; - } - - Integer type = ctype.get(targetClolumnName.toLowerCase()); - - Object value = d.get(srcColumnName); - - BatchExecutor.setValue(values, type, value); + List> values = new ArrayList<>(); + for (Map.Entry entry : columnsMap.entrySet()) { + String targetClolumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetClolumnName; } - batchExecutor.execute(insertSql.toString(), values); - if (logger.isTraceEnabled()) { - logger.trace("Insert into target table, sql: {}", insertSql); - } + Integer type = ctype.get(targetClolumnName.toLowerCase()); + Object value = data.get(srcColumnName); + + BatchExecutor.setValue(values, type, value); } + + batchExecutor.execute(insertSql.toString(), values); + if (logger.isTraceEnabled()) { + logger.trace("Insert into target table, sql: {}", insertSql); + } + } catch (Exception e) { logger.error(e.getMessage(), e); } @@ -149,59 +206,53 @@ public class RdbSyncService { * @param config 配置项 * @param dml DML数据 */ - private void update(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { - List> data = dml.getData(); + private void update(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) { + Map data = dml.getData(); if (data == null || data.isEmpty()) { return; } - List> old = dml.getOld(); + Map old = dml.getOld(); if (old == null || old.isEmpty()) { return; } DbMapping dbMapping = config.getDbMapping(); - int idx = 1; - try { - Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data.get(0)); + Map columnsMap = SyncUtil.getColumnsMap(dbMapping, data); Map ctype = getTargetColumnType(batchExecutor.getConn(), config); - for (Map o : old) { - Map d = data.get(idx - 1); - StringBuilder updateSql = new StringBuilder(); - updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); - List> values = new ArrayList<>(); - for (String srcColumnName : o.keySet()) { - List targetColumnNames = new ArrayList<>(); - columnsMap.forEach((targetColumn, srcColumn) -> { - if (srcColumnName.toLowerCase().equals(srcColumn)) { - targetColumnNames.add(targetColumn); - } - }); - if (!targetColumnNames.isEmpty()) { + StringBuilder updateSql = new StringBuilder(); + updateSql.append("UPDATE ").append(dbMapping.getTargetTable()).append(" SET "); + List> values = new ArrayList<>(); + for (String srcColumnName : old.keySet()) { + List targetColumnNames = new ArrayList<>(); + columnsMap.forEach((targetColumn, srcColumn) -> { + if (srcColumnName.toLowerCase().equals(srcColumn)) { + targetColumnNames.add(targetColumn); + } + }); + if (!targetColumnNames.isEmpty()) { - for (String targetColumnName : targetColumnNames) { - updateSql.append(targetColumnName).append("=?, "); - Integer type = ctype.get(targetColumnName.toLowerCase()); - BatchExecutor.setValue(values, type, d.get(srcColumnName)); - } + for (String targetColumnName : targetColumnNames) { + updateSql.append(targetColumnName).append("=?, "); + Integer type = ctype.get(targetColumnName.toLowerCase()); + BatchExecutor.setValue(values, type, data.get(srcColumnName)); } } - int len = updateSql.length(); - updateSql.delete(len - 2, len).append(" WHERE "); + } + int len = updateSql.length(); + updateSql.delete(len - 2, len).append(" WHERE "); - // 拼接主键 - appendCondition(dbMapping, updateSql, ctype, values, d, o); + // 拼接主键 + appendCondition(dbMapping, updateSql, ctype, values, data, old); - batchExecutor.execute(updateSql.toString(), values); + batchExecutor.execute(updateSql.toString(), values); - if (logger.isTraceEnabled()) { - logger.trace("Update target table, sql: {}", updateSql); - } - idx++; + if (logger.isTraceEnabled()) { + logger.trace("Update target table, sql: {}", updateSql); } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -214,8 +265,8 @@ public class RdbSyncService { * @param config * @param dml */ - private void delete(BatchExecutor batchExecutor, MappingConfig config, Dml dml) { - List> data = dml.getData(); + private void delete(BatchExecutor batchExecutor, MappingConfig config, SingleDml dml) { + Map data = dml.getData(); if (data == null || data.isEmpty()) { return; } @@ -225,19 +276,17 @@ public class RdbSyncService { try { Map ctype = getTargetColumnType(batchExecutor.getConn(), config); - for (Map d : data) { - StringBuilder sql = new StringBuilder(); - sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); + StringBuilder sql = new StringBuilder(); + sql.append("DELETE FROM ").append(dbMapping.getTargetTable()).append(" WHERE "); - List> values = new ArrayList<>(); - // 拼接主键 - appendCondition(dbMapping, sql, ctype, values, d); + List> values = new ArrayList<>(); + // 拼接主键 + appendCondition(dbMapping, sql, ctype, values, data); - batchExecutor.execute(sql.toString(), values); + batchExecutor.execute(sql.toString(), values); - if (logger.isTraceEnabled()) { - logger.trace("Delete from target table, sql: {}", sql); - } + if (logger.isTraceEnabled()) { + logger.trace("Delete from target table, sql: {}", sql); } } catch (Exception e) { logger.error(e.getMessage(), e); @@ -309,4 +358,52 @@ public class RdbSyncService { int len = sql.length(); sql.delete(len - 4, len); } + + private class SyncItem { + + private MappingConfig config; + private SingleDml singleDml; + + private SyncItem(MappingConfig config, SingleDml singleDml){ + this.config = config; + this.singleDml = singleDml; + } + } + + /** + * 取主键hash + */ + private int pkHash(DbMapping dbMapping, Map d) { + return pkHash(dbMapping, d, null); + } + + private int pkHash(DbMapping dbMapping, Map d, Map o) { + int hash = 0; + // 取主键 + for (Map.Entry entry : dbMapping.getTargetPk().entrySet()) { + String targetColumnName = entry.getKey(); + String srcColumnName = entry.getValue(); + if (srcColumnName == null) { + srcColumnName = targetColumnName; + } + Object value; + if (o != null && o.containsKey(srcColumnName)) { + value = o.get(srcColumnName); + } else { + value = d.get(srcColumnName); + } + if (value != null) { + hash += value.hashCode(); + } + } + hash = Math.abs(hash) % threads; + return Math.abs(hash); + } + + public void close() { + for (int i = 0; i < threads; i++) { + batchExecutors[i].close(); + executorThreads[i].shutdown(); + } + } } diff --git a/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SingleDml.java b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SingleDml.java new file mode 100644 index 00000000..6e61a15f --- /dev/null +++ b/client-adapter/rdb/src/main/java/com/alibaba/otter/canal/client/adapter/rdb/support/SingleDml.java @@ -0,0 +1,83 @@ +package com.alibaba.otter.canal.client.adapter.rdb.support; + +import com.alibaba.otter.canal.client.adapter.support.Dml; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class SingleDml { + + private String destination; + private String database; + private String table; + private String type; + private Map data; + private Map old; + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getTable() { + return table; + } + + public void setTable(String table) { + this.table = table; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + public Map getOld() { + return old; + } + + public void setOld(Map old) { + this.old = old; + } + + public static List dml2SingleDmls(Dml dml) { + int size = dml.getData().size(); + List singleDmls = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + SingleDml singleDml = new SingleDml(); + singleDml.setDestination(dml.getDestination()); + singleDml.setDatabase(dml.getDatabase()); + singleDml.setTable(dml.getTable()); + singleDml.setType(dml.getType()); + singleDml.setData(dml.getData().get(i)); + if (dml.getOld() != null) { + singleDml.setOld(dml.getOld().get(i)); + } + singleDmls.add(singleDml); + } + return singleDmls; + } +} From aa89453bf66b18102a96b95755731ad6ab13eaec Mon Sep 17 00:00:00 2001 From: agapple Date: Wed, 21 Nov 2018 22:49:22 +0800 Subject: [PATCH 349/360] fixed adapter --- .../adapter/support/CanalClientConfig.java | 56 ++++++++----------- .../launcher/config/AdapterCanalConfig.java | 2 - .../loader/AbstractCanalAdapterWorker.java | 32 +++++++---- .../launcher/loader/CanalAdapterLoader.java | 16 +++--- .../src/main/resources/application.yml | 5 +- 5 files changed, 53 insertions(+), 58 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 8e1b5596..672d83ec 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -12,25 +12,26 @@ import java.util.Map; */ public class CanalClientConfig { - private String canalServerHost; // 单机模式下canal server的 ip:port - - private String zookeeperHosts; // 集群模式下的zk地址, 如果配置了单机地址则以单机为准!! - - private String mqServers; // kafka or rocket mq 地址 - - private Boolean flatMessage = true; // 是否已flatMessage模式传输, 只适用于mq模式 - - private Integer batchSize; // 批大小 - - private Integer syncBatchSize = 1000; // 同步分批提交大小 - - private Integer retries; // 重试次数 - - private Long timeout; // 消费超时时间 - - private String mode = "tcp"; // 模式 tcp kafka rocketMQ - - private List canalAdapters; // canal adapters 配置 + // 单机模式下canal server的ip:port + private String canalServerHost; + // 集群模式下的zk地址,如果配置了单机地址则以单机为准!! + private String zookeeperHosts; + // kafka or rocket mq 地址 + private String mqServers; + // 是否已flatMessage模式传输,只适用于mq模式 + private Boolean flatMessage = true; + // 批大小 + private Integer batchSize; + // 同步分批提交大小 + private Integer syncBatchSize = 1000; + // 重试次数 + private Integer retries; + // 消费超时时间 + private Long timeout; + // 模式 tcp kafka rocketMQ + private String mode = "tcp"; + // canal adapters 配置 + private List canalAdapters; public String getCanalServerHost() { return canalServerHost; @@ -116,9 +117,7 @@ public class CanalClientConfig { private String instance; // 实例名 - private String topic; // mq topic - - private List groups; // 适配器分组列表 + private List groups; // 适配器分组列表 public String getInstance() { return instance; @@ -137,22 +136,13 @@ public class CanalClientConfig { public void setGroups(List groups) { this.groups = groups; } - - public String getTopic() { - return topic; - } - - public void setTopic(String topic) { - this.topic = topic; - } } public static class Group { - private String groupId; // group id - + // group id + private String groupId = "default"; private List outerAdapters; // 适配器列表 - private Map outerAdaptersMap = new LinkedHashMap<>(); public String getGroupId() { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index de27296e..2b9bb243 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -37,8 +37,6 @@ public class AdapterCanalConfig extends CanalClientConfig { for (CanalAdapter canalAdapter : canalAdapters) { if (canalAdapter.getInstance() != null) { DESTINATIONS.add(canalAdapter.getInstance()); - } else if (canalAdapter.getTopic() != null) { - DESTINATIONS.add(canalAdapter.getInstance()); } } } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java index 3702fd1e..db87bf01 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/AbstractCanalAdapterWorker.java @@ -2,9 +2,12 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; -import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -12,6 +15,7 @@ import com.alibaba.otter.canal.adapter.launcher.common.SyncSwitch; import com.alibaba.otter.canal.adapter.launcher.config.SpringContext; import com.alibaba.otter.canal.client.CanalMQConnector; import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import com.alibaba.otter.canal.client.adapter.support.Dml; import com.alibaba.otter.canal.client.adapter.support.MessageUtil; import com.alibaba.otter.canal.protocol.FlatMessage; @@ -180,18 +184,22 @@ public abstract class AbstractCanalAdapterWorker { */ private void batchSync(List dmls, OuterAdapter adapter) { // 分批同步 - int len = 0; - List dmlsBatch = new ArrayList<>(); - for (Dml dml : dmls) { - dmlsBatch.add(dml); - len += dml.getData().size(); - if (len >= canalClientConfig.getSyncBatchSize()) { - adapter.sync(dmlsBatch); - dmlsBatch.clear(); - len = 0; + if (dmls.size() <= canalClientConfig.getSyncBatchSize()) { + adapter.sync(dmls); + } else { + int len = 0; + List dmlsBatch = new ArrayList<>(); + for (Dml dml : dmls) { + dmlsBatch.add(dml); + len += dml.getData().size(); + if (len >= canalClientConfig.getSyncBatchSize()) { + adapter.sync(dmlsBatch); + dmlsBatch.clear(); + len = 0; + } } + adapter.sync(dmlsBatch); } - adapter.sync(dmlsBatch); } public void start() { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index a72197fd..162cdc11 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -97,14 +97,14 @@ public class CanalAdapterLoader { CanalAdapterKafkaWorker canalKafkaWorker = new CanalAdapterKafkaWorker(canalClientConfig, canalClientConfig.getMqServers(), - canalAdapter.getTopic(), + canalAdapter.getInstance(), group.getGroupId(), canalOuterAdapterGroups, canalClientConfig.getFlatMessage()); - canalMQWorker.put(canalAdapter.getTopic() + "-kafka-" + group.getGroupId(), canalKafkaWorker); + canalMQWorker.put(canalAdapter.getInstance() + "-kafka-" + group.getGroupId(), canalKafkaWorker); canalKafkaWorker.start(); - logger.info("Start adapter for canal-client mq topic: {} succeed", - canalAdapter.getTopic() + "-" + group.getGroupId()); + logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-" + + group.getGroupId()); } } } else if ("rocketMQ".equalsIgnoreCase(canalClientConfig.getMode())) { @@ -119,15 +119,15 @@ public class CanalAdapterLoader { canalOuterAdapterGroups.add(canalOuterAdapters); CanalAdapterRocketMQWorker rocketMQWorker = new CanalAdapterRocketMQWorker(canalClientConfig, canalClientConfig.getMqServers(), - canalAdapter.getTopic(), + canalAdapter.getInstance(), group.getGroupId(), canalOuterAdapterGroups, canalClientConfig.getFlatMessage()); - canalMQWorker.put(canalAdapter.getTopic() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); + canalMQWorker.put(canalAdapter.getInstance() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); rocketMQWorker.start(); - logger.info("Start adapter for canal-client mq topic: {} succeed", - canalAdapter.getTopic() + "-" + group.getGroupId()); + logger.info("Start adapter for canal-client mq topic: {} succeed", canalAdapter.getInstance() + "-" + + group.getGroupId()); } } } diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 4d120c6a..5495a4f8 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -28,10 +28,9 @@ canal.conf: # username: root # password: 121212 canalAdapters: - - instance: example -# topic: example + - instance: example # canal instance Name or mq topic name groups: - - #groupId: g1 + - groupId: g1 outerAdapters: - name: logger # - name: rdb From ec235f3efd395d43b0fc84ecf27234400c6073ca Mon Sep 17 00:00:00 2001 From: agapple Date: Fri, 23 Nov 2018 09:56:55 +0800 Subject: [PATCH 350/360] fixed rocketmq ak/sk --- .../adapter/support/CanalClientConfig.java | 20 ++++++++ .../adapter/support/ExtensionLoader.java | 46 +++++++++---------- .../launcher/CanalAdapterApplication.java | 1 - .../adapter/launcher/common/SyncSwitch.java | 3 +- .../launcher/config/AdapterCanalConfig.java | 1 + .../loader/CanalAdapterKafkaWorker.java | 3 +- .../launcher/loader/CanalAdapterLoader.java | 2 + .../loader/CanalAdapterRocketMQWorker.java | 14 ++---- .../src/main/resources/application.yml | 2 + 9 files changed, 56 insertions(+), 36 deletions(-) diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java index 672d83ec..3574b678 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/CanalClientConfig.java @@ -30,6 +30,10 @@ public class CanalClientConfig { private Long timeout; // 模式 tcp kafka rocketMQ private String mode = "tcp"; + // aliyun ak/sk + private String accessKey; + private String secretKey; + // canal adapters 配置 private List canalAdapters; @@ -105,6 +109,22 @@ public class CanalClientConfig { this.mode = mode; } + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + public List getCanalAdapters() { return canalAdapters; } diff --git a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java index 4e735c02..41d1864e 100644 --- a/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java +++ b/client-adapter/common/src/main/java/com/alibaba/otter/canal/client/adapter/support/ExtensionLoader.java @@ -27,8 +27,7 @@ import org.slf4j.LoggerFactory; */ public class ExtensionLoader { - private static final Logger logger = LoggerFactory - .getLogger(ExtensionLoader.class); + private static final Logger logger = LoggerFactory.getLogger(ExtensionLoader.class); private static final String SERVICES_DIRECTORY = "META-INF/services/"; @@ -36,8 +35,7 @@ public class ExtensionLoader { private static final String DEFAULT_CLASSLOADER_POLICY = "internal"; - private static final Pattern NAME_SEPARATOR = Pattern - .compile("\\s*[,]+\\s*"); + private static final Pattern NAME_SEPARATOR = Pattern.compile("\\s*[,]+\\s*"); private static final ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS = new ConcurrentHashMap<>(); @@ -173,8 +171,7 @@ public class ExtensionLoader { return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type - + ") could not be instantiated: " + t.getMessage(), - t); + + ") could not be instantiated: " + t.getMessage(), t); } } @@ -194,8 +191,7 @@ public class ExtensionLoader { return instance; } catch (Throwable t) { throw new IllegalStateException("Extension instance(name: " + name + ", class: " + type - + ") could not be instantiated: " + t.getMessage(), - t); + + ") could not be instantiated: " + t.getMessage(), t); } } @@ -334,10 +330,12 @@ public class ExtensionLoader { // Class.forName(line, true, // classLoader); if (!type.isAssignableFrom(clazz)) { - throw new IllegalStateException( - "Error when load extension class(interface: " + type - + ", class line: " + clazz.getName() - + "), class " + clazz.getName() + throw new IllegalStateException("Error when load extension class(interface: " + + type + + ", class line: " + + clazz.getName() + + "), class " + + clazz.getName() + "is not subtype of interface."); } else { try { @@ -355,9 +353,9 @@ public class ExtensionLoader { extensionClasses.put(n, clazz); } else if (c != clazz) { cachedNames.remove(clazz); - throw new IllegalStateException( - "Duplicate extension " + type.getName() + " name " - + n + " on " + throw new IllegalStateException("Duplicate extension " + + type.getName() + + " name " + n + " on " + c.getName() + " and " + clazz.getName()); } @@ -367,9 +365,12 @@ public class ExtensionLoader { } } } catch (Throwable t) { - IllegalStateException e = new IllegalStateException( - "Failed to load extension class(interface: " + type + ", class line: " - + line + ") in " + url + IllegalStateException e = new IllegalStateException("Failed to load extension class(interface: " + + type + + ", class line: " + + line + + ") in " + + url + ", cause: " + t.getMessage(), t); @@ -384,18 +385,17 @@ public class ExtensionLoader { } } catch (Throwable t) { logger.error("Exception when load extension class(interface: " + type + ", class file: " + url - + ") in " + url, - t); + + ") in " + url, t); } } // end of while urls } } catch (Throwable t) { - logger.error( - "Exception when load extension class(interface: " + type + ", description file: " + fileName + ").", - t); + logger.error("Exception when load extension class(interface: " + type + ", description file: " + fileName + + ").", t); } } + @SuppressWarnings("unused") private static ClassLoader findClassLoader() { return ExtensionLoader.class.getClassLoader(); } diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java index 83f4b350..3d2ef585 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/CanalAdapterApplication.java @@ -3,7 +3,6 @@ package com.alibaba.otter.canal.adapter.launcher; import org.springframework.boot.Banner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; /** * 启动入口 diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java index aa6e55bc..c0e5d394 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/common/SyncSwitch.java @@ -64,11 +64,12 @@ public class SyncSwitch { } } + @SuppressWarnings("resource") private synchronized void startListen(String destination, BooleanMutex mutex) { try { String path = SYN_SWITCH_ZK_NODE + destination; CuratorFramework curator = curatorClient.getCurator(); - final NodeCache nodeCache = new NodeCache(curator, path); + NodeCache nodeCache = new NodeCache(curator, path); nodeCache.start(); nodeCache.getListenable().addListener(() -> initMutex(curator, destination, mutex)); } catch (Exception e) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java index 2b9bb243..3ad0d5f7 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/config/AdapterCanalConfig.java @@ -47,6 +47,7 @@ public class AdapterCanalConfig extends CanalClientConfig { return srcDataSources; } + @SuppressWarnings("resource") public void setSrcDataSources(Map srcDataSources) { this.srcDataSources = srcDataSources; diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java index 631154c2..050bbf65 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterKafkaWorker.java @@ -44,8 +44,7 @@ public class CanalAdapterKafkaWorker extends AbstractCanalAdapterWorker { while (!running) ; ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetries() == null - || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries(); + int retry = canalClientConfig.getRetries() == null || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒 while (running) { diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java index 162cdc11..80d5371a 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterLoader.java @@ -122,6 +122,8 @@ public class CanalAdapterLoader { canalAdapter.getInstance(), group.getGroupId(), canalOuterAdapterGroups, + canalClientConfig.getAccessKey(), + canalClientConfig.getSecretKey(), canalClientConfig.getFlatMessage()); canalMQWorker.put(canalAdapter.getInstance() + "-rocketmq-" + group.getGroupId(), rocketMQWorker); rocketMQWorker.start(); diff --git a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java index 7f2e3b56..2e737353 100644 --- a/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java +++ b/client-adapter/launcher/src/main/java/com/alibaba/otter/canal/adapter/launcher/loader/CanalAdapterRocketMQWorker.java @@ -3,16 +3,12 @@ package com.alibaba.otter.canal.adapter.launcher.loader; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import org.apache.kafka.common.errors.WakeupException; import com.alibaba.otter.canal.client.adapter.OuterAdapter; +import com.alibaba.otter.canal.client.adapter.support.CanalClientConfig; import com.alibaba.otter.canal.client.rocketmq.RocketMQCanalConnector; -import com.alibaba.otter.canal.protocol.FlatMessage; -import com.alibaba.otter.canal.protocol.Message; /** * rocketmq对应的client适配器工作线程 @@ -26,13 +22,14 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { private boolean flatMessage; public CanalAdapterRocketMQWorker(CanalClientConfig canalClientConfig, String nameServers, String topic, - String groupId, List> canalOuterAdapters, boolean flatMessage){ + String groupId, List> canalOuterAdapters, String accessKey, + String secretKey, boolean flatMessage){ super(canalOuterAdapters); this.canalClientConfig = canalClientConfig; this.topic = topic; this.flatMessage = flatMessage; this.canalDestination = topic; - this.connector = new RocketMQCanalConnector(nameServers, topic, groupId, flatMessage); + this.connector = new RocketMQCanalConnector(nameServers, topic, groupId, accessKey, secretKey, flatMessage); logger.info("RocketMQ consumer config topic:{}, nameServer:{}, groupId:{}", topic, nameServers, groupId); } @@ -42,8 +39,7 @@ public class CanalAdapterRocketMQWorker extends AbstractCanalAdapterWorker { ; ExecutorService workerExecutor = Executors.newSingleThreadExecutor(); - int retry = canalClientConfig.getRetries() == null - || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries(); + int retry = canalClientConfig.getRetries() == null || canalClientConfig.getRetries() == 0 ? 1 : canalClientConfig.getRetries(); long timeout = canalClientConfig.getTimeout() == null ? 30000 : canalClientConfig.getTimeout(); // 默认超时30秒 while (running) { diff --git a/client-adapter/launcher/src/main/resources/application.yml b/client-adapter/launcher/src/main/resources/application.yml index 5495a4f8..098bc1f3 100644 --- a/client-adapter/launcher/src/main/resources/application.yml +++ b/client-adapter/launcher/src/main/resources/application.yml @@ -21,6 +21,8 @@ canal.conf: syncBatchSize: 1000 retries: 0 timeout: + accessKey: + secretKey: mode: tcp # kafka rocketMQ # srcDataSources: # defaultDS: From 29f03bf398ae0dcaf4a2ce26a243332192c05443 Mon Sep 17 00:00:00 2001 From: agapple Date: Sat, 24 Nov 2018 15:34:39 +0800 Subject: [PATCH 351/360] fixed batchSize calc --- .../main/java/com/alibaba/otter/canal/store/model/Event.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java b/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java index edb4dd24..db1618e1 100644 --- a/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java +++ b/store/src/main/java/com/alibaba/otter/canal/store/model/Event.java @@ -71,8 +71,8 @@ public class Event implements Serializable { this.rawLength = rawEntry.size(); } else { this.entry = entry; - // 按照3倍的event length预估 - this.rawLength = entry.getHeader().getEventLength() * 3; + // 按照6倍的event length预估 + this.rawLength = entry.getHeader().getEventLength() * 6; } } From 839d5415fc47a6090b2c7cc22ecec04f8c93ca65 Mon Sep 17 00:00:00 2001 From: gl Date: Sat, 24 Nov 2018 22:48:54 +0800 Subject: [PATCH 352/360] add max.request.size config --- .../com/alibaba/otter/canal/deployer/CanalConstants.java | 1 + .../com/alibaba/otter/canal/deployer/CanalLauncher.java | 4 ++++ deployer/src/main/resources/canal.properties | 1 + .../java/com/alibaba/otter/canal/common/MQProperties.java | 8 ++++++++ .../com/alibaba/otter/canal/kafka/CanalKafkaProducer.java | 1 + 5 files changed, 15 insertions(+) diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java index d21348bf..ba39bde0 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalConstants.java @@ -41,6 +41,7 @@ public class CanalConstants { public static final String CANAL_MQ_RETRIES = ROOT + "." + "mq.retries"; public static final String CANAL_MQ_BATCHSIZE = ROOT + "." + "mq.batchSize"; public static final String CANAL_MQ_LINGERMS = ROOT + "." + "mq.lingerMs"; + public static final String CANAL_MQ_MAXREQUESTSIZE = ROOT + "." + "mq.maxRequestSize"; public static final String CANAL_MQ_BUFFERMEMORY = ROOT + "." + "mq.bufferMemory"; public static final String CANAL_MQ_CANALBATCHSIZE = ROOT + "." + "mq.canalBatchSize"; public static final String CANAL_MQ_CANALGETTIMEOUT = ROOT + "." + "mq.canalGetTimeout"; diff --git a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java index 24d0ec5b..aa7f53b0 100644 --- a/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java +++ b/deployer/src/main/java/com/alibaba/otter/canal/deployer/CanalLauncher.java @@ -101,6 +101,10 @@ public class CanalLauncher { if (!StringUtils.isEmpty(lingerMs)) { mqProperties.setLingerMs(Integer.valueOf(lingerMs)); } + String maxRequestSize = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_MAXREQUESTSIZE); + if (!StringUtils.isEmpty(maxRequestSize)) { + mqProperties.setMaxRequestSize(Integer.valueOf(maxRequestSize)); + } String bufferMemory = CanalController.getProperty(properties, CanalConstants.CANAL_MQ_BUFFERMEMORY); if (!StringUtils.isEmpty(bufferMemory)) { mqProperties.setBufferMemory(Long.valueOf(bufferMemory)); diff --git a/deployer/src/main/resources/canal.properties b/deployer/src/main/resources/canal.properties index 78343b43..5d7b9d06 100644 --- a/deployer/src/main/resources/canal.properties +++ b/deployer/src/main/resources/canal.properties @@ -104,6 +104,7 @@ canal.instance.global.spring.xml = classpath:spring/file-instance.xml canal.mq.servers = 127.0.0.1:6667 canal.mq.retries = 0 canal.mq.batchSize = 16384 +canal.mq.maxRequestSize = 1048576 canal.mq.lingerMs = 1 canal.mq.bufferMemory = 33554432 canal.mq.canalBatchSize = 50 diff --git a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java index cf72a2dd..a337ccc6 100644 --- a/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java +++ b/server/src/main/java/com/alibaba/otter/canal/common/MQProperties.java @@ -14,6 +14,7 @@ public class MQProperties { private int retries = 0; private int batchSize = 16384; private int lingerMs = 1; + private int maxRequestSize = 1048576; private long bufferMemory = 33554432L; private boolean filterTransactionEntry = true; private String producerGroup = "Canal-Producer"; @@ -185,4 +186,11 @@ public class MQProperties { public void setAliyunSecretKey(String aliyunSecretKey) { this.aliyunSecretKey = aliyunSecretKey; } + public int getMaxRequestSize() { + return maxRequestSize; + } + + public void setMaxRequestSize(int maxRequestSize) { + this.maxRequestSize = maxRequestSize; + } } diff --git a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java index bf97de8e..230e9607 100644 --- a/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java +++ b/server/src/main/java/com/alibaba/otter/canal/kafka/CanalKafkaProducer.java @@ -43,6 +43,7 @@ public class CanalKafkaProducer implements CanalMQProducer { properties.put("retries", kafkaProperties.getRetries()); properties.put("batch.size", kafkaProperties.getBatchSize()); properties.put("linger.ms", kafkaProperties.getLingerMs()); + properties.put("max.request.size", kafkaProperties.getMaxRequestSize()); properties.put("buffer.memory", kafkaProperties.getBufferMemory()); properties.put("key.serializer", StringSerializer.class.getName()); if (!kafkaProperties.getFlatMessage()) { From 90dab6831b2fd858eec030ffc56ed60405d71d0f Mon Sep 17 00:00:00 2001 From: guixincui Date: Sat, 24 Nov 2018 23:40:18 +0800 Subject: [PATCH 353/360] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/alibaba/otter/canal/server/CanalMQStarter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java index ad4fc266..ade0adaf 100644 --- a/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java +++ b/server/src/main/java/com/alibaba/otter/canal/server/CanalMQStarter.java @@ -13,7 +13,6 @@ import org.slf4j.LoggerFactory; import com.alibaba.otter.canal.common.MQProperties; import com.alibaba.otter.canal.instance.core.CanalInstance; import com.alibaba.otter.canal.instance.core.CanalMQConfig; -import com.alibaba.otter.canal.kafka.CanalKafkaProducer; import com.alibaba.otter.canal.protocol.ClientIdentity; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.server.embedded.CanalServerWithEmbedded; @@ -146,7 +145,7 @@ public class CanalMQStarter { try { int size = message.isRaw() ? message.getRawEntries().size() : message.getEntries().size(); if (batchId != -1 && size != 0) { - canalMQProducer.send(destination, message, new CanalKafkaProducer.Callback() { + canalMQProducer.send(destination, message, new CanalMQProducer.Callback() { @Override public void commit() { From f7bf4ac6ff260377d7879a6fc1e05c596737827b Mon Sep 17 00:00:00 2001 From: agapple Date: Sun, 25 Nov 2018 18:33:34 +0800 Subject: [PATCH 354/360] fixed log test --- .../parse/inbound/mysql/AbstractMysqlEventParser.java | 4 ---- .../otter/canal/parse/DirectLogFetcherTest.java | 10 ++++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java index 5e94e40b..af792ec8 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/AbstractMysqlEventParser.java @@ -3,9 +3,6 @@ package com.alibaba.otter.canal.parse.inbound.mysql; import java.nio.charset.Charset; import java.util.concurrent.atomic.AtomicLong; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.alibaba.otter.canal.filter.CanalEventFilter; import com.alibaba.otter.canal.filter.aviater.AviaterRegexFilter; import com.alibaba.otter.canal.parse.CanalEventParser; @@ -21,7 +18,6 @@ import com.alibaba.otter.canal.protocol.position.EntryPosition; public abstract class AbstractMysqlEventParser extends AbstractEventParser { - protected final Logger logger = LoggerFactory.getLogger(this.getClass()); protected static final long BINLOG_START_OFFEST = 4L; protected TableMetaTSDBFactory tableMetaTSDBFactory = new DefaultTableMetaTSDBFactory(); diff --git a/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java b/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java index 719e86dd..089163d3 100644 --- a/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java +++ b/parse/src/test/java/com/alibaba/otter/canal/parse/DirectLogFetcherTest.java @@ -1,5 +1,7 @@ package com.alibaba.otter.canal.parse; +import static com.alibaba.otter.canal.parse.inbound.mysql.dbsync.DirectLogFetcher.MASTER_HEARTBEAT_PERIOD_SECONDS; + import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; @@ -7,6 +9,7 @@ import java.net.InetSocketAddress; import java.nio.charset.Charset; import java.util.BitSet; import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.junit.Assert; @@ -213,6 +216,13 @@ public class DirectLogFetcherTest { } catch (Exception e) { logger.warn("update mariadb_slave_capability failed", e); } + + try { + long period = TimeUnit.SECONDS.toNanos(MASTER_HEARTBEAT_PERIOD_SECONDS); + update("SET @master_heartbeat_period=" + period, connector); + } catch (Exception e) { + logger.warn("update master_heartbeat_period failed", e); + } } private void loadBinlogChecksum(MysqlConnector connector) { From cbab3fb561f6c22d65a00512e72d462b407cfded Mon Sep 17 00:00:00 2001 From: agapple Date: Sun, 25 Nov 2018 21:01:19 +0800 Subject: [PATCH 355/360] fixed issue #1168, support filter with compareTableMetaDbAndMemory --- .../inbound/mysql/tsdb/DatabaseTableMeta.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java index e9bdc2ad..033dcae5 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/tsdb/DatabaseTableMeta.java @@ -76,7 +76,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { private int snapshotInterval = 24; private int snapshotExpire = 360; private ScheduledFuture scheduleSnapshotFuture; - + public DatabaseTableMeta(){ } @@ -207,8 +207,8 @@ public class DatabaseTableMeta implements TableMetaTSDB { packet = connection.query("show full tables from `" + schema + "` where Table_type = 'BASE TABLE'"); List tables = new ArrayList(); for (String table : packet.getFieldValues()) { - if("BASE TABLE".equalsIgnoreCase(table)){ - continue; + if ("BASE TABLE".equalsIgnoreCase(table)) { + continue; } String fullName = schema + "." + table; if (blackFilter == null || !blackFilter.filter(fullName)) { @@ -310,11 +310,19 @@ public class DatabaseTableMeta implements TableMetaTSDB { boolean compareAll = true; for (Schema schema : tmpMemoryTableMeta.getRepository().getSchemas()) { for (String table : schema.showTables()) { - if (!compareTableMetaDbAndMemory(connection, tmpMemoryTableMeta, schema.getName(), table)) { - compareAll = false; + String fullName = schema + "." + table; + if (blackFilter == null || !blackFilter.filter(fullName)) { + if (filter == null || filter.filter(fullName)) { + // issue : https://github.com/alibaba/canal/issues/1168 + // 在生成snapshot时重新过滤一遍 + if (!compareTableMetaDbAndMemory(connection, tmpMemoryTableMeta, schema.getName(), table)) { + compareAll = false; + } + } } } } + if (compareAll) { Map content = new HashMap(); content.put("destination", destination); @@ -513,7 +521,7 @@ public class DatabaseTableMeta implements TableMetaTSDB { String sign = sourceField.isUnsigned() ? "unsigned" : "signed"; String sourceColumnType = StringUtils.removeEndIgnoreCase(sourceField.getColumnType(), sign).trim(); String targetColumnType = StringUtils.removeEndIgnoreCase(targetField.getColumnType(), sign).trim(); - + boolean columnTypeCompare = false; columnTypeCompare |= StringUtils.containsIgnoreCase(sourceColumnType, targetColumnType); columnTypeCompare |= StringUtils.containsIgnoreCase(targetColumnType, sourceColumnType); From 4e9e3f5abe243ea6a85fbb438fdf7c90adb6abf2 Mon Sep 17 00:00:00 2001 From: agapple Date: Sun, 25 Nov 2018 21:17:34 +0800 Subject: [PATCH 356/360] fixed issue #1192 , NPE test --- .../otter/canal/parse/inbound/mysql/MysqlEventParser.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java index 0528d19c..35e98f73 100644 --- a/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java +++ b/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlEventParser.java @@ -135,6 +135,10 @@ public class MysqlEventParser extends AbstractMysqlEventParser implements CanalE protected void afterDump(ErosaConnection connection) { super.afterDump(connection); + if (connection == null) { + throw new CanalParseException("illegal connection is null"); + } + if (!(connection instanceof MysqlConnection)) { throw new CanalParseException("Unsupported connection type : " + connection.getClass().getSimpleName()); } From cf23e0ffea37ba67aa022e9dff9d0247fd624910 Mon Sep 17 00:00:00 2001 From: agapple Date: Mon, 26 Nov 2018 13:11:24 +0800 Subject: [PATCH 357/360] fixed javadoc --- .../java/com/alibaba/otter/canal/common/utils/BooleanMutex.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java b/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java index 660181ce..3a2ee258 100644 --- a/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java +++ b/common/src/main/java/com/alibaba/otter/canal/common/utils/BooleanMutex.java @@ -5,7 +5,7 @@ import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.AbstractQueuedSynchronizer; /** - * 实现一个互斥实现,基于Cocurrent中的AbstractQueuedSynchronizer实现了自己的sync
      + * 实现一个互斥实现,基于Cocurrent中的AQS实现了自己的sync
      * 应用场景:系统初始化/授权控制,没权限时阻塞等待。有权限时所有线程都可以快速通过 * *
      
      From 8496ceaf155d7dc93b928fd3b6af9ccafa979e62 Mon Sep 17 00:00:00 2001
      From: agapple 
      Date: Mon, 26 Nov 2018 13:21:51 +0800
      Subject: [PATCH 358/360] [maven-release-plugin] prepare release canal-1.1.2
      
      ---
       client-adapter/common/pom.xml        |  4 ++--
       client-adapter/elasticsearch/pom.xml | 10 ++++------
       client-adapter/hbase/pom.xml         |  6 +++---
       client-adapter/launcher/pom.xml      |  7 +++----
       client-adapter/logger/pom.xml        |  2 +-
       client-adapter/pom.xml               |  2 +-
       client-adapter/rdb/pom.xml           | 10 ++++------
       client/pom.xml                       |  2 +-
       common/pom.xml                       |  2 +-
       dbsync/pom.xml                       |  2 +-
       deployer/pom.xml                     |  2 +-
       driver/pom.xml                       |  2 +-
       example/pom.xml                      |  2 +-
       filter/pom.xml                       |  2 +-
       instance/core/pom.xml                |  2 +-
       instance/manager/pom.xml             |  2 +-
       instance/pom.xml                     |  2 +-
       instance/spring/pom.xml              |  2 +-
       meta/pom.xml                         |  2 +-
       parse/pom.xml                        |  2 +-
       pom.xml                              |  4 +---
       prometheus/pom.xml                   |  4 ++--
       protocol/pom.xml                     |  2 +-
       server/pom.xml                       |  2 +-
       sink/pom.xml                         |  2 +-
       store/pom.xml                        |  2 +-
       26 files changed, 38 insertions(+), 45 deletions(-)
      
      diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml
      index e62fc386..61660f96 100644
      --- a/client-adapter/common/pom.xml
      +++ b/client-adapter/common/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2-SNAPSHOT
      +        1.1.2
           
           4.0.0
           com.alibaba.otter
      @@ -14,7 +14,7 @@
               
                   com.alibaba.otter
                   canal.protocol
      -            1.1.2-SNAPSHOT
      +            1.1.2
               
               
                   joda-time
      diff --git a/client-adapter/elasticsearch/pom.xml b/client-adapter/elasticsearch/pom.xml
      index 2d6c3def..e55c0d27 100644
      --- a/client-adapter/elasticsearch/pom.xml
      +++ b/client-adapter/elasticsearch/pom.xml
      @@ -1,11 +1,9 @@
       
      -
      +
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2-SNAPSHOT
      +        1.1.2
           
           4.0.0
           com.alibaba.otter
      @@ -81,9 +79,9 @@
                               
                               
                                   
      -                                
      +                                
                                           
      -                                        
      +                                        
                                           
                                       
                                   
      diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml
      index 10d08681..4c1098ad 100644
      --- a/client-adapter/hbase/pom.xml
      +++ b/client-adapter/hbase/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2-SNAPSHOT
      +        1.1.2
           
           4.0.0
           com.alibaba.otter
      @@ -72,9 +72,9 @@
                               
                               
                                   
      -                                
      +                                
                                           
      -                                        
      +                                        
                                           
                                       
                                   
      diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml
      index e1f557ce..0eac4d95 100644
      --- a/client-adapter/launcher/pom.xml
      +++ b/client-adapter/launcher/pom.xml
      @@ -1,10 +1,9 @@
       
      -
      +
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2-SNAPSHOT
      +        1.1.2
           
           4.0.0
           com.alibaba.otter
      @@ -33,7 +32,7 @@
               
                   com.alibaba.otter
                   canal.client
      -            1.1.2-SNAPSHOT
      +            1.1.2
               
               
                   org.yaml
      diff --git a/client-adapter/logger/pom.xml b/client-adapter/logger/pom.xml
      index bca319f1..4247083b 100644
      --- a/client-adapter/logger/pom.xml
      +++ b/client-adapter/logger/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2-SNAPSHOT
      +        1.1.2
           
           4.0.0
           com.alibaba.otter
      diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml
      index 2f12209c..0e2f1783 100644
      --- a/client-adapter/pom.xml
      +++ b/client-adapter/pom.xml
      @@ -3,7 +3,7 @@
           4.0.0
           com.alibaba.otter
           canal.client-adapter
      -    1.1.2-SNAPSHOT
      +    1.1.2
           pom
           canal client adapter module for otter ${project.version}
       
      diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml
      index 36881768..9569abbb 100644
      --- a/client-adapter/rdb/pom.xml
      +++ b/client-adapter/rdb/pom.xml
      @@ -1,11 +1,9 @@
       
      -
      +
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2-SNAPSHOT
      +        1.1.2
           
           4.0.0
           com.alibaba.otter
      @@ -84,9 +82,9 @@
                               
                               
                                   
      -                                
      +                                
                                           
      -                                        
      +                                        
                                           
                                       
                                   
      diff --git a/client/pom.xml b/client/pom.xml
      index 28ca8d27..0520f759 100644
      --- a/client/pom.xml
      +++ b/client/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/common/pom.xml b/common/pom.xml
      index 9a3c3b22..17adae48 100644
      --- a/common/pom.xml
      +++ b/common/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	canal.common
      diff --git a/dbsync/pom.xml b/dbsync/pom.xml
      index 9aa0fa68..cca7e744 100644
      --- a/dbsync/pom.xml
      +++ b/dbsync/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/deployer/pom.xml b/deployer/pom.xml
      index 25d0c6e3..d446c724 100644
      --- a/deployer/pom.xml
      +++ b/deployer/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/driver/pom.xml b/driver/pom.xml
      index b7db03fe..8864e940 100644
      --- a/driver/pom.xml
      +++ b/driver/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/example/pom.xml b/example/pom.xml
      index ae040df6..19fc9cf9 100644
      --- a/example/pom.xml
      +++ b/example/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/filter/pom.xml b/filter/pom.xml
      index e9a2f60c..1d5e1c10 100644
      --- a/filter/pom.xml
      +++ b/filter/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/core/pom.xml b/instance/core/pom.xml
      index c96d8ce5..30522ee3 100644
      --- a/instance/core/pom.xml
      +++ b/instance/core/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../../pom.xml
       	
       	canal.instance.core
      diff --git a/instance/manager/pom.xml b/instance/manager/pom.xml
      index 0710dbd1..fe6fd6fd 100644
      --- a/instance/manager/pom.xml
      +++ b/instance/manager/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/pom.xml b/instance/pom.xml
      index 3ccf7ebd..84d14fb3 100644
      --- a/instance/pom.xml
      +++ b/instance/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/spring/pom.xml b/instance/spring/pom.xml
      index 3d5526d4..b76ab819 100644
      --- a/instance/spring/pom.xml
      +++ b/instance/spring/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../../pom.xml
       	
       	com.alibaba.otter
      diff --git a/meta/pom.xml b/meta/pom.xml
      index 8683e8e7..df99f3aa 100644
      --- a/meta/pom.xml
      +++ b/meta/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/parse/pom.xml b/parse/pom.xml
      index d610838d..88d0c544 100644
      --- a/parse/pom.xml
      +++ b/parse/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	canal.parse
      diff --git a/pom.xml b/pom.xml
      index 92d8a530..f2c49c16 100644
      --- a/pom.xml
      +++ b/pom.xml
      @@ -4,7 +4,7 @@
           canal
           pom
           canal module for otter ${project.version}
      -    1.1.2-SNAPSHOT
      +    1.1.2
           https://github.com/alibaba/canal
           
               org.sonatype.oss
      @@ -381,7 +381,6 @@
                       
                   
                   
      -            
               
               src/main/java
               src/test/java
      diff --git a/prometheus/pom.xml b/prometheus/pom.xml
      index 0e0b2217..b735fb0b 100644
      --- a/prometheus/pom.xml
      +++ b/prometheus/pom.xml
      @@ -3,13 +3,13 @@
           
               canal
               com.alibaba.otter
      -        1.1.2-SNAPSHOT
      +        1.1.2
           
           4.0.0
       
           com.alibaba.otter
           canal.prometheus
      -    1.1.2-SNAPSHOT
      +    1.1.2
           canal prometheus module for otter ${project.version}
           
               
      diff --git a/protocol/pom.xml b/protocol/pom.xml
      index f1cdfa2f..058d0717 100644
      --- a/protocol/pom.xml
      +++ b/protocol/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/server/pom.xml b/server/pom.xml
      index fd1225f4..5876d1d8 100644
      --- a/server/pom.xml
      +++ b/server/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	canal.server
      diff --git a/sink/pom.xml b/sink/pom.xml
      index e4f9674d..e4c50111 100644
      --- a/sink/pom.xml
      +++ b/sink/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/store/pom.xml b/store/pom.xml
      index 531b39a4..3d4b948b 100644
      --- a/store/pom.xml
      +++ b/store/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2-SNAPSHOT
      +		1.1.2
       		../pom.xml
       	
       	com.alibaba.otter
      
      From 9aaf20fb5ef1632d00aaf0dac487ccf18b606906 Mon Sep 17 00:00:00 2001
      From: agapple 
      Date: Mon, 26 Nov 2018 13:22:04 +0800
      Subject: [PATCH 359/360] [maven-release-plugin] prepare for next development
       iteration
      
      ---
       client-adapter/common/pom.xml        | 4 ++--
       client-adapter/elasticsearch/pom.xml | 2 +-
       client-adapter/hbase/pom.xml         | 2 +-
       client-adapter/launcher/pom.xml      | 4 ++--
       client-adapter/logger/pom.xml        | 2 +-
       client-adapter/pom.xml               | 2 +-
       client-adapter/rdb/pom.xml           | 2 +-
       client/pom.xml                       | 2 +-
       common/pom.xml                       | 2 +-
       dbsync/pom.xml                       | 2 +-
       deployer/pom.xml                     | 2 +-
       driver/pom.xml                       | 2 +-
       example/pom.xml                      | 2 +-
       filter/pom.xml                       | 2 +-
       instance/core/pom.xml                | 2 +-
       instance/manager/pom.xml             | 2 +-
       instance/pom.xml                     | 2 +-
       instance/spring/pom.xml              | 2 +-
       meta/pom.xml                         | 2 +-
       parse/pom.xml                        | 2 +-
       pom.xml                              | 2 +-
       prometheus/pom.xml                   | 4 ++--
       protocol/pom.xml                     | 2 +-
       server/pom.xml                       | 2 +-
       sink/pom.xml                         | 2 +-
       store/pom.xml                        | 2 +-
       26 files changed, 29 insertions(+), 29 deletions(-)
      
      diff --git a/client-adapter/common/pom.xml b/client-adapter/common/pom.xml
      index 61660f96..8a6d75fa 100644
      --- a/client-adapter/common/pom.xml
      +++ b/client-adapter/common/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2
      +        1.1.3-SNAPSHOT
           
           4.0.0
           com.alibaba.otter
      @@ -14,7 +14,7 @@
               
                   com.alibaba.otter
                   canal.protocol
      -            1.1.2
      +            1.1.3-SNAPSHOT
               
               
                   joda-time
      diff --git a/client-adapter/elasticsearch/pom.xml b/client-adapter/elasticsearch/pom.xml
      index e55c0d27..35711014 100644
      --- a/client-adapter/elasticsearch/pom.xml
      +++ b/client-adapter/elasticsearch/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2
      +        1.1.3-SNAPSHOT
           
           4.0.0
           com.alibaba.otter
      diff --git a/client-adapter/hbase/pom.xml b/client-adapter/hbase/pom.xml
      index 4c1098ad..29ee8b33 100644
      --- a/client-adapter/hbase/pom.xml
      +++ b/client-adapter/hbase/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2
      +        1.1.3-SNAPSHOT
           
           4.0.0
           com.alibaba.otter
      diff --git a/client-adapter/launcher/pom.xml b/client-adapter/launcher/pom.xml
      index 0eac4d95..32c70028 100644
      --- a/client-adapter/launcher/pom.xml
      +++ b/client-adapter/launcher/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2
      +        1.1.3-SNAPSHOT
           
           4.0.0
           com.alibaba.otter
      @@ -32,7 +32,7 @@
               
                   com.alibaba.otter
                   canal.client
      -            1.1.2
      +            1.1.3-SNAPSHOT
               
               
                   org.yaml
      diff --git a/client-adapter/logger/pom.xml b/client-adapter/logger/pom.xml
      index 4247083b..11c8d53e 100644
      --- a/client-adapter/logger/pom.xml
      +++ b/client-adapter/logger/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2
      +        1.1.3-SNAPSHOT
           
           4.0.0
           com.alibaba.otter
      diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml
      index 0e2f1783..64c2cfbf 100644
      --- a/client-adapter/pom.xml
      +++ b/client-adapter/pom.xml
      @@ -3,7 +3,7 @@
           4.0.0
           com.alibaba.otter
           canal.client-adapter
      -    1.1.2
      +    1.1.3-SNAPSHOT
           pom
           canal client adapter module for otter ${project.version}
       
      diff --git a/client-adapter/rdb/pom.xml b/client-adapter/rdb/pom.xml
      index 9569abbb..458e8ab2 100644
      --- a/client-adapter/rdb/pom.xml
      +++ b/client-adapter/rdb/pom.xml
      @@ -3,7 +3,7 @@
           
               canal.client-adapter
               com.alibaba.otter
      -        1.1.2
      +        1.1.3-SNAPSHOT
           
           4.0.0
           com.alibaba.otter
      diff --git a/client/pom.xml b/client/pom.xml
      index 0520f759..3d029080 100644
      --- a/client/pom.xml
      +++ b/client/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/common/pom.xml b/common/pom.xml
      index 17adae48..9d6777b3 100644
      --- a/common/pom.xml
      +++ b/common/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	canal.common
      diff --git a/dbsync/pom.xml b/dbsync/pom.xml
      index cca7e744..4b027523 100644
      --- a/dbsync/pom.xml
      +++ b/dbsync/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/deployer/pom.xml b/deployer/pom.xml
      index d446c724..d7de9395 100644
      --- a/deployer/pom.xml
      +++ b/deployer/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/driver/pom.xml b/driver/pom.xml
      index 8864e940..05baa5ac 100644
      --- a/driver/pom.xml
      +++ b/driver/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/example/pom.xml b/example/pom.xml
      index 19fc9cf9..819b4b12 100644
      --- a/example/pom.xml
      +++ b/example/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/filter/pom.xml b/filter/pom.xml
      index 1d5e1c10..6fc6ba88 100644
      --- a/filter/pom.xml
      +++ b/filter/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/core/pom.xml b/instance/core/pom.xml
      index 30522ee3..a7346fc8 100644
      --- a/instance/core/pom.xml
      +++ b/instance/core/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../../pom.xml
       	
       	canal.instance.core
      diff --git a/instance/manager/pom.xml b/instance/manager/pom.xml
      index fe6fd6fd..ef5c987e 100644
      --- a/instance/manager/pom.xml
      +++ b/instance/manager/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/pom.xml b/instance/pom.xml
      index 84d14fb3..2a4da45c 100644
      --- a/instance/pom.xml
      +++ b/instance/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/instance/spring/pom.xml b/instance/spring/pom.xml
      index b76ab819..bea5d37d 100644
      --- a/instance/spring/pom.xml
      +++ b/instance/spring/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../../pom.xml
       	
       	com.alibaba.otter
      diff --git a/meta/pom.xml b/meta/pom.xml
      index df99f3aa..002f6231 100644
      --- a/meta/pom.xml
      +++ b/meta/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/parse/pom.xml b/parse/pom.xml
      index 88d0c544..985d5de9 100644
      --- a/parse/pom.xml
      +++ b/parse/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	canal.parse
      diff --git a/pom.xml b/pom.xml
      index f2c49c16..e5ba86c9 100644
      --- a/pom.xml
      +++ b/pom.xml
      @@ -4,7 +4,7 @@
           canal
           pom
           canal module for otter ${project.version}
      -    1.1.2
      +    1.1.3-SNAPSHOT
           https://github.com/alibaba/canal
           
               org.sonatype.oss
      diff --git a/prometheus/pom.xml b/prometheus/pom.xml
      index b735fb0b..f6357c20 100644
      --- a/prometheus/pom.xml
      +++ b/prometheus/pom.xml
      @@ -3,13 +3,13 @@
           
               canal
               com.alibaba.otter
      -        1.1.2
      +        1.1.3-SNAPSHOT
           
           4.0.0
       
           com.alibaba.otter
           canal.prometheus
      -    1.1.2
      +    1.1.3-SNAPSHOT
           canal prometheus module for otter ${project.version}
           
               
      diff --git a/protocol/pom.xml b/protocol/pom.xml
      index 058d0717..1b776b7e 100644
      --- a/protocol/pom.xml
      +++ b/protocol/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/server/pom.xml b/server/pom.xml
      index 5876d1d8..709a14e0 100644
      --- a/server/pom.xml
      +++ b/server/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	canal.server
      diff --git a/sink/pom.xml b/sink/pom.xml
      index e4c50111..5e706819 100644
      --- a/sink/pom.xml
      +++ b/sink/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      diff --git a/store/pom.xml b/store/pom.xml
      index 3d4b948b..4ff3d1e1 100644
      --- a/store/pom.xml
      +++ b/store/pom.xml
      @@ -3,7 +3,7 @@
       	
       		com.alibaba.otter
       		canal
      -		1.1.2
      +		1.1.3-SNAPSHOT
       		../pom.xml
       	
       	com.alibaba.otter
      
      From 9bda60bf89dba37a7f75eba3be0f3e39ba92a623 Mon Sep 17 00:00:00 2001
      From: agapple 
      Date: Mon, 26 Nov 2018 14:29:13 +0800
      Subject: [PATCH 360/360] fixed pom
      
      ---
       client-adapter/pom.xml | 75 +++++++++++++++++++++++++++++++++++++++++-
       1 file changed, 74 insertions(+), 1 deletion(-)
      
      diff --git a/client-adapter/pom.xml b/client-adapter/pom.xml
      index 64c2cfbf..dd216b25 100644
      --- a/client-adapter/pom.xml
      +++ b/client-adapter/pom.xml
      @@ -6,6 +6,11 @@
           1.1.3-SNAPSHOT
           pom
           canal client adapter module for otter ${project.version}
      +    
      +        com.alibaba.otter
      +        canal
      +        1.1.3-SNAPSHOT
      +    
       
           
               UTF-8
      @@ -25,9 +30,42 @@
               rdb
           
       
      +    
      +        
      +            Apache License, Version 2.0
      +            http://www.apache.org/licenses/LICENSE-2.0
      +        
      +    
      +
      +    
      +        git@github.com:alibaba/canal.git
      +        scm:git:git@github.com:alibaba/canal.git
      +        scm:git:git@github.com:alibaba/canal.git
      +    
      +
           
               
                   central
      +            http://repo1.maven.org/maven2
      +            
      +                true
      +            
      +            
      +                false
      +            
      +        
      +        
      +            java.net
      +            http://download.java.net/maven/2/
      +            
      +                true
      +            
      +            
      +                false
      +            
      +        
      +        
      +            aliyun
                   http://maven.aliyun.com/nexus/content/groups/public/
                   
                       true
      @@ -36,6 +74,28 @@
                       false
                   
               
      +        
      +            sonatype
      +            sonatype
      +            https://oss.sonatype.org/content/repositories/snapshots
      +            
      +                false
      +            
      +            
      +                true
      +            
      +        
      +        
      +            sonatype-release
      +            sonatype-release
      +            https://oss.sonatype.org/service/local/repositories/releases/content
      +            
      +                false
      +            
      +            
      +                true
      +            
      +        
           
       
           
      @@ -52,4 +112,17 @@
                   
               
           
      -
      +
      +    
      +        
      +            sonatype-nexus-snapshots
      +            Sonatype Nexus Snapshots
      +            https://oss.sonatype.org/content/repositories/snapshots/
      +        
      +        
      +            sonatype-nexus-staging
      +            Nexus Release Repository
      +            https://oss.sonatype.org/service/local/staging/deploy/maven2/
      +        
      +    
      +
      \ No newline at end of file
      

      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

      n@@6j*ds!*`67m|-(_2`?xft<=Uu=5Nup;x zZ=|FYT%%p-b}ml6OaGyxaxha^Bp4ufC%MwBSt0-8c+@htVCs#lUv*GkMsJ-g4S7NE z_ik+_l)NXH&6F$}nk!_!WUg7*!(5ox8E|c1aY!_=rp2hseRT+p!2NASfrN0vEXmx_tvYP?YME{#=MC~tIM=$)0z>=?4$A8Y}|Or8TkjDaR+9`v}u=o|6d+*mM|~p bhypeDPOalC=~x@mYO~GujTsx<|NegfB2)=^ diff --git a/images/store.PNG b/images/store.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f5b2b66ef72c52f9f74763b720261cb38f04dc10 GIT binary patch literal 23994 zcmeFZ2{_bk`#-GQ)g48PQVH#f5JL7!QrTK8*+UY>l5JuzZ74$Bh3s4QeP_l}gfbx} zW-LRJWehVIW-w;_ukWP0dhX}>J@5Z`|L<|U$9uf@aomTD`F^kMygujW{G8`?J-Mu_ zxtVJZ7Y7H&=8G53UFG25gmG}Jx&G&R@QLIuWCQrI#{H`1S&sa+y+rWKA9iPS&Tw!P zg>x@ktpmSrbiH8g&cU(sF!cYLi&qa#b8wjJT|9S2|E|RpVX= zAVk{synM;+keqTLb6-pPzTmG9E+sz}aHu-{r1J97-~(i(;Hw9m_XWRCd7nPXaBlTz zr-jEAP_FqEtL=`fmY05CeP?!*6g2iQcI)%!-jehLc(qLV3dMyr>LLhw!?90=PY(Kd zfiGY)^mChr6({&{@7xtU^y|JW|K-o(SMWY`RcsAwDb{&7y_NhWxq&<#j=ZyhN63vN z?@M8wv^tFn0C7r8qqzhSgI`m94d7a;XvNC9orM*v$SahkCt#A;6 z8ZLeCn!E4u*3|y;sXjlVqQ`~9iSxMQMwt2?rd?i6CHT}GZhrr7>o=obPk5ed z)Wb{?Dh3IRAV$BYF!F1AT3-n~QGm{f6~lCyP&RO@&W#QCc&-Hvoff~yUyidtq+Ttz zM&PYycgju0Ge%>(I!4ZowGx*V(B#W_n((P{IhP(2W5adM^^Z)&NKBu_*_P_#5tSGT zbhSeb)Zr~JJgfu|4WS17dpjC6$T1!jgAaF@TKH3Aw=WMA8MlATSEeu*smxM;W*dBk zsK&IT>#fXb(R=Jda=LR;;LY*yDvgjReOjxiPUL|z1-I^ToCCu zbwqE`rnSadFuoz(y4+=DjhddV7NW6UIU>JefJL{Kqtq-$c$XIv`>WjBRSKl_oGWLJ zR3O?d)Q~+mOwK@TZTHF6KIXNJHDnYiCxIIptZ@K$ENx5t-LD@yydxtOdTTDG=I@${ zEVypmL$xLL7w*>kIH!S8%}A}k6%y5Uzl8d2_KFd!bb#Pr%S9k&&{e;Bn;>c4LkEua za3}pi^F`ySYrt#Yi0b3Rl*o7K(+#|6g z+~|7O>gWZR>P0UDds1eh-%MkaKdMay?fKQofO>AMis&mZUgFo>*0Sz`q8QAo#{1^r zcBtPsyUg({n#)QX!Fw#;xh+n?BCm62i^+A}_73+F%#O5K+Kfn|0S!gmqA57BYjjK; zHe|hjCwu3e=Hi;NQerI#k!PBP3ygbWtHxwnN%*o8!MU4!|Q7xsMZ zdhd?c-_@`jW$8cicoBN!a(I1!PH~zOZL(*eBT|;wL#>Elye7^r>b3b!Jg$NDa%V6~ z0{(i2)yW?Sy*=A#qv_wl-zZ^x(Qz~ zF{SU569s)4eb;WZ!A(x~ZCCr79dS2p6`-myPCCZl1`nDR;rqrs2P5Hrj^ zMf7)!5h)`#lshrJ8P-OuRg(F3_|*3*?jAn3nh4R(zbm&_*h(gvQDl(JHeRV1#l)YvRgS zi^iGaQhu!PT}9%{7GFUx1yXd6e~nc^44G8!I;7d*IeHA1A}z<%lkv6M zL^0w&U_9SiL0%#?pHGA~6Nm9;G1x2}xnfy^SB8E+?YGB-4Z9qn>i>H%{9jP|{{?^k zyft9TPK_Zz7iKjefnzz=k^#FVxOtX`<5>!9z+gvtB6HHNLu3udv$Z2HTk}%hD{-7| z0e|2!U3fjSU`f~RmRj+MW4NqLb90=IU6e$w@T%E%A(tz*D4|rGz6o;dGbpOg;gi$R zR(SKLB*$$RuA*lK`_aMsC>$KeFr(&Ld~)^N9Q)+n1RnsOWR-YF`C1*U=HR%$xsct~ z1Z?22IUYQD&z3XyDAX;*SonvX&_mCD`6Pg=Z+hq;;puLf{ug?xL}&CA4?R{$L?cQp zczP|zJ3NP2a5X2#>$Xwz^%+HUE;8sdwasFh0W^ONz5Qal5Vfr3bK3gm} z?kU6W?a&xl!*SJ)noq936+Y(jPKx=6zt_BdyRI8>XD?%ANyhNrteR8|$AXx^4UOmU z(>~|Jg@y-w7=rNO&BH0w`(8HCvtBbv$>a`AVaE&iwpzTA^j{K)8RZdDoa7N?&1BT& z5j$)-mLu5Hw@*BSrbzHZFDbznn(HcM9dT7MKJ+IN8%npw!UNi@9N!s~{8=zJr&3YJ za2;xP#*tP0Kk@nH*~i#LnWH?S6AO@29-m#ly%+0m(lPBP6A*-VrY_+USiyKgMu92X zxn@PhXDr^k!}i{Bc8@bh_|?>!RHonXRroj1$#34O`}=m*`Q#E~gd=N)!!9}xvN#iJ z%SmUL19ykTSB6GXG!oH5Dn93<*7QDUMPKC-7>Y=EvYq0p6Z15@{;7hW5Tx zv!9p|zuR0caQad>kH+C{~w^4sJQ?n(z$v*pFtW0*mAg>c!qOvJMVKqz^S{>eKRDW9h7|)I>k8 zutdm{$|G4DSft~o{p&aHndG&{qh0C}D^}i|skr$e%K7t%kzr&}!D7FAI~sD=yaCr| z8#`O1>xAJYYUwI}jMD^_2#y6cLu%_bV{Xb?*OXrv$LhCo2R$M8pxYDq{ANF7phOaV zTbu~xHM1%bCe`oel_(EdNbh*^I(!CL#6{ZI14W+_Rs8}jEe0_)^Z^Q{xI39*OIX^> zqMT5!+gZ~@DySoRO)it#)hRQvE=EQP2x{y0WqNAj+c~11zKYRcNkWM&NlxD5eOgtw zi1%OxS4<>^t5HWyX@yEa5$e=BaC$1tr;M(mW+MyUspj?9uY41XoL~nZPg=XW`nm0*MSSjJJW_O^Q1n8MVnaIB_+^P#VL=8UoEnYZu(?59`gVs4h>ZMtI_JSGyf{t5~W<9lMsklz8pB1mjF$>sR=o zUwh45r&t7Cp!gJp_C}`tqnEJ%!t)ii*+Hxx$$N=t=ND-w!(uKXRjq4=UT$s0x%Jj| zc!k!k6lyOpI}qLN#&wcQ{_`b^K8u&?a{X`7#wx8Ecme6_%eLVD6E-0Gx=>~AlRmvK zp(X{h;gJzazYq|^i48H5b;}cv?_WZEIXP|Lz|=$f)brlk(_dE2>KfKlUHmASlcQ|^ zrn~yMI2_Yju{1JyI{Uz`$c>z3`7ygJ4r)ETnRf=Yl~lGrd5tVOZvBm^=bH|HG+AE! zt7S-f=&r90?o0mZPww+~0U;9`RM!m9DcHKUskhWSL_qoOD@(cajaQPN`6V26^?2fj z_=}#AD4wm1(>0ngOd2&#Oo*lX)h)L;s-9?QzpN(Atg}a1xRCqd%=Sxj?%XR;KFc%B ziG~5ny&-Igx&~oE?c)8;o(lKh|oXjU*ael5?3o*3r zmHXV9$Bjj?J-mN%6nPts?=AP)YC*();ak6J^BX-C*&GL#D~4V-cI?>V7yO#I=UqP9 zD>u=<%K*0!;&9vNRBk8FYthTxJCD!t_kS8FxRQFFp_R_+ z;unD{1C}SSZI4+)h9!+rNZR7(mO%ueg_li}*%-!Y*Q>H93Ez}#J-&HG8e%-N@QG^8 z=lu#k1xqh4TkS)kUPp#*d~3BLyx#2Y^SY(T^qdn85BI~8j$WACSR9FE;ONP?yCB4w zFtJpj^s(Z~{B>SzJ$T+g^+~+U*t!+pjRL2+$ajXDj3PA=MQaTCyn}W*opc;Fw)tyT zUHGPb?#5xbA#5^ogV3wV3|sfFc@?8eQkT8A^W>D;q|aWoNeJ`*H1Mi=*!iw`4lt#F zsdl8rmlD&#AX=ooiI<=};pBi_P`VgGRtFJNn(Hq_C(x4;(>))3?4M>fwUnO+R&{uq z#Soq|PNHY4_{QYdr}6GC^Nl13eVQFQDoilF(s%_sk4$#r$TJ!o435ZIo_qFWNn!j? zK^<(nW*tmmN@>1!fe(dqnh z^*OScu&nDxwd>!coH2YD$&>NvjZIYe4!L!)a_Lin{z=U-LJwMbUiUPdXxRU%{M=&+ zs=(St=P#zA*9$gpWH2x8dR9WJB;})-^qsY8jJ=pzwSeUG# z@TKRzfrC?Sh~O#NeO@P@1s0MX`#}rddJSLCA(-C$!1S=um=$sRIlr&BEH&q-+4KF^ zUtQE3wA{iK?w6N4Z9<#YoN~fa8`_WU1fp&bym3347!bw78Zk zkJ~P6I`e6(-@U9N?J7I9cc0hOa|&w7%^KqT0%#@C-P@$k2~70f;I&CN5Be-a?w#Kd zdfg?@6F*~yc|_dSC--U5vR9oyEbh;OgK}cU?RgPfxA}+lGsiE>LJ>_Vk&seG1pjA^X&sdryZQQ4hgQ!bT6*I(bZ#cpoG)>l|;{d83tZL zpgYg?v$WJuTvhJ*TpUkI&s*h9?_&x^>gOi!T=ukk>jMe7 zSGXl(@a}b1em+iuG^@J0eQ}P-BMny1MV(FRREtUx6%)o_TcT47ufEAS)ti!o!>xa* za!hE|m(6wtnJhh%GP`lf=BlRAlS$Y?<(n6F9c*}IkJ~>VHf;RM!2cr1eNw;-Da+R+ zq>FLanU-|xVSUaT-c0Q4XqRTVmr@^GC;!od<0{_0Q%G!kS%wa_M89_44@|+3T>_W@ z8hS0h+VK(~za}B7cVZQ&_Q+F@RD2KqX|(BchMS2Y*7~8jZ4&7wZ)_w+=2@WLzNzZA zlh!p}TUvQWtxk6X&`h`6@NN27`g(6cVc|#&Yp->CS1IiFy97(ed%Ps9bnmCf;x+;F z>=T`cF879^XEBy+EAXzrB%p;-jBZ==C*}V3(!N->JGgm_@$!C4{WJH-rUuu{*Q(l` zrFgtkb6FUzFksV0_R1@T*#TBk*1Z)|v=8mHMRm((1;WynQ^)x+!ISPvEsv+&Ki&%6 zfr;P8FgZi>>q)$?QhGAI8^NVUootuVTc1+XQ|)*c8g6*l8Qi_(hJBt_-Mnho&!4yv z@b-utp7>P=we*6ZwB5sl$J8r{i_>?KIewzS)wA){&&A&lYXEC+CN{2rQvbHBq(tTnuE!y<5v?a11Wu~1qt(A)LO@Y2)UTKjk&tr6~g<* zSF~5)K8Yqrw0{8bafGuGEUsisAi(T#C}Eq#)hiSQn3v!`SNzZLlIY##VlZf#uOx3Y zj%(c`7O8ZGR`viE7{Gf{R!7*P&$iKG`gJ!Vdmho*LErGW6&(nommJxE91(|4pZ43K zQml6sHkG%Vlwq5gyBI#Rwd>0&Ygp6~LvMGI!m#Uy_o z_a17|9U4vZX*EGy2d+!$@vRNi6Z`BMM3nXgHH-2O#cikwqw(3teBT0g>IbWJH3t)S zkfSmh;PqYEb3z^mJ^U`^eknR5*pmB>3qAnN$yB3-hiAv-R}BiBOWxxT#Qe^aQRp>gc3T*g~SJk zP!8$oM9v0#tkArg0Q2MA+vGM3w}Jslg=oi*k>CBU) zelhgP(!I;=SgLHGy)w4)mX=}ORld(MzswQvBw}Y#mtTfCbU5CQJ)^0ATQ8y|#=^AX zuO}+<5%otdvlgF({NHl-D2b-4-USmexED|I{AfS@N^L@;k;M=2gY-shfbQX%`Xm%p^u`u(x*TaK-DbbSBhmu_paC6i~O zKVPJjQrMUPbSkc&8bet9dXC$>e|Aw52v?<>em;^~xQ4?d@TbTESQVzc8+;y$qi~K&JDa(jfu!1 ziE7u&1dN&N(w+*hDIfUsCo7jO`5b1cPt+QY*R7x}+i&LG!&tk8k&@9`8A;S~JY3t2 zK@)T}Eq8WT546~0ejY1piLv+AAz|5agL;?aQBHa1-f!vJ(G&hNRGYBtyc|isa+Ez5 zc)qKNl8c>AgGE@CnQz+iZr^s0d<> zULRqy$G!h!w4TvZw7sv#;${5Y&xdkGq=l< ze0EJ~0qE635Fzc93wV(KAT8&1kJjFbult|ZO?ek96}YS%8lQ+(584#o#u=kihgcGP z+Nw_H6WETi=FH8^|A_W^r)stdX2rdjrs7nmNV~gIz|-(~=v5h)shrllkWu~;?J@P8 z;}fRE%lk7MY;T}*{N6Ry6$8F$VnsS$%_r28^run3&s+zwD(cEmi^b)0^puXS!-GEV z&SYCf_x?L8le((J&xg#sdM@DV2G@0XpDrQ7_39oLd5pIE+AJ(wkU`BR>CJ!ltXSIteZ580n++p!o(M)S?MLFB!awBXi%HyLf{ z;}ORMJ5SlRs7KfjDVHh-otr3fyQ1MV)jMyX5M5Ci5R&?h(6EzHbCh$Kx8;qO5#wAlUi1BL)FLad;EArl!W0HyR6LR1m0Bk)y2XvyPy9P5&uyfaKFIJ`d zUdy-VC4ALLWTbTUXlS0bx|VgV3cK!{$Az0my+J^x667?A7hp2FRR|_id*zyhU*4aR zc=>kJyn#Em{&eD!ZNY^dj|#UE8b%qyawzp6P zOexoTo|jKHA9?u!Agy^k9QE0s*7Dr1eTt&G=zCwxV)_aNO1C#0YWN?EBK@ ztMFkaNC0I{5d2nNnzKi%SYs1T|UD0CwU=% z|5ek<@yw>ZN4E=TAAJG&7Ib&x!b9moLVz^BI<>HD-!DD-W=^tRKC>H7KD!$>a0PF& zg>ys8yY<^!*p4pWQ;KaT$(!UTM`7ZR?{nH(Kip_r49O!n)Gp-Z_2uZx>to@3vrU1&lPNJG;>94|I>nYWmaE1#_$D7IwLHe{6xeK_55IgE2UbgeM-&s zQ_XTqPRH1VI;w=LK<(gox68s?g5r!9*cmaSjS1Vi`XHV{#3nwLkO8rSzkhC_xMfl2 zW?{K{^y277=f&9w|0)EzhVpp+_0#`k-FqV7q9(nm=_!2a^Wz=Zyz50RdB!De@g0oZ z?2(5->ALO++GO5K(GFZpy0=%)q;JGqTT4lLfz4PDj!=dOj}nJzMM>nCSd6$ex0yS$ zy)@44tFeg4P=5#mm7E%Mer$~f6Dt`W${N@Jga5J_Kq>?>TnGIDomVG1{FXo1Y3^;Q zoK6k*yLi>O82hk_^3grE0_OQ}{pNga?DzIhuhT}%sxo0kAgO;x0FGVAUhH*DPf1@a z-_$J%>&2$ei(fA+bnp?cXpevP2t7&|C3~;5jVmW*V#rT~Z|(iDi$^?I226su zc#U5ydTM@ShxeNf)Lgz*N8SB<^?}e<%#QYka#@6luEE8xe6fA(EJ7x zSI$jOqYVl=o)5;ko!z!Hr`BgKZSCqIbopJYWSIN#E%us#m%kkq3=q$-Tp7I!{~9eK zVl_T)MeaZ2mrA{tDmB4+?mWwneEdr{TF@6?kY^Bi9ue>H%U^DD#d1YU@TFYcC9%u8 z{Yj1i)Hsy$!5`_9*PaUl6+KHZRZO$Ax`% z^BHpG;lZ%SUEixo)Tk}VZ@G+TL$mBbivELgX9BrkPfyQbW{A+X-CU;E4H3Qmo|7{y zOToab1}VoK<&z39pKviFKT@rEP4E1Kr%zY)D}T-<5i9)rA#$%kL1-Iia3!~T z`Ho$@x0j9)bB^velk~R3d)&f$*wPpRa5rF+f|v2n6oF5WRisQDbKKS@U&1fgq4K%h zX>e!&aEY$s`{FR4Tz9;OL@ceC_;vqxYVeLW*VQ>zUGlG5i0o>XZL+DhZCmcoCdy9 z#8Yj^{76;+db<9M0qKJ~XQz+L)ff#cb)}@j{hrX~xj8^wmNKumbRejUYXzMk6DsN^ zgl;dHU=>lhb>c9a{Mv4Q*^wBD^C`_S2|sv4_wc*zeh5{1e%w{fIDP*J7JtP z!(W$c|0aN+St^C}`K6!7V4NGwCM6wn8I76u8dZC?>sVI!KLq+7Q4~=9S?Z3{Tn7kW z+=168eEWc$OFZ3+`1I&So)T(}eS)o``Y-*}9|#bz6U)%AtXHr;bM!0#$$5ozd!O8G z^Msf(Q01jldp$sV;dl~p>Xg-Q94c(M%yblk~L5v9pjGwL_QU? zIjh(k_p(0yK)Gih54}^XU};P6j&j!*?IyOm7CwsxmY>ev-yRTBvq2|8;2B^zGJJBS zoLmA|?Ev9CKN8;(PIK!ycrN#HzDE{wGUA};u!A7cDtzfv=?OKhXj|@BW$7s?wJ8=u zC|4UYYewf*F~vvJ57BxC!4`Jb!y3Df!fu}g@3W=m9}CcF9ufP)K`?l~l||n6T#a^& zP=0bsddiNoVqOxbY3pBmM-n#$Y@Pzx@)^RG=iWGp-(Qh6R_LjZKWo(~bkc86rrxqg zeoUJ&cG(2+rCuZ^xp1Va*YLTRo9z|+CmyhU;sG^@c@8I-!>W&R25b)Xnr|>dU8K5% z1DIuRX>Wy_ISv<1lkYPI$9M(NswGz{uC{_rGj{8%Tz-D zV3cZ2ZP}-a|iE=CD-VQ*ERm`=*5mY z&gAcu-~eTUUqG@2TKexR#aA8&wdxlbH}#NI%F57K;eiZ-O_y^lS9id9EA{Mu1@Y(b z`H)b4wvmE86Tx;lKSAK=Y`*_*YXH3Qlb_S7G0-I`2cpgT!1Jkv`wbu#ukg$6dyigf zYzmRSjQ?~SOh*gkLq)g*dVych0!j;?_rD+SDsca&A+Anr>w9$V7!3}pLI%Z6N>3@O zk!FZ8{X@blZjS&h3XwpL3BKsB_1n-Nnb+6?nji#jEWDYJ8tl!i!sN2@f&RsN^)QD2*u0 zRjEFdCpuOI5e8v@@E!Tka0%i64IcXL-Rgw-Y0~Da;BJY%0Z-W*d({|zdbO-1P!iGr z3L!}Br2}bacf=;9tu41}x3Jt4Qib%PI_Ik118DDQ_;I{oc^ZdDXK3t{+78z(rk^(7s+F`Ars}3%>;qaBL zQ3YGk8z$$2)F+h7pvql`TArf*YTF#dOh8m>(79fE*I2H5{|4dX(~acvCvd}b_@V#7 z1c3vX=PQ@-NgOfEU2bw(@J~fJj2@@sZLog#`zluMA-=Fabc|)*%X*wuNUT5N<6K8& zEx|L9^LJUx>sZ!DzKcOnJ+;iOkGVdfVL#UA-x##M%xr!_{Z5(j6s_e5fjYA#0}n~l zqcG8%sLPLO9|O1p+2xTZ-kSupT6o-?8<(o>4_JHKG9$8t5&zuK(7f^OFmtMpJ>O?GRv%*%vdNJuC zLF0e1AU1VTvmmOA`f~dVSKSqN!l$hb5XEm3=x84sh~{i}t5Y7W%nVs?F{!II^>kd> zL$YY5;P~GtqDw-ec=J`&8Rz~6GvM@$^sKH$ckU0pmaeQHy|Yan<9^|_PG`>7y{VXaWpVaEF|^ zgC*i~Q+VR#=x%yHi~K^5f3eeJd^+42J1S0oyCFkgrM+dRocGOM8~ z*tv;$i0>zSmu+q~HU<9!W7h?2KK27+um9qQzrzR`z=$n7YXIDc*gxC|KQQi(tIo^j zR}Pm2INW$0eZUua)&s_ZNO*W3Ze|YJ9wBNg_2zenl2B1q4s)`sOU3|PX&%xQ>0M}T zP2ie-yc6IySwhb67oQPx6~CUR5-+|yZiAFA({&Atf0U=Sg>>~D$}KYgdYYK6Q(tX^ zIK!uk)RAgOwyjTlhWjCyIL!Fht?gs8^bt?4_ zQuEE<{W{g=*SPwU&OBtMJy1e@EK0X^!Dnqt-?l@-G|n!1H8h_u*d56h2E3!#-1b3z z;LE?c?f0oI#kQ*v#s-Ylm(C&S0c@;yoKS!Pm5|5ItjM}+D>ew^7$C&OTu-o_yZiP* z084hx#436kl??Rt-v)o{n>qZ)f-=)_8&&D+&D8cSp(Vfb-D3fH-5#bu98YbI88qIF z0j%xcugtCS<8)xBkh~@kusIWC+gko6QQvzFnBCQz)G+k}?mNpZg;(0iud%up>!&f2 zo@3^CNk!gMCS3ViF2ZDgKeO;mnYQs^iQ2mtK)jTJe|3E)6g#q+(|KnoK=}Ty{Fn7d zVj^pj9!pfztrARk$<+FYnxyx@eJ|x`)y|Lh_hURp-@VZDVy>UltCy8GBL#-;6^h@m zS9~3=i*7m6raU~E8G>9=+M}{G0q{hxNUG>K6a=!lWN1IMO#q*K{eBd1f1RjFoJzMx zLD>nX?p(X@%3P!!X1sVAL7#%t`w^D5yFrC4t(X-rj*S))iTmOYH>4ag^Y1zeA8O-lMHb=x-j~SmYm{|?)SOX42>3G_f z_Yi{xQJjP#w*gP`?nw&VJ%~l@roSaFFL< z>nae7{k7Ru|6^S*RtJ|?_B8(qYi!ELhNJKEyh`~R7sg!N*mBBmY?qBpF@Qeu^#I`* zV!J<(39J~H)H(z>zTynjn~p_eqtM!>JacsIM74uXh`1Q)MmJ!0%K?=hrZM=xCB{=x z%i#P|)+ZA2fY~qd;*sxwa|^(p#t--MVO%~th!b2s;po-KBjTCi8$!EnEd{63O zZsgHHV##Fi!8-1obU@(PjzI>9JqI8O9wNWLfVdA=?4`dVyWHzbrI=#UB<3>n_dRoPGc+?@r0d|S`Dhs{0*}?8HEAVR-h7k z@RmWv@f8Dcre&}j+_6HWy>juG^cooI?qdmJ0THC!*t#11f&ax_iq@|LWN0yvmfL5E zZq)HzyafkQeoB7ztSG!gBkJ7$*dTFBQw@V=MBvl`c_q!L`HBFik6pN>l=N5^lJy}_ z+z;BKHQ5)K4XD8E;;7gr?X?nnq~DuMjkZ87J}Ky*JW{=YfX`mcIByPR5GmDEAXn6U zPeJ};cLN~@O7FYq=TV#MiwE$4e7|E)LMC*tP&Ns;spK6c1eum#qQG zs*<{FCn#0=TCO&ws?ks}zGB6Bn^{ z2>_nQiXnv>ikDWGj~x@U`OUwDoBsrmW#N`n3m38VDD^?(hgh{A%p}g=b|UO0*9?>Cq4r znt2?KGl!$0G$+WEdi+X8vpq+QN3%O{m1T}N_WDDNKBLE05ipt`o(7ayka$)xJMd14 z%XFGJ?FV53Bq2?EWD-Q;^YY%a!i4fE zrG9|j&`Fn3i}XQn{>sgRE*>QLhAmmxn3Qg zmGZy!2A~m2+2p%J73vWB#Y_II9MO(dWo~4jJ^78e!Uv?Xil0ISB;45lIK(!$a5l*V zid@zSd)`>_E5;x{<^BV@q;(E!Yl!jHC}TdZ`VJ8NyRykp3_mC+|7qx;6aXjqn_gJJw>vo#gg>M%#;=Z=kjXyYK2FH4m zJFi4(0DYLW6jU(t7LqGcC8};dMlWSMru>v!MFXlGWs82}q{jlLpo3ML!I=!W{|a!z zef1y=weH~#9{+0@K_gS6R@%`|FBtka_D{JQsGg_9~k!zQ^qXl(AFO_63J~2;&_#{q^ChG~uhgu$Gdi5fz2@f>TNIo2D>gj1Xqy&M*xXd@0uvx~un)p%$pv$Xl zJVemPrP2BD_LgqIr`l??4PPY!&&{j;(3?6y7rra1IW_m{cT>&-rrh`P*tH*3bW7|* z$qMG5-blz>)t{p?c?Fo#sJWG|jap`P)NJ{k)Z?kAuV!Jz|2StkwlNDRK0o{n2nMi2 zi#^mV@rc9byA=@nLBZFL`glhcVGo<)U*@^A`{C>+UdSZQVjXtKso0I>6P!Q3B4wvH z|Gs6f@(W(v{R<>@cxSk0dPiKEE)G%#K5Nt@!aSc^7r^wSX{m(gPgM zj5rw~BY1VUg%Ze<_3?v>&^Gk{EW`Onhtb>uK)PkOk~z#O z_9RiLE=U)FKWn@_Pae^+wF6N-{ouXm*zWKlQJ|0oRVLSDt@n zU>6BR^S9!$EB%Ky)p|TePoQ!DJf!3=4tJ^2b=C6VpEEH99-I=@M-_PqTCioYkbmI^ znfWbB@z|Zgh9$gDJ`X4}b2`%n`uc1gcDy}yO=uQNH4|U#r!tP537DE7HG@2ohB?C} zVCrwh?ae*maCHw4xmzIVq{6nRuSxl=aZWY^5J#l=#nAVb|F)O@!TO+pk?{?y;U6AB-k9zFLCDwjQkS%%_6&1TcwzQ+&Pit= zn5!sHJx4_q0fGsp(gF!eGi$X$&MZegcp!r(ey`kQarS)IkI2AmmJ#AdX3pZ8HIOUE zz6&Czx*$gWC$&pdQDc!yf_r+A=$?sp=4vBETao6ooDj2FZ23e&$|76erXtUWx zTp+8Y3n_%ZF=$;VKgBLL16idfKc$>iiD4s@BV5fYoq`n4-x#SG_yuME0jH-9|Qy@0M^ zcW%|J(B(&AP(>#l#Ltx~2zph68mke?*FgCle;?T5E+9m%j!?hF8{ReU*xy@5BD_@( zsxcVMrD3fxEz?#9`=QVtlvisrqyXv(N%amHt?p1 zGqrBzyE16XR}LJvCxdsdM?u|8q=1Ug(-os|S(S2MC?mf*e9^9I5`18ta|2xf;EP(N zsmK5s2us*%ygRs9C2tJn=rWB72O*m2LPs-nD!j`5#OgW{kABw^(ax555FmrY>ON%* z5Z;j?zN3d_pB+#1)BU|M1f<7;L>3&R@X%)Vx9xw*Mn2a_Gt?LPyhGGH}4Ye%Qe!SqdmdZ+99f?%KTP zWTi9+dSga=Y#+j`pwc!7JD09;9MM5F>xJx)145vfVXm8s&s?r{!Qs^*0fUm2qoDk( z1Y|}PE8KD9gQ|X?ZMX!s02qL*IKp87s`NcNoS<&X<&?rTi%@nK$QfxdaKWjKm}_{T`M+#e zVmd~3_)KYNp5s61O=_(&^Y~*yo;_&6fYAE&dx!38&?`ZhNM_9>`g=dJ$VeEK*mYtb z@K@Jh+Q*@jz*k$*smEYY@^osT1Xn{PS08MUwmSP;#i6HKP0a?M z`-QuHz-#sIs4(9eA4EPTcO()zXpuEa=-9h0tFLRh@Xm|6&2Jz%#pH3CKi@T(KU!ljph5+Y%e%H#J4w&CfyG;qA#~P&5e7w~vpMw2-nRblR1}gL-*7T(g3-^Ba`H&^o+cH6lwc73Qab`~Q zyjk}O-jdZjuRL@vNj%Q0o6_IIAJ6$qiylOO!SuWBHFSe?ux&7a7DL5qAz_ci&Rxn= zfojm(YJ$Y&bU6qct2TUBhHz8dvE!oG6~lEBPX`LvQTmW?z`2Ky|>T(<$@17IyWMt7iFB8qGQp@fta*4jhds>|;! z*If+|M?}0ARDxH51Z@MqGFp}6^d8ti8FYJ<2QM3C*_V9$-#v{2RoP<-Axz)EaUmW` zMsOtA{yatl>U%@REl#1vS`%s*qVxv{Vj3t}D||{0N{xLrabyqzKd49y+-yY7p1(=7 za055%Ko==A6d1{$H3u>|yJ`$$uVRwejR3l0#R63_?D~2_qQ50M!J`0Nnd1QOd#4os z3b~56Ipj5I_B|uHSly{93jOt<3X^97KGUI@VMG`^(Jp;jh&^_cMM9D7H|o6fP`lx^%~(1S#<4%aT7{*oU#{46i>M5F?}62>Sfw)NN)D>+091X4n4!UD zj&@b0=BfU&zR=z7@yO*)Ou>>TNw!{E)JOCoSJR2jgebXNb1QjzDTBV$soPd;Z!(qv zLKk4u;O3Lf&~+Wq^$x%f6iDj`TyexXWiAhl8n1tkVKV_-i%j`#U;JtrbBlC|T|4na z#++HGEq#!F!?rhof74u5)s44jS6oGbhugYJXJ=4Ck zT)FR-53B+`cdA`(N1yraKOo^>B1y%3$)=t4eBzE>fR0uAJ+4GWUB88`by_N$4jJPt z;SWKyT%s1P^HzlGtqJE2PEAI~ig#`yaXWExz4o7|!YgUbON1EeGB>Np-*O%iYaSKJ zO9BCWJdek_zi0X#m+AW~Naywr6GRs4_m9>x!-QSx6;Myr{;YHCnMzvi+Ul$pv2BKn z%V{6{<=o6E}44tW^o zYa_-@x*Ht6_rQDox~X#9hb|KdGmmc&YY^kkGp&0Y?@seJXm9PbdT!FbU9WA9^x55D zVb`f=X)9FxdhLYJ>|7I4v4A=hwA~M5BO1xXyJ@GnnbsKnQApTP%-sI;{KAZa2CDT- zPf?%!@}Bsf*B9vv-oMO-7wR(@O*(rtpP`zN?scelenT>zUIYPVcKz?C+CdVYSDuz|y7wRG*-SbebaBGm6<~T0xgYNzS#4gA{{3a&7a6Zu{ji`d@l>fSef(ewHdM1&lZ$Du)M%px$hbE3!`nP9tm-xPa)A1%qRoE1f`lmR7t zLBQUzx{y>ftoOd$Roz*seEjnJY2~IwTKZt@b*mR(FZE32EBEPx;|5#VYx?*Sj=R0? zTX-67W^u_V+-djr%|{iHP5|l&4|%m!t^H~K`xNng5 zK8;t8LC+~2F<<|leHzsqM}on&hF;ur@MXBL#4;X)^2n}b{DREHtQY|>8Nd8vgZgtG z5^;V)!+HpCDd0$t9L5=bF~Lc+9PL^P4dH)2*!o|o#Ac39(rm0`-`(|p`dsb*SDQzB zFqbNY$kg_z%B~zEbch7%5NZ=gDJSq7e{;8}{5AX*-~1PbHdMHe>B@r@1$UpFR)V~~ z*awVc-B;G&3IoX+#QSo*dH(AX7;XcE?{hokBESW6;Ba?ueqrcdsg^vmsupI!$~?G_ zkXg5ao9ZcP)`_KMCeq(M6(Q8INOh=+!35L!I9@fgan^_>t5GL<`5Dre^r)|&KDhFQ zMPFeJ`U{$t!|uTQ??ffa`{He#o5SLr8v-dHFYY^fP1*=MDmPCgDBe^G=-OLG{DVKDg)xPGW7vs!|9P8pj>6%40dzrF~a!rObkAI z$e;_mf)eLz-uZ0@5wxBjf7Ga>ilu*5shh6x`=U?*`6X~^TDQ#9kfv{Uz}STB%c_Pz z3ln3cokE2(!Kco@rY$vGm5g(dk@@6iQd>gj=EwuZE2cZ-1bOTH7(ABG^jrA;;0j(@ zR9nf21y`CG*phN(+G*___X=$Jv}3#&Q*hM74ZJ8n2Ca%e0okE4|M~bz*@LgpwPxKj zNLKsGj0U;P$P!MelOL=b1y^tqbmUnB=7`pL=%|wK7eK`daK|zhzh*wjz+D@!S@^0| zJO1I3=@FqBCdX+sxXMUh>6`hfVD+BVO)<<#cju{IPt{n~Y*gJs+?A$kdCLE)0lQm@y2-%QP6X_o4b@|J&!E=bY!BbDw+fdG7t)=X-zO52G$I%t6T> zuAQ9AstpLF`L`@6V`udoUmO)-2D1gX(rGb5MB2QsgcWzxP3n>VSG10kWn*gbI=KmJ40e7h;s90b6Dgw7bH)aRL}_m=SjLtY)H zdeeJ}EAA;NLY2oV_{sb59v8v}45n5&fen9$&`Q8?cpfid} z*muofZMkAd=L3Uyjdax^J@;fk;3_p6HO<4XBW}p04Rs0jLjfc>pD4bDP+9oAb96I9 z)E?9HwwFg&eH~AolLC-iMh<|wau*n*C2H{EG-5k%3Ud(IKQJSU?tGb6**%fnW87@?-$;8|5d5G~FYX}F{ z+}w)vs>6I$p+Cw!`_iKz@UM>TW@EIi7hk#g)b4MnlM9MtYMsfL^Jq45Etf?kAJJf& z^_4s{+^E^E!SBbSv@0iG<X*1x4g+X|L<*2 z6r(RT`3Q5uwbHd2Ir_svz%X((L2h|*Msnz(M;ixU+B>mz4Njj#a~S zRlZ3Jy@lx#5xS9?X?#t~QxodG4#Ijjdfd_g4>B>0%7!HiBV?gO%&NiZQ%|;ZTu@SQ zz`RFsjkgQBbH1070(vi6agPX076lxYLOq)-Ypvcp^HS7Tef~Jw;va4=O8u^zo>xwy zuPx<{WET1LMSje`k!iEyjeczZppL>0$m{HlhF$KP_~yjl4Ff~;Ln#wXCDYLvc}76e4qf3&&0w8 zd!gxRj$?rcJy+80xgN=xInjQx)WmT$xoWdh3eR~^SH^2H@|sIi58|bQt*6oVkfxUP z-pt8oUEvGS(}DTUf7qORgTB*mkbZ#yH79{pxLuRh;Z7SV1X;bj5#NTLImho6+m3|x zX5Z!nV@@qi%amtaH^Hj)E~VXcouov`s47me5-NGVv^+glPziRDD?1G+v(6;ImHW^_ z{1Y4+0dPyM3~PeiM*GdBOfgelrkc`r;8xV#EgSeqTw~@Hq47X=(ywDRGzP1&V<}Nb z2%Yvi%=t{L^h6P#*ZO4`z@+&#WM5b0+vqx|86>Yg@q)=0#$YMF6}3d&_`l=}I9{<9 z7j4xf1pqjY^GslJ1sr$uazrS=2u@+|^Q%8`HSm+Vft45nvjcQ#SESDJD^nxVmtr2v z60wXlb+&AT1&bX{IjjFuk#OZQ;*=$n!}|)RU04pIVH?ZgFx%;VTB)Avl-nrP=r#?9C1VO1z%Fis3a+u@# zt|?~LLTx^2tK|z_Vq-E=@dB)f*igC?uC~aRM#2Wiu6vxTyaPdON1T6n@e+j9G%Je8 zJjT~g22TeA6p#Gj!A1H{$*zHRgUb<|TEO?u-11$jl6mzCwI;JIRoX~^OPmh79xAmz Gb>&|JSwNZq literal 0 HcmV?d00001 diff --git a/images/store_remain_events.PNG b/images/store_remain_events.PNG deleted file mode 100644 index 4e617a216fd0f2ce0aea071971af1b1edbad68ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56792 zcmeFZ2UJs8^f!vVfv8vzl<17YC`C|;)YxV$G!=mW0!Bm#ReB9kLC_Jw5eXec2`C{H zAwg+T5Hf-g3=$wAL_|Oc5PBdX$#()e#ZlK=>wWM4*070ChvQL-wABvCm7lZ$PK9IZrCnmeiwemp~{-^yvdH3daj=jD)hcXAOh+e9 zN%~|pkVf>nuxpj!qWMo(|4`qfgzQfXP&aP*_aE8J<*K>Xk(dz9h%v1S$@}+RFjx_e zbzD?^_QHLw_W5&UwZg1>)-`^akA32QmukA{+(e8gS4lW^ss_}%Bg9Rk!au7{q(iQT-|3`?@SP`&s$bRJ zE$j|(dXumBR9W!T<7IaCF>)Mjtc;`ru1~{(`V%VHV@DyyU>~v9>?jYxRcc6xwJt_h-FNo^( z2uyQ9Q}=}pTui1N-cv?!<8-mgsB6WCrvs-byTsf#p_d!eo428}ce*LVaLclk2{|fy z?w#o#c&Hqr*$U#FZJAX^LeR)A&N8J>YR6oVdcg|+l-zH%p#oE z{z{7Pc$vJ>dNg%e`W>iNzR%g2M7Nq_fwVLe(xbf{ku){uE^=Q6Ye}bcmcMm?#b>jU zK3skFlV#48E8}dLY6{If{$z@6k}U*HjfyzKQ{1y+J-V(?laX$Xm2@jt^MldW-JITl zUaqE!!yjIj^B)?m52oM<(vD(nb+@AYRpBcQu|(HOft@elYQ=-fNQ|G1$6LAre9yd^ zqIbaeOk#Zwwg=J~E==wfZ;E-g@!`u3fytgQ+BT!hYkpRM#9G95JhNxqz}z8upP#Yi zlaatMMNhw2xdDBInA?4UkAufqZ27Po4#(MTIiI}frb^1ki>IYu zvx_4!J-Lez!)gll9kj9E-@f`-s4j8Bm4#!H#IOsx+>}?JN8eJH zQT~Tx(uh(8(xNwJb}Z_iEC!iInbX}{d|6x0v4lUM-9sMU9-n?WQM|!-cXaW#J{779%%f88fiPMJFf1VoPZ{7h!0EHaVITlRT|f63I70OOSHPwj+oZ zymxa4cw4 z@^=`U$wwVB#vJB+{phYdiq3`G^mipyeWa908R=JEtCHJVyxn{cgvmqbAN}I|%B>T} z6U9e0T7$w6ob&$FXK!AGwmh?MBoS)nC}Tkg0VB}0ojl0SNoUq+#Qt%TQyiB(lVe^+0FUHCi#GZx{>xYabcs_9nTOPtAj3)A~1?)1%q(|%c%+}k z_8i}GnNsNEm=&|krYx&g%Q?E1FtfTZ}sWCIAl!gqfK-bj(E8z;83^#C!pB|e}9>b zxO9@&$&6d>`T3F47o6tO<)wnY4C$nYK%RVr0NAuv3!n&5wtFm-D%+c5=wQX^H>W77^JOi&V4;i@bL z-x${FT2vWVllWj@tY_8xI=)-woj5tD6`|}-oau=bUixu_%<#w_dR-QBc&;L63eBT& zXH2aLvcqVE#%0>ly*QFhUch=u`YRclgdjVeWbQ#nW>a>5DLk}<%ANHLuCi&`f+HQPA(W;# z(YEzW%g54BmWAe_Nt!BjFTQB+Rs-A9j=6wnjhQ!374Iq9!r6x6+ zgb+s72Zz$&m=I)B-IPHBfys&FJ3m)uL=UU@g{1Cxcba(WRZe#cX%6P4Oqcd*mKw-U z74!_2+2|MGNM;JHem9T^eUq|~JpWkTT9(M2{gzs|@xpsJB1)>yPu?V&nzqed2TpH2 z+2tjr(0bI>@1b_XEecfuV-vN?Sm8<>$)m{lFgA`-WWlmWMrV`Qz8@<5^Lly+(36l@ zTRJr~FNYd9M6%(ld33cA=xw!CCOX;1xP*Sqj{H7{t?!^D9LY#aVJK&?rD4W{nD2a- zw0k|L++(|xLsiOH3-{Twshtg%l&Or%^c+3;mk;dBda~2)UFh%VtQ@Z-O_H(!QKBH} zBsqxRL$Ee&DY`>+4K>I`%BQn(BOMqnHZreB`KY`|%~~JosT^dQdV*$^|DeV;R(5}W zZb}gYvnSRJO4kkwU7pt0O?4v14BH})V-85huB7Zh=s8iYXy z^U|R49zqaO6gO7E_q;F?Ij&DxQpv^&Zsj_!=_9XvYi{)^iM$j7xdCjK?MJRoe)Q~& zJ*j}9Z!Z7}>U$NGpfC_(aE*bPyyWkmlEU$$RJHjQ6)1C{kNoNDS*<-Sb92YtOCOE2 z6Id;i&lu6{J*2iBv4(5#>#~E5M8;C*bKWv1DAUkPE{;HNu07~#r#aAaF;88Y&V4?V z=appAgKKq_|J@1NV4AUsGSg?;9I z>0ZC}MsIc>DK9d#>q^rV9P}ZTJ~(cb(6Cpp>(|(Gbsu7DoRh1HOGb9Nro_F*$5r5i zwG@J|$Yj%BNTgFNE0g=0`^=D?G4La@5=i;9`yM{trJKdy4cew1^sKxy726?w*T~tD zZQL?F`K(~TpR@6D2{xd4E>J@vmVWt-?|ID;S1n{6ejC%PW74aKEJad1RqA(@gu&GH z=K=c}^P_X5W!rky5S&iTRAhg7wwHK-7O{lh-z1ZOIM9_`XQX(C-d>TO-F7K=cGg+n zPNt1fX3C#X5~b)EeH~rlp{J~7 zU~V}Fl#JzA(Qnrnw#4O4DJa>bPP!fRvck4XP(qNbt1tgN%&iTpMGdv(Gd1n#$icRuS6!Huv~YG)qIf zNPZfG@&mnI^%ZHxECe3eND85h#j+lw=Gt?-pf>s|dRq68EGH)3-Jv#|YttVq8yqZ> z-%j7mTLV<94vdj#9fcWYExE6ulqNsnvC^`=DgR9+eEOG2%wfi=`YesKNnBd9k!AiOSyo%fSvag;smk*-0D50+#<$nr?K zLMiJ$=B1_3P@iWlK!8VqVs{ zv-K{!y!?&jh)w3blPlPH*O``~+gBRaxd-C8j^ucLq9=kX{a`60IwM<6ze=ARQFPa< z+i^9`1k5;|Y83Tf5;mJlLA$Y}EH5wX;E zg5|(0SMotLSDbH|po|9<3|w{#K-Bl#choJH&ggN^orP2M%u=Le({r97jp=FICbIi8 zXYmJv?90`HI!l6${WvgV()9G$Y)&gnq|_5D4KwW-QJ7Y&agTejY;{dh{m)x!gPpb1 z>oVLEu#$!;;Vtg5=PqZcm5={PsPj{xw>2w7RKrXU|)?z+=ucA!BLCByI!yK1 z-)dtW4T%3?`8XNq{QnhWHZs;4}Ji6O+8E32xUW6kilwmfd z1|C{8%pWTad)i%)(Jb2INX=#GsHk4QbH5y&2%fKd4k?JLHJ_{PzEe&Q2Q#%t4-QonBMtRG) zx+_KZEzz1!me)?vRr&v_Uo{t9+;D`t?2d8fCFG&@Z8znKSbk^W`6#xM@%6@A!;kZc zw+l-K0wi8KngIUEucv)in&tdee7{+?<@^q%8&&u<>s(8_a5wJZckF+61S`2S#j{1X z$f&vSyw9JoHmy~*4#bXFgZ)*uQn6=vw%&fOE#CMC_%b(!zwvssJiYZwc{f%5#-pCW zQv4X~jrf<+@$S0&%R%+|W;0axO%JvZj_)ldC)x&G zvYC616B%{dnI!%v*j&)F-6W6GBCCS0o5+n;-mKgOfsPyXF%0iBmeEmsATn+1*7 zH~Kx%^_>Adv1z_54M}oowp$jjHndrN7_qV-K&#=`^nonP`(Fn;^mRG_aFkxgU(VL&6FIUJHCzKJKry;WN%mr1Px_c)QIH-j`&*$#C6N~p%thpm7IADi_HRlV4unB*EtQ#T@qKB~D4n^Z&TQ`BXJ zrEk1MC17*amg1z2wxQ{ajaiyYQpZ7CzH*`*taQO;Ky-eHx0bhP95FIw?_ zr@alg{JIp;6Er-kD|W&1SoEKq^R}LVSrgAcEOVEFnkH`4(OjB^D?l5Ec@#-bk+4CP zQ=~_;&*YJ;Sf4FlR`c$olkkCOyRz22baFO^+fs;|Pt{#0qI%jPpSAtuY=mjJ@&14- zOh%oty7Z{D6@A3Tm6p%tOM4>T(=I}iV=a4vS*6O$88lT(|sGjBLZEDU)Q9a*Rz5dh(K#ncPgt_6se=IWxR z4T)6~O}fe1cTOysA2!~y4jqZze)J|sXuwmTsh3>It5IW{uRJ|H!bmAW@}?2V7Edig zkZTrtm^zy2E zNzRVXyV2$!c;{;pq*#dQkxdUmt6g;~>OL>u-F@%@aZUK1<}YiL9aZSNJf(1>^RJ(k z&~g-}6K)J!UGmdsFEOxFDea}a(lYDMw)kRl9lGg)6nZfZcovb27lKzBIu))myk_ym z&*+QTjKCaH{M-mj1ihrKZq;WWZ2GKUqu7^G7>@hnp*t*B*NJLsLsMYL^KowGqxd$q z>s0wmEjU7#u1J29WS->5tDf=EdaBftcB7P>+o=W>oObD!ZklfZ{TcliPk$;aG0H#o zQi+hh>B0Olm9~rAqv$2#i7T(PeFI=^r_y$r zzrH>ogYF_e=RQ-k!(kP!^*)QVnNrEidu;K@;@ahBH$3M29#)yo*-i|jdK_+$gkAR@ zy%y#S@~DI-ea(^eoWZuZA_+^F%#wO5yr#A~T`-wi#xxx<cDodvuL;Y3 z>CgglxtzWpovE>UX=nfTwv%LczQ4cD+MaukBv*14>A43l8%78*izVk1iakR6hF3sm z4);vI!;hAO_&hJSs{ZAfdW!Xn=U8vlxR3a+teQV*0S~A*5?U`58;GY>oOj50QbW0w zAsgOqeN$)sr#e509je>Y3)LgN{JPI-z>-&HQ641#ePUs+p-( zBtMo=QF5PZADA`{?({r||zeq?)aAG*1b zY;0N9(bktiG7b~CWIEpNuN;_lDR(1ZLzMOuHw+KWjuj_qdD=~lS(p4%1n)J5bl0-I zB`V@LI|OZp=YrduO|^EoU?rh$!+8=(u&SX*S8MKTGuzQwC1D7&miR*^yxF?_oJ5=T z($vFd&h6VSm}{uexkVS96C+`bFP*fK6K*ZCX%O3I5O({@m3y0CI`iS{gzp)Zh z-sRMmgcZBFM?RPe#i5$}7L*oWzOl~8rcF$=JeseDNu%ABP&8f=T z)>fz09CPin1I#9!4s}BCm9NMqxJp^Xlya}CuR5n|H;|;Jc0YwCA(ZR6@Qm}B$o<5=kwlj3$3+Pc1cm!68!6i zbC4X+@s>WV8ASx;;oh6K@z%mY2cWH-LiG_RtUKbJZ=O89E1Vd^qg6E8E>Bm^;5>7q zTL_uaq>k(jAmVu1}gtFVMI<`O)&C=wGF!@FukYwL6O*tAhWhJ){&=<*g8 zI|o#-CX3Tr9tZ|09{GDE{@wrvN_Va~Qi$3W+dPLoifrsX`DvIbB&clgbRj{)kRFL= zL_9Mq?Otg}k2-<4+R;~EY28jm!e}ZpzauHZ4u7_$^*KJhfr%q{on2>1g!)}1mUR6J zqiKG`+7Q%^r8nnyrDP8$l{BVt@cwSP`s}#yF8`_N>}?rH_x{P1miPTb%n@I*umQCi zIHID_!&ch5vc5!RCYt5CQb*xNZd_YSXO2uXby+5UD#ox)%5@4poOQ^r0yIXW-C@-f z^^I6b=HL=a-wxHstuDwq(ZFbFI{s50mzc>RfgeZFZLmf z58K-ww07U=>mojF9d*XIoB2$A+a2Wyzg9=r*sY})gB_`7C-yh)bTaShiN2j_Gco-x zrM-(*r2)aZoM>CO)PE~_IsQQbH5mfck3_iIc_w6cww3W+)*EHx27(5pk7(|rm#`VG z27Ssghe4m|(qBux9D;VSE$v=ycnv;Zrhr1_d-2Ravli!IgMVF`zg?oSRNaHIzl)${ z8RzNPS+=Q%@H`xZK#>Fe?(_e*;-TdKNLU6))^Vnu)oe4zf7i}*G-?e=XWN9`D#Ogq zzZ3?B?1%{Hb~&yrMDPAZ;>gv92%59{eB{^xjr$xV#jc+DY`vh72~z+0+hX5_WIrR5 z{~HGakk=OP60iB|LWU*wdx0bDR7i~E0vGqg3rmjwzk6}i06s7eh&8&n1f1JkXK$Kv zNw@D@ddGT{+*OeHDDG{u7aZ#qnlbwX`28=8n`&dWq{}j?ZHnyLj0GtBf%EchXqCLq zKbAg=o^ES#5#6E|~Ey&q3;XYwYk%xJS8x zat&nZ=SwDt<36d-HMh>E;NNSNWp+50Qs~yV$x@G_#m#)>}&ar3U>~ zK|}~MMVPgBoYZ)qN7V2+owXr(CB-l3aLvEF+?LfvW0(>{-o)3yHkLlJWQ}1tPo-IE!Qx?1mTw!FE$_tDhDKdUDtS z-ifG#O75s-PV2&7TT6G05GsvXe{M#{xWY_R1OURHf!)a*j_~(ekRdT>iG*Rxj;FQp znXT(e)JBJV9T7E~a*H&zm9~5Om40wvdoD^xX}hJwwv?Jz)~9LGS@M<-HP2UWJ=$1? zHOpCR*i)90?~Kb2x%iysZ>$$)7O0y)TO_~lKv3mpB^iO;^4l1?qV;MqHTh_RCLD1$ z@=?)^3&jJP#FMHzcBt&l$?`b!QJo4n;v$6;6;rBN+K*dy4^IuP?Ry7DsIIRwDcOAO zas^G@-Pkgj5SrKLsKdA{y+Hml3~Xri$2Txmi^*XdduJBD6-!%X81lutUG-jvo<8xV zj_f_%Ug(>B&|qQU>9y4qIqm<6i@0ivZMlD~@KOAg&?6UezdpYLnkv>s)(0#~k(l zN)B$r`BGu;cEzZ`JWLC0cv@-anw;I+@f$)#0~%l8xSjZ`?1ba{L=b;oyjh3tdwfDcubLpI>!0U6 z`0h#lKuZpOL&@m*$o`W5>yySppy{cwN874>w*EcoA-mt)MN=9=drF-hs>HYX00|Sf zR#UcZ8i2Pd=z|6Xq`03 z8II`9FQ~Yyva!vFe0^r(dOah-*5bKfwu`#@S1lJGg#AQIb-7}~9kP_W4SC|MT&|Bz zn~zm{PGfk~Kaj>o6A8 zH6^re0A0W%G2FtEq{91e)$cy{9m3U5+>WV($mfM|aP?JjV^ND5bP}dDptXgQBNiEV zX@VoY+;xfBkQa$t=^4bT{#mb=`Ui*%tEmHFJei5N?Xsm7RnvbEv8i5lTki~e3X8m{ zr6ai&@q993kHiO&DT-A}EtsN+(Ko~z^Q@znb|}m!Y{lw%zkT((={dV@T+FefOV7@g z*OhKF`)R9>;=~8?t)mlri8Jq*=PpC{D&07v2AHM)9N&S`#g5+&kX_Oeq?LMR_LIcy z(rl}KN>yKuiKqehc<3PcqtYt31A1Z9-pE71JPXw9wXLnZ`>o=MPb5#+;E8FV^J{kg zl6*v4-_{;_p|YSes_3|rWz(}ye~bm3>X-@;?YgUZ5g6^glvS}+rgvjRy^{JoZL+Vp z^X4uC#erhgNn_9y&l!IcGiEOjcg6LsZVi+8xKz07(Ry@D<#c%c@i=7O8bgQEGSlTS zIAU%#eVN*0X`wL7ICaJ|&yq*oXSrL+QC;Gait*y4b>bS!vv(><_|zXq&A2$ay|{*; zJ})VleS$44ndNxpKWT*G(6OvE=Z@uT$Y=l@z+R+2GBZ%ecfb{!9x z^vn;!^P?7N*0$AJaV(#H#JpzpM@Gh~Pnpbn|LDIL!MXz+nlf2=q<>fG2jipU>{0bq zhDZ6+w4^_uoi}6d3dG%bd};q(skylOP$kCXkh$6~63USn9y9k1|Ib%pTlOne<{5m- zYaPkY$`L5oTFLNM8gBJkl4R%#M^Fr`lX%lrS?AXn9_4*1%_wk_xo{!bPi5FOEcquT zSY6TmUW zxUmf7yp8jrA^fKW$1#b#Uh=fio0N5>1Y~5L$)z%A&92;1N7X@|DNl2rb6q8O@l!`r zY=W?Rev!c4g(G}~eNw`>>i~gR3Rsn`rZneX8?f<%Bb>vy78;Z27H8 z!yXMb66RRAGvPwX4e4l2H=BKnoUy%Y9sqTd6AO^*9+VfB)bMU)x0&GVHyBX|@h?hd z{iqwWsHlUPUP-qKOK3SG`5PoyZfnh1OsPhIL@u;)f8DLr`1X)VV1=$$dc;5{ca`UL zU|O)^ewlt+TlEUc>60?_m&=e>>MKt$#w31WPBLm zd_&wY#S^ke=|=uXtpq~4y zr{(obKC=5UVoPOs5bXteT*Sw(d1 zuzqvgkI42@aS7Pudb;u+J|8a*I{cN=mq3r>YU(x*>d7u>TQ#NZN{N-Yp;HtIVP^?D zUgOZV;%QtPzz%`8IemVC!KlLf(c`)=nVFk3m2kAl(>@n;O2?_${fd?nt?q$6xA+9m z-8j2fzhfUbnfmcSLRg7+YjKJ^Ba-8(5|))(+U>qIk&u*9c6yCgNOG)vqZiKnXsZQ% zx6*L!1yGAF&n~h@N_T8SQ{{?jp8gxIThvgp6_2r6?NTK8SQ(Ego3>ouyNhXY)vaAq z8f3tjSkmVV0GYAFy^P_JyJvDE+9cyz>idkWu`EYkTuKM;O94^RsmbPt7Z1RQC z239|L&BQ7I7R4{AxP$YVhY_bCBjWpikyub4K#RO^NB;C@9;f9SWX>MlY5R$*d%UN! zZSLCr`~--~zjhP8utY7(t$;=(Gy#afZfA0SPIv#40woE)yzN$++MKVW2xYi`t5E&; zuR7gtIjbmMC)d%|1=nFuv8vC&2PU`vxk_-d9)eyj#kAKMV_b?avQ&iLyf$DIg&BA~ zcM^U?d|lb|V667L#Qm2xq4y^*-=B~TA2_ZE4RpUK0C{RSH=Po^;I7xg2VPZ}3wfJ; zEK+Z~_!u`xM)`)O`dhz&wa4L80{uW2w$Xw%C=A?A1qNyue>I3iR`2uv~ zy}t!}EE5{ptWF5zF=W2T_1n)%61mq9wlJ9?6m~!7XK<+5!kC+0X`wJvE@8;J9_?~v zr_zlJJA4ouo`#m}=L{o@PH$SAqIaFLV}9GIJKo${^VY($NMXvw)y|-DFWxr8f00w% zCH+Rr=sNd&PaHMwO=;!%*E+jE97o=nt$$m8PnYa6X8YcF*we{U4v}`>0s~RMi4*t> z@dddz7dZ~stjKIc28_FVvrrk4$r2#MT}tzVYUzhfFolt%ikI$zAXq(k{CBY0@4MXl zI_lnA(BLvA8`xWI7eetuT>a1c#E-*PfArkpGeGE9j8$KTc{#WxoeUZ&z>F~I8Be=T zX<6k5g2pE>n5w!^_31iXd;#(!o5GPm@aU+#)G1u~UeBpGC3N$S7;RuB&AMLz)@K8qo2}7rSC{(J#u_!GP>nP zo6N?B$Bmf29Pe+AymGxH^ta)qs561M!$r}w^Dbd~6M!+jtqa|Hun$mFJ+PoONre*) zPXFH`7+Z+54zl^(cw&bp)kJ6#AH$`HzYNW*v=`nNtzbdDf$%KHr^Z;_5>w^lHNc>(NJMXLd>b zQxhWAhep>e++Aquoc66g3rEOTpZx`racMCS|1Y81 ze^b43Y3IGA*)O|mq7*q@A)Z_xaAfMQMb@c?4=5H?+$&M~fpz{YgP!)@g!lsLi;P3` z+)+vW`w^I(>NunmqI}f1CEHkHSioa%K_7Yjl{M;?tyQr--NAjIre9j)iY94@nW>S$ zh8nnOZzW?rBYZHcYsA7?6=y%a^!i>21Ib~;hm1VW@&5SdAN6Qun|qrD(7Ajn3iNg2 z^+Tq22_X^8UIuiifr_)?jS&5ZX(htJR&1Pgxd8ksOYVdaW0;W5*YfN-4}Q({sbrdo zceQk6cU88~gw2^pOTY{2^!GKhP)`rZIg6pg&k*@ zu7%@E%IU#6zz@etrlCROmmK-rDTFXy(bB|F*xQhh!`JfYglUJ(&6G?-WM#2KOd`sf z2RV4w8;C^E_`)MYRLdZV8eohLAb_F!qbw(j3xn`SM6`&BplU? z=zm0o>Y0Xrg47x32;iNq+^DgGHwE2OZ{3BXI)kOCKz>xv1sz^gkjk3?yZ|nj8Y6() z6--G^AQL$`#i*I`&4MaGc9f$#x19}!L(i}vT~5A(kggo@hp0g2IdExugJa&(0j=;9 zXG6d7X5cd2TF6B$aU6z#r5PB4a3dYFi%|#VyW5Sx z;LfQ262NL(%UftP!&<1CUDG}S<}>yOI`d!g7Wzh?F)iuyZU;9?n?QxWsZ8TWJr7*R zuVLP^7LIDk&kZLw3A#NUW@NR~_%$I{N9{yXadf)%O>wBeeeXPlqjD-039NU}ic;B2 z!cjYo3a2#ignm$9fqoj_Rha!`s(_y&&lYqO@q#Jg^5lF$rd${kJohY(OB}O@=2vi@ z2+PNLk^=j9C~}UycVE~?)VXv)Gt)va&PLe>eY6*3k2lSrGPVk}-_@{>5}Sp?_RM=j zrr4>d9vd6f!4dp0q7|wqDO!y)IFz(KJXNGxoz(?7KplOzRXxR@q7vJ5fc7sYLRNG@ z-!S2F?03*NtneX-z~lze5@>l&e=RCOq-Ey%F4Q}26lXWkSOqzEN|H^i=H&uHT~3jl zSR8kap^dN1GWekgMZ=og1iNy!nuL@UvYcJT+bd}>b77qH%beFryr_$WEAv zN`%sYCEOGoUbA5MBuSa7*EyDf6A{7PRI>+QNC9 z-jd#H^te%L7PF9kthhu&Hgh84}GF} zGu=n1xIGuA(U^7y#Jg=;8XDUER zS0?;`lIBwEe~)iMpAWrEK}}SazFMui>8sr6ehW7x-?*B*$>%cZl#@u*0ph?&%Efub zfQ0fJu`wp!4=N1PP^ujK09Ih2ttmee6VN5C_~P7~qJO1Fr%hZ@!G0UU@+-Laz{n0q zd$3j&{7BfMU)Fvv-+bgcY?^ctw#*wk9+J4a-|yn25mHiguSk&m$t~qhbM1O}`^DPSeHvSy1 zXz}T7-^(M9Th8QD)`dl~Rfg;8vHPA(3p)ZFmL>lFRia(LM*<2|0@xi4sl0&|t2y?a zTyyBt;1r>(vWu`qm9$psAL;iYZCv63-Br*wrms67!!1+Hg%8Z27XAL)cPUv58u#t) z?vJzy+o+g!L-Ev6VQX8M{)^&;mjF|SEX~Hew3NSwuyX|R1)!m2;ktu`K z;jyt<+uG6>w4xnC^fg%Enp6dOvExg|CWL`rl_W(vrWln}?34hlv z{+3!1U|-93_xFW(-imM1>mAUHDvx3Ks;n79T0D^)!NNZIPYzd_e0|EQzgNIPLsl90re^l8u+r_1K-pO z@Q;rpF|kYt7&tqOJ1ML!cKWebnt<}iz;pDxUg3Z#R+j8cpS1VwR`@3It|Yc$HMuAU z5)i?1S=y5^29U%!8c*)N?| znv@rzUfRo7Q`}P=Ha(F3ULPYS`wEL;J-XH7HT}+#+g*kU+GUOo-_i!C169|fJ*!@vtWf5(j(pGWJJX0q6LJb!Fk<#u79Yo#Bi1SnUzD%oCU zmV3V1+kETm&F0j0+Z6M%e;`f)>~^V;fSiUQWdGC;cLbt;jcL|Pr&5z4rq+6N)$FNn zDWfhOnI{w4#EpU_(O!qI0GpmHyRYtt+rhyUni`Ql(G+T*XEY3C6}hdcEf+U^di5WD z0EZpd>?5#Z$HqTN(+P9?NeS88@nfcWJsc49ilOTi&2*9D$7IkW!DCOp;Dsvx>U&QFL8@W-Jo)`7Y-QM&K>!YqfrA4IQ zR+$OR^K}9d1sjPmg>D0pd&hUWjQ;BGLGPzQtN~F8qbmwmuf6AshpYef5~4hLmHrlT z`X)t25(o$PdI?a$A>iu6ztd@Ke>-0&H2b_xAV`uS+FCeS#H?HW4LbqR8pL?R zrr*IpJxLL?Dxap-;yBv0g9ZYkV&TkHm3=TEP0N0hbN`W2VCSuzeRqJ?%jYUCJqOj%!5_2k z!;c57b7=)YFKE<+Ay`s0r1o1&I^}1%E3-d&OgMe9HnromH_Nk3^T)LL;RRq5u?m2- zvXc0o4sKmpo7r4SGX)9zQh2`x=vPWS&+(-Y;qx{xf7> zs!a2s(m9LYsDC#`BLMqAW9QWPXYza% z&YzJ%+aQPAKoa+ZfajQyVJTtgnIfymSj9A#L9b!jt~P4?k4GAMx)12Gj_!FA2+SX7 zeeFG88vM}6fVF_tZ2{(TDu4=VF0fL$`$*qfsn4Z>r%0Q`0otYKq6+qhHfE?|aA1|b z7Bj>Uc$%8Vw%rZN!oGxW8X0IV?3(NZjfNsh+CuRi)-wmv&+q3%2(U$sj*xMpJ<8|^ zGT# zA;n38rfCw}6rD0(9Q?q_ z)6Gb+82G@5W%`}gk)goxmmuO$c@E!jrrrz10f@_ZT~WYR(qg~ifFkSrX|BG4q}+)b zioR!sG?uY7WV&eZX|Z7EH*7^kVVZ4NL#gzv+JI$r%vMVcJQe%(r|$*E$<+4p5D@o4 zvNGJ1za`^TfuQT)WF>D#bC1fOu?~~{6#8D(?H>B%;yl7Em#&_eQj~j`g zKH3R#N#s-W!?pqaaYkeZo{|D$Yxg$)r^4zTP2PlfALQKn6o@xp9>|Hx1F;&@ z?*;Qu+9fUsnLs(a^8FU&#ebIy-(dU7>_hcY@zRgOwby=SV;bZbPj~)U*H;FvgUr39 z3^r^6ZK(d`>j3av-EMoCnnk(KmD&&BL>LKscG2Z~-(9&DGABkelsS;Rsc3b{S8(T5 z?~cE#1cjwpct=T`^^e6CsS#)@lmpB2SLh5bCAdA?y{|dx>%ig&)(3N~|5jzW177#I zCN5>AVS>s{^Q+w=^jO{`_23y%XL#?L&Ay!D^L^fbgRMiKIwtxWK$RSYl~7SbRG zo3s0eF|XV?&}wO~yzmPp=`$i*lh8Aj(*avbTU0m<36>q%A!_edQ|g=2bmOq6Ai7$5a(dPc$8dlGULGMjHlR=P1K3@R0AV2;0DVZVuO&ZL zPM;PatPJ6p3__||VY~vHQWYOzE7nqhpP+1mo@Q1A`7rqb!?e}_;nAqI#)BwuzB``_ z0VloL0^am6t6VreOjnq)Cx#)xPkFRGXUBV!c%HNp#984LU3M4`q8C`m7ATOIuL4?x zGh;nS4uA%QSpr@Ep-<(gnRSL#fud!;5w`$V^wTiWtEaAt7klRpnCJbAYa-lGpD`)P z=MFXE^~o@}N7J_j(Lo(k~wlm);j zqO1U7bKCnrFY_~`IVc`_a~69OQi zIfe*86ut$Jn!)1~0A-j1phX~>5qW_qf^-0oXP~i?ZBslg$xh%6L%K?}IFf8O_#{Ys zYb<;KMhd}&wU4k_%T;)K&5?{eevQMj?jl!yjRC+`x1|6M`X4_>1&#oI>dpe9i6RUU z2T-Jfmtd?bbfwQ>ssL_+0GLlM7_40kBvcv6MkUfkv($lssB$2gK>jj#+q|ON(+7u| zkrknUU728deIpSzBx1hO0#FaB0yH@HfXHkF;30&uqrws>w4&bwpi&JSI&@A}^b~V1 zW3oV~b($*Z<}~t;R)Vkk^Y}-JJCrP05IN#=;dm6vX10YS%%4XWZKotjnFIl7=a|bu z>{XPA+$2)YkUanps~TC7WE(Ah&B0HlDCt-*Z^3QAL7NSLI2p^k{ZjE@VSw&~LfGfl=1E^ZS)w5s4SVM~^$_7OIPQb2|ZLrwW1wx}FIw*)}1yJH7^Ig*|-+2yFfCHuvJ0tbKrLm5D$X2NDq zFa;URH6>AoYG;F~yix$^Q1KAqXxY|F!Zv%9K2XICJ1X=hFj~>QbO5c+r6mpkMK}WL z?BNGM-erIUXA|JB0+kH7LVi%4lmrt3zKH3?SN1^e5np3#a)t3gb1J01Kba z4C4b(H)d)Fus?qwp_(28Ser&HFfN&CfH9^6Ql4kPB%U2$65?|(39%y_7|CrBlHOnx zARJA12hVY`!Iw>t=`sO;8v&I2w{+ROs0`gHNtWvfQNST$=kdmh@i8JR7sclS#|#Ua zeI)b=ofVpQ%zZFZ=0)|KC+c@U^S2fP#C%L0u*146&5a7W1ZJ7_25-bQA|@2Ud;)OmE$KeDA2xc73`W-xyQ` z)x_FUo?qH4-$<+oQS6h|O*ic4PUKDYRayz}DnX|^9f}tJD7O-|7gp(Ru>~pjKx4%L zCMeu8!VX$o_5Zq~s$wJetPlw#M=`EdYKV6*`IdK%OlL#01vT~y~pq!|)}E%GKWcW;5-Qz8y` zhg@9t-vsOFK$9$JGJq;7zDud>eb4K08_1kro|riFyY;bCPl3mY>L$m8G+D(;D}Zps z^_GHyCQt0Y$+G6$IO$~jLR-ZXv{lR{3{fGjKc>!aYy+(!Pg`j~90(oPtHt#3`4({z ztOXRK=Hw5A1U2pM2G259Hf#|v%I_RmJ5DZ>B_>3qDHd?O!81VWf(88bbG|ZvHgEp) z-7g7vtx$zkx|T=|7Az`|HARUyW~ue+hl@&W;`;+*1i+u@k&FZ-A^^ug3I3my*|Bko zd8)c;9!v}_Ucg_q?6)ZXC}+0f*Ld!N_b0RRZ^n1!0VLBZjBgCRx#=6$p6jXtdV$cY z{T9K+SRl040kGa6uMKG+2um$Vz4THHCnI>w!OkQr~IJE0< z=szaAi7AL3boWJt8G|RL@b;!?dmn!3Hx2Pa{q1{&K715k6>tr}*?YY;0Gs`8kN%TY zSnV=kTiu`?b;^@^9_0u${}QxczG34-?Ot zQlUEFX4;8qjJ6M&+Fi8xzg2)07hfD#=+jbwk^>eiYTz{#fNs{0?eJbfC;|M#F-S9@ z7QS&C;7)Ph@84R=54WLd5oswXA44#*Mj3hvZ4c!BvC?acq45!E^9LP*i-83yH0%T2 zKG>rF`W7WFMFA+CwItxxDNoI=FaR)C4z2ky^&9=j5*f6BV+)MjIG`(0i8$%%^i3o0 zelA2uq|xP`YXKdClM(lN6GD57e@uN@|D5Xve-G3U2sMoWlztM4*^;qU@f#a-HiMS{ z(A$|MfDdI31Fb{^=Q}Pg`%$o%fKCv>Qjs76UBJ=QHQjr?TaW`%-&)lq&HxJR&>|a9o2JnaIi<=@fSc%ZNPYSr4A*z2X^9^j0rVnCSc6_vHAk^d|Fv|J_g&g+K{yEhb6oqqe^=0RS_OE_4xi~@Ry%A3>K?Z~ zHo~NZncw~!kA^m@SKfNtn~4={h2wDs%*M4%({w)r1i|Fr5QKADRo=Akthfv`%@yxQ zopUdXGNVP+DL(%jZDjCeRPOobkBNQV1zkzK`BW$*%Ia_Z@6Rst@ZRAgYqji|vv?K? zO&KiBfSIpz-p_p(4APDA?1RtSm#_`?%^OjIeNQrX{`Y5-&zQhx+m(D_K`2~GVYK;5 zGYge0ezJHssE`e1ACxkj}9TWq-kLSFsf@`ME+L3&@9CXV4LTIa~4NlwXPFqy)0D+v?l3a@s*1|v|!tA&yRJOz?Ls7NoKM% zE1Dkr|Gnx`Kosble;(k@ivwA@W8MJrdbq(!;)l|IjSN+I0`6J3Y35@+3xfBh#xNh5 zP>X%n|4IC+s1*l3i<|C~%C6KRhxnF7U2fP`tN7c6e}%?upo;6d!zcA61qGjtYbyi9 ziN5+zH$AyBZv^|E`>hK0-J-G+p4)1H0fDpUKgTE$_0oh3oz?Xn>)Df!ud+v#NqR)C z6D*}2v$7#S70Y_o0%u)WS-JKE>%9_OWIJT=o5>xG$n-Dx0- zX6k?F-nEqRKdh?9s5dp*ZBJ-#zFyjNchQ@@e@_gffY@hSVDm}MAyXT@rqdoR z>*S)14l3G2UjC{$MOF8+rQO80`~7|W+cp5$Z#mdeo74ws52u!Eh_u5uYXIFbU7A|B zJ_v6LrSVqZNnXQFmcqmLs4J&(X@^CZZqJLB$H*ZI#jBv#hXQe<#avhZAV7)gJ%Uu zfkEql$h#T3AeDz!B8$kBP#S(Tzd~!fAfF96d^+nTU~O=?am~jf0%P^Oe&T}Mf~Ho$ z?dk2pm2@i){N!U=t6+1y?&!;FUZ);09H)R}bEn)%{>_ylrN7&o)qA*uLr+)94b<&Q zDJrS_touSJD1^se+)fy2!BIzoxpGeF{wOgS4_EC;^J=}6 zJ&3M#B_I@+DI$01x>fpQSdh`=(QN_(xz~1DXr43l7LOJG6EQUHWP%eyiM!|+QX9Li z_Rma4xNBX`_hjt6{yey2`x|(^R;*9*vjkvfsjSqycuS#ZC3u7AS-}eXDJ+d8emO-1 zMBmDJ`;Zn*J%Y5ZHn9O*{z1AQzG`&Div9*7M^3!waxuh_u;_4m{8K}hhJCzmTZw~H z=*ZZMFK36?G5ka#)C|)7Go$2;;KU$Q*V`{z0Zopu6D15V-8VS&dj@Cxfc_(#Lrw{0 zsnN1k(63I9xV9^)BfXjTM1J0>czbnjUX5Ry_asd+s=b9O7UT`Ak3B}`hqZ~))e94vkJW%HI6Ec$-8os> zra)7#4!lG}`(m|uw4`GdGyIoxSW($ut{sqBZZA7Jg= zN+ZuqO`rJ_B-$oec_+B}QbK$J#8^!@s-*qQbu0Q=gdW#%5Uc{cgE-ta+z&^LdZR%T zAonR;I|_ z_oT|(Sn=+vPa@fULxD|AJI1TobpW0Nzg&Xdy&Co zlQ`G>f|~QJJlMZ;+s_L{zM|%fW~~y$u5_%Ha{0ey=RH>b!?L`#z>sWWOrPO_&B^55aYPk(A`{zRRtsEB~n+=LT7LeStTnX0a^_~9P#x9rBBQNE#`Uk<7t)wOa zH*t#wHhuvs$lZi2&z#4?@}SO+@=5DPgZ5ZAmmHr4EwsAmC5>9@`&=m{xA}bXBt(>O z@hRGYyW!&1OH`()h|3TOdN1FwS5wRH5NriW-#VuyEFHEgt-N-hq82CTN5IlnK#hoi z0-q9KD8vXvMc(=|ZT8d?i*DnV=xs`!%0PRG;kXmVrr|-lG&{M!$pGWE4m*6%6k#-y z`<|D10n}e4?4w}A&Fk}!D_8GZB;m^A?FF@O4GgeK=ic<|LBrZTHQM5iX%9dLfJ~YX zjF#a?vr%d0AA|{YQ5IxtpC>dD2a!Km^y+9xskSLXRo71XIBmU-XLnIZ9|bfOK+RH`Mn~5gdbx8s&Dr@W`6)8*9DF|l?x04>`B9B3)XKx- zkfr3nI^Wng#yVsXg!zu?(yd-|%lv{?Yp%?r4`fl@a>OsJ*{jWbsI~h6@emdS`jrAa zw*!s7zjHOo_F;aCJ{A3V+rPSG9jKzpqef_{ftnWD-P~9CBtieidbQ4(200Y3cr|R^ zNj~Y*Pp)yUeE-%Q)Q|$^kK(WP~A5PN(w9Tmn5%9f5Lg zf=ZQbmUg={0#16l>BXZyhoA2|pg;Y3)>#Tw^Hsz8_qptSv3?Ov>dn{P3Eas`bN-JbNXnEw_7@h{lbp*c zukLT#1;+QNtAi5edcWyhLkuOkXyY)%{g}R3sKUJHcK1Bz)7gz)C~0>;M~Za(3NBUA}=! zxZq}StR_PTKN*m`IUEv8gA%K5v%SI$;ufsFVdPFMkcF}pdvVe`f09AUnk z0&8-(+_{Z?qjD+E>ds^bgc-icb^#~h-lr|oQZ7wN>+Hy0`TU=)*4B}iNbwRiET+tL z%ke82ZH#Yoh6HO{b}eP(IMaCXJotpq-~zya#&B||D`i%DtLnVw9FU)FR$bdfZtuR8 z2_r{m*J9gUp|*u;x^u}+g%PAP1*4JI8BK^uJ&b*=M~H8_JqA_pl+>H((x8*@F~9+C zb&g`ohPMxF`ttnC%uf>+oe*+UAc8%@YBem2^yZZ(65&913?|ZDl5>69Hx4n1CP(7&md^{6t{h?Bo{g(a?{y?G?J>z$Z z&>hvV^y2JFYMbYMCYswYO(hn=7Z4s@6&RBD&Mm!=b)cvPPhEikd;Pyei)W&GktE+> z1p@y%SC``y_Y?72kCrRpEE3GJ{hgU$@W6@XJf052>17j_BSAv+@KwqBqrgQ8Z*Que zI^u>LOc{Ee=-L|4_XJ8g1z!Y^3 zV?h%KJ#qE8@!JrAi%`oV1M$WS-w8y#rU6@Sd$T;RXgm-5)KHxj5@JG5 zMZj)&av6*uPusK!+!GLw3+{pYS?5CBZ#}xmWNuS3>jnoN<1BJM`w`_FP2>MGp&76$ zu-Y31Gz#MiKZoFbf4sz+JM!c?QB6#L7vY5II>q8EBH~sUo^Gj6nlz&_y>5k|M=1Lu zVh6ex$M6H1E8hblG1^OweGtNEEhLOqD+YSq4UwFkyXAkD4Z-;f2c3S&mu+uptMA$& z3euPIi;sW?WV`d9&iWj<{ELMoq~o|WBxHqP722Y1=D4g#YjB{tq$jTifvOge zCdm9Ln)ImZxt~6hzp{d)Gs=ZCJz_M`L#xZ6*73ilAt4o+@6@cjL+OnNSo1vy^EO6& zF5HEvXi;|1E4*PnxL8T-LKZHqq(_(yCaK25o5TSIYdF|H+Yo}AG^!Bxh(Mwr$e=P| zj(x|rwS+P2Dow26ikHz!J5#YD9)iVcmHe8;P}?6c|DoeP3={f>6?yE6J3{Ncvn{S0 zArGDk0;2VIWK6$eeTX}B;^Fif1oO%8+&;-j(~*x0pxx@vy)HtChzq_mwbCZ%217c@H+eH@9rl;}6ePS3dYzPsqNlq?mn$2pK1Dd8{O!OAU1u%1shM}~ zaPlPxgz0C2)2YBh$_!5F%j$xM>ZVNyftPz~^X>JAOid?yQ*u;`D)CgwY_m-i$6S@; zE6y^g8r=7CePeNNk+;9Z!~1ETr7LAaSAz1H`HzPNpvb0b@zjb2WjAZXH)pI7 zzed;3t(a&4QM%we1?CHYGZN>jAAp5$rwIIPKRxv1g91ZqnO^c8&*Tb-2*?ZBX+|YP zNG5*y<8GmG^xcrEE6px8`ByBb(g?V{4g1il)}A0taIYh2904xPPq!0$?hzt?9ObGp zGkA2j(U5X&(<&xQb7kOb8vVu=H@E{oV5M25boKc=VB$AX&N*ui9j3`zQ?=VPr4$;f zLOjX*KWi3ZxVH8pnXc*Yp?_gVhv5Y-0)H|_CpB;nWctizsSfd~{6b|5;ml6Bf^j5P zYJpV&@I+R<)HnyL0>dt|0TN&pi@n*?!+c7iMJaKoXQXz=TgG9EY?ZR~T$bPK6rJA&W3oyMSa}Ci*d=hGSkA zFLeG4k}cB-m~N&Yz`)}d`z3QXhgOWdylhk$NjgKG)4>{)pnKjYXupnW*Mxf$(>!tb z%Ibt%ke;M0U%~UzyzWX*hgD&&7 zt;z?dMtMT`W2RH;=vCI!@2hr$+<-{iLpQ;}PQhXC(Wi#+Jr{4f=5C0+os=SVRm%_I zXea;5d_I^fdt_N+jiH_X5^%`aYf9w~t4DIqifS+{>zNf`=FX~62Yv=K1^`fN3ItJZ zw+C@dtT0(0L-UWUop^zuJ^QwKw$xwv-7N|sttHga_gl~3!z*;ldH$p)^Lii1i+|ms zQa-r}3`r!l>`NfoE@oPy8m@Nd#61}o2lUXH(SV`3s*94nW>K=Y6$?lM+bg+eCgrCn z67W`I^f3@OM7C$ibQbNipMDjy0unD$?Wrx6^4-)40yg}=WF6@=F#TOr6CZCvT9xB$ zky`my1ov&d;JlRGTL>inPP=M^gp=n4o#XizD^`i>D$w|yv76&qg5>%EZ{D$R`?Q5laQ&htk+MRdIlv~Mj~3a)`lU?R zga$UM-MuaqKHAb+8L%$8z{42^a3@fov~k3cHy0 zCy{&o)IgqwwLGE)3b22qUw$|)XyqBKCC*#C-(L@KMP$1>0%4Tp)sLGM*tjEo^f+P( zda)ar<0Ax5)3}{4G!5`xtDVHC*y%$(XIG$OGM3y0*ghg-ok7h+bF2>HlZO61b2xJA zQ`6tsm&g?HVW6adwn0c05$g!o=R!u`nc^7{Dd#;(*Np)*Z{yz2v#b$Z`Sjs71RN&! z+^#eD*sX^9?WOMUHWVhKnGW&Wr`gYn;P5m3(aI1oT(NZs4Ymqu$#j(eYs%#Mzf+|i z&Z>VRo`+2>H%*Nfyj%i^D6tU&CKHB4-Z|yVU4kp!uNED3%PibG7^U zl45VDEKR#s+*2tPykvN;m^m%*Pf`VabbrRPGAF$!0g!EdFN={qRe}y7l6wF*+pbjW z=T7Tbu$sg^XU$W`@4Y?t|V7V(HBirffnpqLO@eM=|w4~&0@|lC02qEgCsJCoucS~+k ztCJVvn+_)dE&XO5Yv>8Tz{c*&ncOMN$3GJeEWY@GM$ah68+M2~57G&ns%G$Xa%Biw zwYG3PZ*mZ4HF;^`4o|1=_4gmfWSJJB##t(;zhiTS8z4 z$=fro2NN8NOCe28n#R1OI7|i!W_>!pd3lM4A^9Q}si3v;=(ZqTYjsek{$Cl=<$%Sh zA%~uS%oJNFJ}B{Lq!tHeFC8_w#2=1Cc=15njo0&CwaSrXfFo?>YavE~BC~T8s`X#^0h11-w z4V3DpKNSl9tjutDz_yc9BiI~*_7ZeCmQB)+tamCd{UqNuVY>NFtdZm4B!rP)miOr) z$=;TbI(nMm);^tIyvWm|jNL37ul8Cvb~;v1>Fu#0CTZ4%Y|DqZRPg6ClDumtBLg)^ zsN_t7)*8j@75MwZdGEA0zpGxWL;RH}T5%C&DbKPI z1sDVb;k5Pxy3{Wc^{#+5yU3zy9ND(M`6=g1Qz-vzx!|y3`PMg5BhbK+&+*Ir@>6Lb z)7Sc*s~zwmev;2(CkK>r$t8LBY&B|Ky{^ubmY|XDRA{sy;2#a*8lek|WuqrxI;aRc zuF}bGT=af@auhr=J0G8spE6$ht^I5G0g>&kolo@NuNHOS>LDGtl8h(Lo7(srEPECQ ze;X)sDhxQ1BWYPv01*yQtwGXJQMK>Yi5#%CW$DF7mI$9}?Oi4F_8_5P?N3+PrWJlA z9#QS99~;30D*H!2d?T7b)dhp4PIW%^c3CxX5e*glGfHqXILc$QE@2aA;&O+>2SsOm zexO%ecWQC@EdXMV>rRDdOkf}ApGKbyhbtgkFhKw0g8oKn1)T8~2r>}UaZPNmagFX* z$};_r4t9GEIJC1xzKj_b>&UJNbkhRnHiYk7v*i1zAra4?5Jp1L=oLo2UZ=)&z z=4{jm zEM%Mw==BzuT=G`0fdmAGg+hT~PMY~Rc%XQmq2Y8GjqL8L57Q|<7mYr@QbPX6egH*V zil+%ouYwi;1wJFs-nO&Q1=A{;n^g0H*hzLaNJFN8O;A01%kINuFhchtI&k4!s}R0c z%|O^&?vI&}%j?*}r?_{vmJl^dxJwM6?{Y|7Rcw{+I2FFV?Y^+Ui54TsG)*z&vAZex zcZ7G2gm#HWjkOXv6BFAC7)#JaP$&pq#1=Rn?H2UAE|3#^h2N&!IH{_~R@2Y`MDQ<$^PIR|RL1 z4?~NMFxXhLtsfWo=DZp`0mV&`z6no+RCD{4CPxv)<(6R8WAEsIDe4a;Kw!KXx z0>S?l%&@97f}J(MPWnD*hy7>4NGd|+b96;K47?%~z$yeX@*gdJCQz$YTHu#8j*iI* zOj@A>!glupUZC-Pun;H{Cj(Y53oVBuqlNYj#1F2f!S=Tbc82to`I!uN5_#SASOK4{ zgcnZt?d2_%3C5?X^O5n2%@OQoBe@m1SUp>frD8$Ba5#x`g)?Qp>`4>!2YieU9Ze75 z_pfZ?*UWe35x1|uL)^X%{zbtNFTntZKHqC1mf_mW(ZSd@)M}+&?(7{2JTgYD|UhEK>ep^TJEUU`ogf8uc$zMwv zFz|}n)`PB4g`M&4!yV#bdlgLNZ~1W3!8UNyl$4j0%()?|fT)xoJV~_ZsGJEEUC4-_ zx>YjeSmWt$&VTxm$@b^PlAes6dHc2yi>h(kKu7mKUU%lg)o->5zT7TAe@s;h6z;#i z)P-K1O{ELYIBo)o7C)BHuR#yEz|gfH3DECx0U8hbE;Yh*xZ_z2XiJd82}je2`V0=H z9lhEzRZP4zf0)E~n?DPE4O@=F@5+rO07swcCVZZ6CY)iyUnVLS#i*)g!bd{*11$h1 zqIq>>5w~Z;-||BfCWJ6!3^o4^+#SEBqAiPfi*gngryz^#-vs78uh$K<;SBs5^csK; zz#6>A@UeZ(i(Tlk7wCn(r-aaE5uXJ;2qwar>#NW*v%NBh2z>t0BG|I>GuV9BhL76O z6&5va6(>Nhw~w@b1B3IHjG-^5JP;N-_6aCd>U{=Cax?r-Hvc0S-N%@O(_y-^gfO(R z)lBHXzzS0Xp}~PzK81!*7DkzI(TeP6+|X18mAFdn-z_K?3YKCeF#0za#aQFcgm3j$ zLo#@5Zkrs5zu`$=e{cB?ZYY1;na^Y7=2gsIry6xo@K3NAlX4w$QQj05JA=4ghW+t0e*kvGMA&u zAMP1iNf8b=7eO0RifOm_2y#s$D-O`ufU4<2IvI=wvP)zE^qBOrUkGC{&`0jPBp_!C zD)Z1UkRz+QO%vy4mYKJs1Ph7TduAEux zboly)2XR%f`}EZCJ8%7{;Du6r4}GKA<>2%uam_(}gQBOa_H z{_;HXOt>5{R$oNbeo<#omghpiGCG8L(=-^YCaEOK<{&!WpEX5m8l7*x3j^GC#9jLl zA|;r9$GI>0?Tf6nZ|VH;tM0kqrrKx5kVp&lAiY zgGe!sM~t0p5VbN_CI}NIR8I0_P<)5R3Q<$>K7`qNRn4^`aYd}a#u(ud;m`=DC}-$6 zOarxyTReiysrw7^Ol~efCMsxsMg?HeLdxj{b9_WI+ z7!EzuU`?Y_6&jXwOig91{Vn~cEKZvsyVsdc)j>4*mvbye6 zG<9osZ81V3pwZWQCjdbBKnlg7`HIF7EmeE`=+yQbd7MAX2Uop65^b)1C%^wvOz@4#rY!$v;CbZqya}IZwnP_ zCc{ikG!U90^!Z55bcZK?)ss!p=;O4SjnDD>Toa!B?i8h0l}$wskKKyZ0iw$vyI3nQ zxB~}W0X1NWlxF|vrGW1S-J3U)^5YroM_%C_*Sc$~fJ!IH0L;fs28GPL!^Z)g8xAcZ zfw2VZDns4BEX`99QNYsO0k!L403d2qHJ5-xiz@%{=x?U(z5C?I6pM>rt&zd-aWL4A zyeb-hFwiyolpPa{OdxTTFNc;<6lUu06GZZ!>B9ixD_O>v@$}?R@~!)jduYhlM0ZACP@1gxXFitMqL%uV`~BaoCbFm!&`AJED}9a?BH+U- zhzMZ~)!s1Og}(tV0HJZl4%h%l>XmFK1wRawug%n~F1(&NL=h?A<;Id{rq zf~`e0AoM)+?CoYD{07(&QoQ8^2w+rSx<+=D#x1XlfC$qAztt&oK9b|dennn4KG$!N zd$aAQqr@pUsC<|+c{XIZev6j@Z)vsff;v!?G=xW>j;{w5Y-iZtP=?vJKDI>v<78E2 zl3dOLGUf(+&0tn2m_dsmR<)*SUL>@`2>LZZ^cR~6O4gWjw7Qa7*QHN#ZQ}O5xq#j! zhhELj79PBVh$8mqcMze}Um^1n>DQOHP|ou7D=wt}V5M(&#>NL}qUel=h6)KK{u(K4 z>eAii|2-w=r`gJ&V7MZzox+>?DS`ye$Ei7}A{cr^b6tvZw;~~H8)_k0T@?ZnAM`i< z|BjDTMv{MFipKmU&B)w5vl43=tOX)Cj}J+hY={YmxjF)6P2OcX7Xh;171FVMJL2F_ z9eWEd8H%ClIK4Y5$o!{rf1idrpA|^y!_Cj0lJ%Yh#MR3&Sz7ng?Ajtg{Y@5k^{DBI zQZ^Xvjo822zkj=byXjLrG@C)=1p?YAQ&*7@n17g97@Coe7C~3s61|J~oUIJ#O|h3j zQR$kdo>iV-4B}uKf&y#96wHV#=&Bslp=36M+7_QO1@=+~qIw-PsA^02_5Cwg58iHP zRoX!~KR6zE{zwY*S7quX8HzKd>U{1fy-HbRn$wUbK5|vAST7=;o>Xb=^dpE%Lxl1>pl)G^T^X@$Y=U?hJXR=Thus%@Wc$)5{4534m#S zrX2^70Tzb%>Rdw2+?}h)@trWWUUTC_xJg6w)7kdsTEmcky#XB&S!{p;m?qfahG2?( zgDTP@80I#xF%O&q8nj>u7c8RlZe^?%_Q}!cqW%|ZrSBJmyLCgXFxx5C$@(F>*7`*k zvMtU!$a=dkr@j*T{NVVDf4Y9;)L-AYs+3=SeffgCg(TAUM_v_$3AM%j@eCAC?ExFR z<;f3L*pGF>Jo_?*1=^;mO@Pr$|E{^gvIN*;w$d&VabhYx4`VNGw5&%B4nc+2(CeyF z8;Q}h4&nxYe%?%Y|8pGo)BKS zoef;~F+3=EwSNYz%kFA)?Q`ji&)LOV|Kau=c-{+#{iy(>x)X-_aMep z1V|*^VEH+@FUAKNGZh*_yNe`yMa;2=uAGTPlAiAeb?h8!prvQ@@N+knp=`d^+co~ejdV14}BtzBc-AqS7pn?FZZ{gZKBE4=ywS8`}~&^Z_fr~>UK z5XrXst})2YBPAqW0T6HM42dFlTNO#+?}n9^r&C~M;JG>#Yaj)(vM3OqYuzIXEGt3e zJP@))A}E8@7cBN9A>^+)X;FLG-aN!D22TyW39H_T>(rVHXnyWSm})iw8HCb^Opai9 zA#`z-jKk2Y*cmM%NiCp03_KIOGbA85XKP!5jBVTKC1FLx;86Q$tM9@=QxSV5N;Gnp ztx#pOIAV210o;2Y_>6WhwcBjDRD*JCu-`zj_ri&skw1Uqz}9vsBe#-fL_-zdw(CIc zxJbk%bAka@PvJ6%tZH1asE`^qj!?|KgMRQT@ggJaGtma>Y~}MsF^7L~H&DvBr%c`O z%cgVEViR#)2hTUBVGXf5D6#42_Mm?e{}HXlnKJWngJpy|Eb8q!Ru(l_UgubR%K}RX zAj`SsQPnKC;&~yPS++~zZjR}<5TIM1h71>M?}KiNALzx`Z|fV)6!ICM5*t*?B9? zI5g723YKoz9BT^S7>Cq{9kq)by|~hH$gmc|!3SQSf7|*fD&+ks32(tJ>*J;7O2b7H zi49x?9Tl$nD>wk_sLoaYUrgLQw6~1^ys&%qMx+}E*cR5yqJ0+O2$L~xlGc^xAOrCY zCCsG5zs4UqwsHbv$vfx*eYJ}5*?n43^n2R&v>#JlxFnkNPEV?B+-RwQ5Q5!o4C`82w{!qfHYhQ`!B}Mb&R&&&CWnEFe=@*32C-L4 z-Y(XX^(W!iy9+;2_`v`##k+^EGdyrO#05&1tuy@6;0V$J?-()ztkQOVaJe#5ep$ak z4PtbAzm;uZQ#uBSsvdm*k3wG^!XU8a8xQ?pv0~*SJl#`4B|IHMjbv#)8UyxO#_+6b z5pcSWK2Lo;+vsqe>Y^)Fy|EB52gUX`$41y9WcmXBiMxH(`?E0K8J0l zHrAOl>?DWOg0t!W}6nkYtD3n!M&BCs+v z?Eza6X)AdnNg{uBaw>3E3^WHSluupp2=yh;iH(eQJ)pwlKBkL^Ip91HmPZa=M31d4 znvaHR<}B0;k^L@B8Zug%HAP+PzV4#2#P+3E@m4f)Lvy%9{n?gy{TsS8k#P%SA*RsH zg^BI^4 z0{#C)N^>-%j?UyIfin>i(twqS5YjSaFsrW#X$uHr2QoGUupmcGMZ^5cH94??NrnaA zc0LpKP@j5TB<5_jEamuey8<|m->TL-qLEcklSyGgifp+9O)ygbz%yVLYmNE(Fp>jT0^3G&)hv+ zFp_3jgq>%x9ZN64NM-pXgpF23GqBPx3VEMlvs^CXp*bJ?1tQYcnI(ku^5%Bm;jG>a z5LJXp;xi&Dnm{k@EO0uca6s|% z(;pta@b%AOhM!8DJbCi7|9vKL^!XmejbDB7bHv8Gl1ER9A2mNY|J_OPb%(#!Tdbmb zPy4u*j!v4i>j$B&sS+!_ghjoYD%&2a_u`h(S68qTeszqNAd{NCD?QDf zl+*o*NyWEv(SeFx3SsG6jgH*0+W1t=f9QiB^&$%1`$5b}`MsCoqX$X?Yroa|A69q% zEd7ev{$c+z88ZwnD((m=&v5JR%@VO@%`pa($ZaSXdv(*UkeDF`{LsvQ} z@cpWxpSpL8oA#$LSbnaSDzOnsb9htT%Jx#vV%*)VfFr3rm2Szn9-ql{)aU%6CwpTy zWX?pLUbzzK*0n4P}~Xr5Lgc6!HvX>)csvI zVDnS@#A)*3rDJ)z16XEW%=`4RTo6j`rHAooB`KXZvi2#u=Z4sOi~H-|Uwe8?GVSF( zOjE1)%!KgXo)kTEinsf05WRFSz9${_fDXp0wLAd1oXTSE^yzF_SsEYQ6WxW8Ez>)N z*0Q$9p;)%qdwuuj{D%&oSUdmRi8JK1oAHJn9_eDHEi`X;I_dA{j}5$g+T^!vl<_@H zwWaCrbebEHx6k80){h!dVeJ+-;S-d6384!1@=_YHdTM%J!D%VG5-!|bB<0-=s;mq% zJ51Z1q*Ht)pAlt4mY8j|z7;=-YL|eQzY)rArQM5rT5|o(mCx45th^TVn(dq3TYMT2 zVNNxFha( zYN6fZkfK&U5FDO3pFl)+-Gh%os6qnwb3EHvp z^LkM7MLcyO|0Le65a*p4H^b?I7uaq6^eFEqJ$lGR-5|7-QGHtA*K*A*sOk6!nXN{9 zEGACN7(N4zu#o*tloQsYI1U)eU`-eFo+X&6|`w!<+zWIYl69D%G4BR zlX9+(x{-QfT{N?0N=BNPfa^JZXz4qAW4`rcE46-U zTU{*0Oq=i!*W*31u$THN26dnOhn-uTdEvJ3VNmZxyu7$+djp2$ z7sgK7Ew!}Z@4vDxPMkn7P){5&ksDi_%j0Rl^MkDy=i?eLf>kV5wn3k4*hOHRP)U-G zv#DmdW%7S>pgR`bl6A7xXZ|{g;)Ch2Z6~keUNSsu68H8M)@< zbymRCy51<8ymoU}Ys>uK?#2-g-w9~YrkcddB>KeaU*j%W7%C(`;6Ht_VqDpQs^Wf2 z{v{lCwqqRSWWdR8W)1EYw3+-##{NOf8>!<|IBZQQ-asL_%iPFU=c>P*Djc@Bn7b~n zB;Ow%J1&>SSHGvbL91&HWtSIy$E(*B&dMA4yw~()NNZ8QeP!?q=8q<}7XwBm*Up;M zA(U^ycrIVX^;q&ojIOhr><4!{t*<>5mVW2^wLymOz4uDd-1vL(nTH=s9OdiePxx)j zST$9f9{2EQq20dA-+9oeP?niWc6Z0-<9hFfc$>>y=5FcOGtp;#r+Y&;=5&FDM;dWn zB4)#JiM&b{s0Rwn`=Ovmim)&@78YJwRN~(Hgc%Zvo4jphSa4`-So+ti7mn+{_fn+e zA|vM|>s0yk$@vjq5cGb&hOZ9dS&>bgUuZslyiXrA4j47ejKIVe^S%QQoG;uvXT`hb ziE@W$elL4!lP5nHS3MuS*O7VLCxiBqOZ~lZCyBUAYNqJcB>nzCyTU5I(f#3UYeO%(1ezgSveb~ky}pTdMx_;8+53+5D0vy?=khqT9#+K z%TgZk?|U|0tmOBfXBaBz7T?Tw%|3r5DE!E_hh3C7c`p40`b1rDq+D(b?Iv+>_`e?B zH$u<*p)IB=`&=(40atTgun^p9+Q*4EtkcV6J><`~1T5>&kHMfuKWd|($&X%^F2uzX zH$Pn7d33%Gyz5A0Z6NRN_@KOas!hFRu>@x+@Us-KaU-0g+%CyJnaZ>X36q_S2`6pM zY4qP;{%%#2y536Oz*9@?3H4+|zDn$TACBB2Zni(V5)+x>!8D~dRxOIh_!?ntvx%EM zuv5RfYpSHdC@qO&V3q>A2M8!bg^HhT^E*7+sk}tor`y^a(o7ae2^vOq!78@;VGF%d zd;e9*83#(1E)}9z`UH&*^lPNW?&M0n7~h7hhZlw0%M8J1CW7SiJQhnzPIRc~HEt0c zC?fLGaWz_kS_NwEe*S&eakIegnDv<_UO$5;ry=GEV&r5A7Z&c2RH+gP z7cepR^ahWf+RQJhULe&UjUM-i)(wisRht>DJS=gwsM;g-^yu(>^q8Bi_UI?DY7k#= z;EkdUgzC(_HtV9G<~nOT;bB>IT2OS^r2B;UP1jC~Z7I4olX908lM|ocpfK@;nOP2G zPc7fc2k;iPpYiAd`w-hRT!Y#|?}ZZQdBN_4nHQ(39_CJ1N*aEI1T(gRg(KHP=3Z{5 zm*#s2SFBr;-Z|o`1;MoxvCx=kB1F z9o2JY$sBos^6aLDcVOz%lG7I@GKwuSX}Sj)I`v4lXToY13lGD*COV>U0kPo?vX!V7Olo8wrY@}MXFQ3S5=hW+ zc*lY53$rOTYFVkNAJzOo4E~I5qwkBW3^i_+^i)<0yHto(xK^P0814{Q*W?+2DbYwNn|LH8EWv zZC5Es<|froq2_vwKlOj0as|bSQRSctI|9utk*A*XVd(h;X|xf7-Q+9c-g~ z;pGd&^KbuZi8QfMq$8I`zN_@$gpKZAvk~TtW1Hz;>cglBOFg-qr zf;5TI-BGm?gR9QAJA|Q_b4SmwDX3)MVN+~(Vr;nlpgu<@z0d!+-a7oIH}bP~vyX7j@033X|6yM-=S;aTuPtsy@|6E~ksySn7% znHT6W4_1eAyzwR1z1VgOdXBC9<$KuXfm{fkr^KxD@^6#KpqYHsAJ;B=z{FqO9{Z<$=QaI;k;KW*L9}vy8MM z=7LhC$6(pv*Sexu!TJbZ83KiUd|WZE$3KIIn=3Dbpsn%Ve@OFAbcijcLJo0-SNuz; z%(UMv!Ak7mP_)cv8CO#4w5g3z75o_Aa7E25zU8_H4-hvxy2}`?r72LW^k}OqM%Hh6 z*rlz@*|8?o=4Y@=q%Ca2x?mSxAZV(per!&_yWV(5N7WrvpP0!iB$KlWb4eQcqzGFZ zf6qlYINm!j&~^u3-9ylH+l0f;eZeD+#VN^)NBZkmFNov2<5c7f6m*59NCxus=uLg~ zxP(dJs&5k#=d>(i_*&dUjM=H3e8($+r#h~HKvNqRKOpR8NwPOQ z`*|-l-uTdZ{PI;#RYpk6;sW_66iZ}zSCa%rYTH#5XjZ=WE;BLG!S%uW*VFi+`uoH& z7Gl@lwZ2nHMhi~~{W1(Ag&pjr-8=PzjMO~Ocb4_3l5e##IwmNcAYZuNQ}cC>mBOq3 zi4!PcdUr!{+I@as)=x}#cO=q|Q~df%eQtSq4F72h6C-OZi0s1`91}M7J$YmL{z;Gz z=U3iF^@W4JC!E{pS!qMD6e6N%G#}>+G6abn?kRs|JE(NL(;1?+qH%|7 zfIU8Em$*kaJKeZ)aetX{RKa*Js{luB%}QYLh^qzz3#koup*O zdH9|jq_M~N&1o0lf_1vPv%62GWyy0CL*aVIy_fFrg1gKX1Y^Ek5E8#ra#dnCIjU*(c{IuZ6~rH9my+ znC@(wx%;@D)>6;NKH+Lo{;$S9F3rg;9uLp01YO4UURL+D;(nXm(=i!*=f!lP^?Y4L(F%*B`d?^&RXxb@bHX zSqCIL|IQoNlv1Ww?4BKtq~}Y*=PH(lb(`BW!Vp!hQ*n-NJh4E^Y+N91Rt_;V0W!8*Jd&D&kK*6og-`1=zZ~4(*A}B+0kAO@D#?cFFlM;i!(kX(cIKBm4!04 zKTSFNxKCKqmKPf#wVsB}==QK-pZ)CarIDxKp{)fGxVtAYH?NJOJh1GJ?yNC4^p~A8 zFWT-q5bWjG0X(o@tFC0KJ7E&#ksA{kI_GkhHK>@RVfOdeRxES*IrUOLWXAOG%pRXh z^~CTM(!*VuHV>DmGfgacr(*!k`IbkrI!@H7KokJb0Arhh!VZpY`WY_$dS&;5pCVW? z&^McUhFgB&D_eTCFJxQ_JUV@hk5kTqSTES_xNMct+qM76xIvbjkLq&+K+^b-xlvl{ z6ymB+i;U>eHkwgg1jzmKDfOU7hH9Q#x*Rd^!*O>)$V2q+`S*QkXDd^}ki1|^J|(>c zpU*E3Ub_QXW>pI{&n1umB2ODCH{-tAO273thyALAIdOfd%-`aKLgIwPCSOABL3~_< z?F$mgRfw2dse_-Glo?WN5i{9yYX6GHbv|*_9PW#6SQ^qw_N@dF*x-8IBEPbPuOq*S zaTssyQj``U4;fTD%{++x-P<+)j)-d4$tEtl>CN^b==1eDf|{91_r%=^iDv7)-)-#) zEcT=bRL_P*j@k-LsD=yTM=*6AIhz?61S`b*U+5v8P}qSau9({EdTB-?HSb=a6S#>Z z_QWjQz&(IMxi7;qgeTu#{~JZuUZx3Oh_fT*s4B0KPtlzhuS!)4<(Dv^YwOHH^1IDP zX-5&ixj^y0dGIQaaIZQf1epcho0%|N*~3oq~VM;fzsh9k>QQ#vbJo^APHdvKrlG~5mR!*N52Ff(B!Rb{Y|wsqY5ffQ7i+02@jq>0DS<|a6M9|*rXgC zFvcxB2X!_k=D=POOd(;E31?)~Su|A^*{Zz4XMRuM>fPeEz)bIaIICrYe&AApl=yx=%My z$}(2z)`84+VbbZjS6h3h3*u8@52ogp%Z#G=M+qSp2eMUH)xF120yTPc->!tQ3i+7! z&#Bw>6lgEQT*NWIh~kui!Kd-ve6}gkG8ET%_jO9}TE*?!?5#&IW&W|Uaqfp8y&tD! z#o>J$;oK9!mY7?Kr?m-*o|byEBZ<>ugyMD>C7yXArx1<3iNV+kG z+Vre%VZWr0^s4qs@A-%EIo9t!blFBRE@|2|%UrxHY%Y=#pbte1dv^Kw-3&(F_*auF zx8u$&=ATC}iVfXMFUSvFlj^t0#v1$3-R2+RUTL#Ou7r(_7%J#%Z<>!`rX)JW91edI z`~MB+^@=E--8u%y64k^ElbwnYh~K^z-@r+=I$T-7UIE~znVO*&=^GQNFlpA9UYI%I z5rx1lEix&@G|$4ICzscRmrMPu;!+iEU~uS0^^kvN>oZbc!aE@;<34{efq|nJ!WDj>SRE#Rv1N;c4M>wud+v)YcLy3T2OD#SH5ZN^YF*qE@i~; zU$CEjcAcAOQ7KJj-Z5Wyie2tBp>gHle?DLpw>r&-%{mwFeh9%Z)!XNC^JY^|WFt}I z*_&uBBT9ByMxIuIe-hDzr(?n)DN-BlxCSi|J2~oym9Vh9E8V$0!)9RHJ=)NA+e5)Y zchf2se}?1wcz^&w=KY5Ny_VO!^MIbB$3sR=Q*igj&wIXBlOX{;keQ7X)(*yCR0RfV z)0%)Ag1l-48tiV{9 zOgIRS?2g`hYfh)qgkPR-prCEO-O@vn=^u{UzXLL@brgq`F%-K(F=2U3&#Ol`T_pE% zmv!O0&wL2)Fp4QlY9*2z@MbE^i5^UDX3J9={ zZ<~3dNdGP~uh_M4EOkT7PprBBYwz3RnQs68cjwSSr9w`jn-~$H2(c7J4ogLetW1e= zN>0-@a+ffKLI+z1hft)P!ZwvdZXr^WVaj0+%^b$C&A!)E`aJIL-|xTQ@9}*+zTZ9e z&-=aYeO=ez=z<`O7W*UPEs}-ZkJ_!poJazDOs> zjM@4n!jcLFOJ=)7FP2F9f*{36ZN{s~hU~y_gC7!bWLv$~jA0{%Jc0mfC&rIdX19|{ zj+TYZCX8;0YCUxk=ZJPZdE{eA-~Me^8R(G3LhUzml>>z{ygWbWneC+7q+n}VFQ{dI zrWvy^(k}#U=YRumIt@Vsc2E<@Jsp!A!plP41xcg~Zm@~B$nj54s`D$+;pFef=i znmK#IjJ8>JNwB{q+MDW+Pgf>ZSnZfIOxAs)bI#{d;h})g5hWIh^3)kb!o1XN?-{9G zFlW87$Kjrq4+$Xo@_6^Qa*P%@1|8vTZD%s>JWORd`Y7HxS1N<+N|!@^Wa@u0j%)Eh?q2M@X zhPrG>dqMIUZ$a~0ze+XeKqvfgTBvXLOUvi2JD;WvJm$|5k|P`2Qggdcd*1GB&7RLQ zHv1Uj^MCHdY(k!Q(*7hdCSW+=WEU9@)J29vGzxW?5zgzp1>&xN;?Vt2))2&9059C= zU=Ls8G}IvI?eKYc@Gv`n*ThHZkG-J_*5JNj%|9TvTPV%K(dNpzRF=}qu0;bD7dg0Yg z+5i|1a2hy{z&12L54AJRP>k1T;?t&u=?ffGnMa=`k5pi6_5ofU{?l&$MZFz)s_>DO z?BwOXDL7eyL!df>Agjh7tdVe8tY2_<4Er^DD(gUwRlgC2AA*Y8CkejU{|Sz|BhTVE zg6MMMd27j7*SE6pY?%NKZ=cmIsEN9JBXLcJzl zYF0?r-M4LLey3pUIu6nC16kxaCpCk{+pV+}kY?FD5c@z?)oLnnaRdtds$f_t#hVu{ z3E)90gE?|T3$$*s+sOC~5t_Aun;>^rBB0#6mxTNqm84#`^y_Wd=t}nU`mKc`p5MuY!nz&R{(1we43PYcMK}tjXxq_HDuBXA6B-xZhHjCB{X07 z5u(SJt&W?gIo4sh9(`G?R~qxP2}IUkzhwSzZmdx;;{miw6Q7|XtN*v+ZTva*%a_21 z^pwCToSx?cDh=rl{GNl|V-Kv40rejk?8zv{i#5$ikh|9}5*&a8a?a;(kw58q8hH-q6}BkuRg55XMR7 zWdh7(;;?~VwiyW&?KX8uvIQq=E)2ugW?5~G%!08pS<%? zK(}1CFR0&D`_}TyEYm=WE1PF^#|GvF(044_cA@lK$iRB?$rSR{Sb;mEl6o?}L7z4y z$4fIq{z_~n!}WfP&>Hx#7kK|dx1vLOXO9B##rN1Yso~@oL?R{$Faypu+yR|clZx&r zK`mkuJ9c|BuivO4{Xr_NDT`2yE_p*X79)W8+p0+_N+0YD3c zA*f&a7&H<1wG$!eK?FhHG)GnbBpjJR^Q{LtLEKbRy9~7g8Tg!VZzT0X)lmVn0RcKl z$+}BP;CH3_3-HaMm26;7z=}UkdS0sZo^wiL5xz~vX+cR{+RplS4AJ+gO_eiru>W59 zkV`(m-7v<$&H&+rKcC)C?fhCs#hfbi_>w1R)Q;w+eoA_t9X9^!KqJtC|p%ZK$LSpz$+}2QbtEqa70z6nT{9q9Ln)kpXSGOBrU< zPqLZdS7iOLZae7)-HX|E9fNQhyU^3skvv~zMp_I4OWW3v<@v{-uYM7B8jHOcF7iwV=?zjA;YMBE7)`7&nL^)QRl(*b=7sLo|U|YcW zd9b|%47gHMa;s_&CO_*hyUExSU_9@a=cX~!@_E?=& z&i=2J`omj)B?!M4iUxI{H2#pn*qY!+Rd?2xgJBMoh5NtOQSG}1dN%xfmlWssKR;VM z)-zJpP3uMliXmGjL9s3Lls7*kOPEWfkd zV4_<#Bv)YAs7Ps>{JcET!RCy7#eWe%BbY%vdx9?D>zH8%Nk1X@=H%gOXkI zF5SU`++37?H&?fGWFQCdiv}$kYT%8YN_19tgH&ysSo!h1RgdzWo=%Eo=K1F>bai(> zEgVytvWmyi-k+k8?tyr=hKVcL=x7^YLZE(;)$)<&S zjCCjL=A4^X3Ze>1x7CZ$zcw%s2&y?FIfft+ZU)DMXmLgLfT)U(k4WvGnwnBgx#7G= zVf7NzOxwuuf+Fd3K(Ax|g>X;^PcJQA98Xr(w47MXKWV)~pn$XT!&M0zjHTotZ`zYT0*FkenaY zTNx!~)7aR!e3h6OFSIY-P;_HAW~w@x%q0t(ulvupeeElT%ktQ`xcJ&K!V@v>rZibP;+nb4@%tj6qaz45tQ{JrZeY~6?X971H+mCY-ZQQPY_2%C3^)g1>%;p+hv*_dA$ zSy#6o=`b-+P~8%sktaSNqUh4`xMRw)mtHOj2Jgv;Bmh9RCO6VHDpF~^;q}of; zMyJ=^%f_a$I?pkm#n;TUTCzAt^(?@aZj2OK-cNu)vUPt_XBS!1Z)8jWy^$CZmleD9 z26#5_vHG?w8{X?-|0S9)xOxz{`nbE-aY-BCVC~iZlYji3l-co5Zt(XXEzvhUmO*~e zH-C_q6_z-?KO6!C;`+0Z@V~==F6RFvq51!rtJLwrqoI03jA>AE^TVF9)AyepSy%y) zi9B7`(fm4cjYgrLy0L~11ai%=i-tTf&vu3ENv$wln0@KdfDcp|Ic2|A(zlmc1i6s{ zelbt%Iq$X|DlfH!OhISuvulKt`AR!cZDvCVQjdiM-(56f^MktM8aLITObMo^o)V<( zDkuD#6(27KkuI&)b%K9|*1t=!low6CpuTCv*60z+;d&QHgie=C{(_=OyWQ3mOPqM= z6+9h!J9h3YZT(1hL-A>*qFOp+v-BHWk$H-}aakIF%d&{mYuN0L^;}bF$U55=q0EJh zOV8x(k7C~7BTaFIknQeo5{gh4){x9azWjm_uQ1=XtQ%L_Gi^DkLw~i(wuGPJ2hpnZK{O z8b_~GgRJlpMm~AHOAPYoYQwq&|J9-pj}P!+aXp7Zw-1JM4q74+heTzOK@&TIu1fOE z23I}&S)T9V;)W8qfuiWV80*UrnVYwDQ?0?}HiY4%b`!-RpZ6Qosrl}<=08RIDHFiG zA*-R6&wpcpJMPr(sAq>LYb?tMwtV63HXIIa#kGJ?23PD6~89Nj%7#Zcl zizFcr&ukQdya_sH*yz9V)Y3>mf3q%Ps|>N=~R zieB=8ovHm^~^hklKz-VH+NdeiTbT?DCR zzV0vLDV*wxE6=2HMM%xBvF^F%BLU+KY8j{K!sq9a)1#Ee{H57L>Ndn?iRMDB5m?@I zaYtoHVb!Xo4^IKQ;IE_JD;7Y*KVQ`%;8us92apJ{4NEx?I4Qj)HY*j)45z8A0O9qu z|BvrQtdz9bCU&P6Ob&$L6aRbmzkwX$H>Gj?dmobdIgxSvndn!A5+EP5cqjBjIkxwY zdpo}u_RB2qhYF=}`&vXv{1n0(S1yItg5u?1*-?=>3)9%uPv#Q&rXG*kkJ)E^;ewP- zTlmKn()Sd#Kfk}SXMDm?NSe3HL7mti4OQMjK7;6-;Q!QZdeorA9xZiK5f2-vmg#@wb_H%~b~P1kFR2-MsNwj9Q8RaV2)y-)(%bBbr+ZBig*GeDDgwBys_&Hvz`JU`5B zjTHF*t)K@i4s5}Cd~^dC>QxFa-$tVTQSq(3WGj@zUTKBPuSCS)ox+tm4w6>ezHM10 zTgio{-F&C)JA(0Y`@<)_RDv2VlKNM1>9kqe>Td^43+~1Kk;k~XY~R7te(wWS=^8<> z4NSX@wxXtPOd@^Z-n?Rv!Ur=yVJ18reSJ!*-RWURN&t^%#c;WZ3HC%lnLIB}#zlPF zS$SnkqVq7}J$AC_Llv!b1ShGdnlk=u8nDmD09u8|R}0jv0smc3CdNc&M? z*1hxm^cF^(&M0@SR7~l#(GkMk=RPR@+YSnD;dYfa-F*x4^D}BAU1a8rALpCdd6R!A zY;cn_FSPe7_9*<;!lmA?u=bf70JnSP;H{(>-cpG2{7+QP$fV z^K_hYlYjl)FcKHdO8E1UDRcTA(w=J8cX^KPG#^QRip6QvXGrDxY*{e+Ok!#JA)<|S z=zE!Wu|9gG({3%hL!9gJt!{t6wBrO0k}@+6%zfM3Gc&?_?w4;vWVE$-_AcQ3`TN^- z4zn$ZLxg-5#h3PGqq=L$|53<_->fzL}vG2kUcA3=$;hn$w+G zCzW0rm{*Msu-8w&)ZpCiJljc_hUF-heTyo4+QYvI^K|Q5??WKM8%US6_1Mhai+{fX z1o_hkyz2WJ$>jAyg{1ks#I`9cr)563%?85~AD)ZYj?il2PQA3!RD4W10_~J|uu3?l zVK#zsWS`x0@xzxJwBD_zIt`8*m26|eC|B^~aFaTVOeyERwf^*jyw94XE%?WFgHg)7 z&=(0BqcR(O#OD_RlTbKky5>|z5a*?&)nT?F=R`K6WqWN}HP1R8his_`c1G_)Ba)La z%~dipQ_=AcTCTERs?4uFV>!?H&^)@GXUrhFWxB>yQ+S66ce}|#DyLviNQ1RjJt?xe zRN?8yVgsQ9$MhE4xOUPy7AiP%njO*7GH>Ql<4^Uh-A+5^@}mO=Q9GnnqIK>{$oukH za-&2hOuoE7+U*r9deS8ercge+d>_m)Px)fej=(k(|A)J?)E^?m2kpj2m&HK?uehPz z4o;|+$fTT1U#TsZRf0|6&#&`YzEO--^tLEkefw&8m0icYm^LOe9;<_^z=EekF9}Q9 z=qEr+;-5@Osi!To9+_?=r4Dll9mn7!94=ebHIBuzbt6AHpN2vU6$I&R;K-^UL|BH{R#=z|5o}kscbfOS+$$b|H3J!_7uK)1u3VN7v zxKLWC$Okfq8i-vW2&E0tw({zG*`5}e?q2%?91iqagdH?LWg#)Vp<>#((}=Z)S*{kWw)^7SrcVB^o>6CSiWF zeNDz{=+(ItJ!1U*z#Ays8+_qCl_2bkmfH7(FBxeTMK)v#%rNgM&fUIly0IcyW$eu` z;k@=WGwET133nljoVu`3JY{T4URx#VjNI$(p?IJ!NJeYf%$v^hDE@L2tjhKF;HYNH zPP(XT{)3q?3*AdfwtVd^?&a>DpLVtcaK6dpC3cGaSmO3b_uMn)5>;>N*~_)(zrcf% zj@HAtAF3@QnT-jH3BzT~09knBfg}5f2Z)B1@!zbOtYrt?zb?4GChVpZ=18s66I0J+ zoU!H7kG{9{(R>ln%Q*J8=bF}1iql074kmu&)C&21gnS&jowI!sEFOX4oQ}=Xpn57! zy{BvTPF;&{N7})BrMwttHfCL!X(DgD?W4MIxj>iCbEf#dMVdQg0Eq)y-DV<4dkKw$t z<~~8E?7g1j(X{z$Ttw*y1l&p!v*yUD>l>o*nnv(ky0%044dt5c^>>3V&+~Fca5Ws_ zj096hR^8fb$LNa}oi<6q+EL0AVYjcA)p&ZiHU=imh3%Wsz#{r*+vd|Nd}C&aEzK3d zQ6~|I`SUTOH*S79wzqb3J+o1J_nHdqOIF;g1e2!-@d@I1ESA(8nBJkrk>LU1$QNc2 zUP$hn>BjMzy$$;|d1O=uGP^S_ibnJBI4wQSsmZFdO!!w~Zr}duunD#aoAKhxQV9|5 z@ZCSw1hgX7TxNa^T+g~wVr6@8`Nmz>k6WHt-{kb}^UgyXUOuy0M>qO((?imx1^egw zTtR8J_^Lkry|hrEnisM5#!NYAojhs$PQpuAcW`ffq{daU!&c!6N%}xtT*MERKqS;t zPi)-r%4G13OWP$$HFC_9^draK%`1*6qn*F>vRylpTwjOm@O}2vh5z_Iz)?YhT>Ou# s{Qo+;EG~<|UWf(cq%+FKOtJ$2>306eP{J^%m! diff --git a/images/store_remain_memory.PNG b/images/store_remain_memory.PNG deleted file mode 100644 index c8e6032fda34c7c53e054c7a0c498df9bb9e5c4e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61006 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 diff --git a/images/throughput.PNG b/images/throughput.PNG new file mode 100644 index 0000000000000000000000000000000000000000..d0c5460708b51eadf969ee5d5ade846671a78d0b GIT binary patch literal 48350 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