package com.tqmall.search.canal.handle; import com.alibaba.otter.canal.client.CanalConnector; import com.alibaba.otter.canal.client.CanalConnectors; import com.alibaba.otter.canal.protocol.CanalEntry; import com.alibaba.otter.canal.protocol.Message; import com.alibaba.otter.canal.protocol.exception.CanalClientException; import com.tqmall.search.canal.CanalExecutor; import com.tqmall.search.canal.RowChangedData; import com.tqmall.search.commons.utils.CommonsUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.SocketAddress; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * Created by xing on 16/2/22. * {@link CanalInstanceHandle} 的抽象类封装 * 想自定义实现{@link CanalInstanceHandle}直接继承该类, 少些一些代码 */ public abstract class AbstractCanalInstanceHandle implements CanalInstanceHandle { private static final Logger log = LoggerFactory.getLogger(AbstractCanalInstanceHandle.class); protected CanalConnector canalConnector; private final ConnectorFactory connectorFactory; protected final String instanceName; private volatile int messageBatchSize = 1000; /** * {@link CanalConnector#getWithoutAck(int, Long, TimeUnit)}中的时间参数是Long的, 避免频繁的自动装箱,砸门还是直接定义成{@link Long} */ private volatile Long messageTimeout = 1000L; /** * 轮询获取变更数据的时间间隔, 默认500ms, 如果对于实时性要求较高可以设置小一些 * * @see #fetchInterval() */ private volatile long fetchInterval = 100L; /** * @param destination canal实例名称 * @param connectorFactory {@link CanalConnector}构造器 */ public AbstractCanalInstanceHandle(String destination, ConnectorFactory connectorFactory) { Objects.requireNonNull(connectorFactory); this.instanceName = destination; this.connectorFactory = connectorFactory; } protected abstract void doConnect(); protected abstract void doRowChangeHandle(List<RowChangedData> changedData); protected abstract void doFinishHandle(); /** * 执行{@link #doRowChangeHandle(List)}或者{@link #doFinishHandle()} 发生异常时, 触发该方法调用 * 如果继续处理后续的更新数据, 忽略当前异常, 则返回true * 如果该异常较严重, 后续更新无法处理, 则返回false, canal执行器{@link CanalExecutor}会停止canal同步, 待问题处理之后再说~~~ * * @param exception 对应的异常 * @param inFinishHandle 标识是否在{@link #doFinishHandle()}中发生的异常 * @return 是否忽略该异常 */ protected abstract boolean exceptionHandle(RuntimeException exception, boolean inFinishHandle); @Override public final String instanceName() { return instanceName; } @Override public void connect() { log.info("canal instance: " + instanceName + " start connect"); //canal中对于Connector中的用户名和密码不做校验, 所以设置也没有意义 canalConnector = connectorFactory.create(instanceName); doConnect(); log.info("canal instance: " + instanceName + " connect succeed"); } @Override public void disConnect() { log.info("canal instance: " + instanceName + " start disConnect"); try { canalConnector.unsubscribe(); } finally { try { canalConnector.disconnect(); log.info("canal instance: " + instanceName + " disConnect succeed"); } catch (CanalClientException e) { log.error("canal instance: " + instanceName + " disConnect failed", e); } } } /** * 不指定 position 获取事件. * 该方法返回的条件: * a. 拿够{@link #messageBatchSize}条记录或者超过timeout时间 * b. 如果{@link #messageTimeout} = 0,则阻塞至拿到batchSize记录才返回 * <p/> * canal 会记住此 client 最新的position。 <br/> * 如果是第一次 fetch,则会从 canal 中保存的最老一条数据开始输出。 */ @Override public Message getWithoutAck() { return canalConnector.getWithoutAck(messageBatchSize, messageTimeout, TimeUnit.MILLISECONDS); } /** * 更改记录的诗句转换成 {@link RowChangedData} list * * @param rowChange 更改的数据 * @return 构建的{@link RowChangedData} list */ protected abstract List<RowChangedData> changedDataParse(CanalEntry.RowChange rowChange); @Override public final void rowChangeHandle(CanalEntry.RowChange rowChange) { try { List<RowChangedData> changedData = changedDataParse(rowChange); if (!CommonsUtils.isEmpty(changedData)) doRowChangeHandle(changedData); } catch (RuntimeException e) { if (!exceptionHandle(e, false)) { throw e; } else { log.warn("canal instance: " + instanceName + " ignore handle exception", e); } } } @Override public final void finishMessageHandle() { try { doFinishHandle(); } catch (RuntimeException e) { if (!exceptionHandle(e, true)) { throw e; } else { log.warn("canal instance: " + instanceName + " ignore handle exception", e); } } } @Override public void ack(long batchId) { canalConnector.ack(batchId); } /** * 如果batchId == 0, 则下次fetch的时候,可以从最后一个没有 {@link #ack(long)} 的地方开始拿, 即调用 * {@link CanalConnector#rollback()} * * @param batchId message id */ @Override public void rollback(long batchId) { if (batchId == 0L) { canalConnector.rollback(); } else { canalConnector.rollback(batchId); } } /** * 获取Message {@link CanalConnector#getWithoutAck(int, Long, TimeUnit)}的batchSize * 默认1000, 如果 <= 0, canal内部取默认1000 */ public void setMessageBatchSize(int messageBatchSize) { this.messageBatchSize = messageBatchSize; } /** * 获取Message {@link CanalConnector#getWithoutAck(int, Long, TimeUnit)}的超时时间 * time unit is ms, 默认1s */ public void setMessageTimeout(Long messageTimeout) { this.messageTimeout = messageTimeout; } /** * 轮询获取变更数据的时间间隔, 默认500ms, 如果对于实时性要求较高可以设置小一些 * * @see #fetchInterval() */ public void setFetchInterval(long fetchInterval) { this.fetchInterval = fetchInterval; } @Override public long fetchInterval() { return fetchInterval; } }