2022-11-18 09:38阅读: 634评论: 2推荐: 0

MySQL 之 binlog 封装

本篇文章讲述的是 MySQL binlog 的监听使用;

早期有过一片简陋的,直接在业务模块直接使用,没有单独抽取封装:https://www.cnblogs.com/chxlay/p/15115543.html

关于 如何搭建主从,或者如何开启 主服务的 binlog 可以参看: https://www.cnblogs.com/chxlay/p/15650522.html

此项目地址已开放,地址: https://github.com/chxlay/kwanyon-binlog-java    此代码库的地址是新开放的,有些许改动,后没有进行测试

******  注意: 后续的优化更新不再对博文进行修改更新,以 GitHub的更新为准  ******

编写初衷

1、可能有人会觉得,已经有 canal 这么成熟的技术框架了,还有必要自己封装一个进行实现吗?

2、canal 是一个比较成熟的,我之所以没有使用的原因是因为,需要安装一个 canal  服务端程序,然后 才能进行编写 客户端进行使用,造成服务资源的开支,那能否有一个较为轻量级的 binlog 框架呢,

3、这里就自行编写一个,借助 binlog 日志监听连接器 mysql-binlog-connector-java ,自行实现一个 无需服务端安装,直接使用客户端的 组件,除了 canal 之外的 轻量级选择

 


依赖方案选择

使用到的三方依赖支持, 两个依赖可选

1,mysql-binlog-connector-java  ( groupId 为  com.github.shyiko ) 此依来 不建议使用,MySQL 8. x  以上版本存在 密码协议问题

GitHub 源码地址 :https://github.com/shyiko/mysql-binlog-connector-java

 

<!-- https://mvnrepository.com/artifact/com.github.shyiko/mysql-binlog-connector-java -->
<dependency>
    <groupId>com.github.shyiko</groupId>
    <artifactId>mysql-binlog-connector-java</artifactId>
    <version>0.21.0</version>
</dependency>

这是一个比较早期的依赖,写文章当时,此依赖最后更新日期为 2020 年 6 月,

存在的问题 MySQL 8.x 以上的版本  加密协议问题异常,关于问题 讨论 GitHub 中:  https://github.com/shyiko/mysql-binlog-connector-java/issues/240

在个人早期的文章中有简单使用说明: https://www.cnblogs.com/chxlay/p/15115543.html  以及问题的处理  (不建议使用旧版本的文章的方式)

 

2、 mysql-binlog-connector-java  (groupId 为 com.zendesk  ) 此依赖其实也是 完全是将 上一个依赖 clone  后开发的

关于二者的关系,README 文件中有描述,感兴趣的朋友可以翻阅:

github 源码地址:  https://github.com/osheroff/mysql-binlog-connector-java

<!-- https://mvnrepository.com/artifact/com.zendesk/mysql-binlog-connector-java -->
<dependency>
    <groupId>com.zendesk</groupId>
    <artifactId>mysql-binlog-connector-java</artifactId>
    <version>0.27.5</version>
</dependency>

第一个依赖貌似已经脱更,第二个依赖还在持续更新中


结构原理

图地址: https://assets.processon.com/chart_image/6376ea13f346fb01d40ece02.png

 

封装完成后的代码文件结构如下图:

 


代码示例:

1、依赖

复制代码
<dependencies>
        <!-- MySQL binlog 日志监听 https://github.com/osheroff/mysql-binlog-connector-java -->
        <dependency>
            <groupId>com.zendesk</groupId>
            <artifactId>mysql-binlog-connector-java</artifactId>
            <version>${zendesk.binlog.version}</version>
        </dependency>


        <!-- 必须引入框架,整合 springboot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- 可根据个人习惯 或 项目技术栈 选择性引入 或 移除 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!-- 日志依赖,可结合项目中使用的日志框架进行选择性引入 或 移除  -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>
复制代码

 

2、插件注解开关

@Documented
@Target(value = ElementType.TYPE)
@Retention(value = RetentionPolicy.RUNTIME)
@Import(value = BinlogConfiguration.class)
public @interface EnableBinlogListener {
}

3、属性配置文件:

复制代码
@Setter
@ConfigurationProperties(prefix = "kwanyon.binlog")
public class IBinlogProperties {
    /**
     * Master 主机连接地址
     */
    private String host;
    /**
     * 数据库名称
     */
    private String datebase;
    /**
     * 端口号
     */
    private int port = 3306;
    /**
     * 连接用户名
     */
    private String username;
    /**
     * 连接密码
     */
    private String password;
    /**
     * binlog 需要接收处理的 表名
     */
    private List<String> tables = new ArrayList<>();
}
复制代码

自动注入配置类

复制代码
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
@ComponentScan(value = {"com.kwanyon.common.binlog.support"})
@EnableConfigurationProperties(IBinlogProperties.class)
public class BinlogConfiguration {

    private final ApplicationContext context;
    private final IBinlogProperties binlogProperties;
@Bean @Order @ConditionalOnMissingBean
public MessagePublisher messagePublisher() { return new EventMessagePublisher(context); } @Bean @ConditionalOnMissingBean public IBinLogRunner iBinLogRunner(DefBinlogListener eventListener) { return new IBinLogRunner(eventListener, binlogProperties); } }
复制代码

 

binlog 连接启动类 :

@Async  注意了,client.connect()  是一个阻塞的,所以需要单独开一条线程进行执行,我这里是使用Spring的 @Async ,前提是项目中启用了异步 @EnableAsync  

也可以 自行使用多线程方式方式实现

复制代码
@RequiredArgsConstructor
public class IBinLogRunner implements CommandLineRunner {
    /**
     * Binlog 监听器作为集群的一个从节点角色,必须具备一个服务唯一ID
     */
    private static final long SERVER_ID = 100000L;

    private final DefBinlogListener binlogListener;
    private final IBinlogProperties binlogProperties;


    @Async
    @Override
    public void run(String... args) throws Exception {
        BinaryLogClient client = new BinaryLogClient(binlogProperties.host(), binlogProperties.port(),
                binlogProperties.datebase(), binlogProperties.username(), binlogProperties.password());
        /**
         * Binlog 监听器作为集群的一个从节点角色,必须具备一个服务唯一ID
         */
        client.setServerId(SERVER_ID);

        // 自定义系列化(没有自定义系列化需求的朋友可以使用默认的 new EventDeserializer() )
        EventHeaderV4Deserializer eventHeaderV4Deserializer = new EventHeaderV4Deserializer();
        NullEventDataDeserializer nullEventDataDeserializer = new NullEventDataDeserializer();
        IdentityHashMap<EventType, EventDataDeserializer> eventDataDeserializers = new IdentityHashMap<>();
        Map<Long, TableMapEventData> tableMapEventByTableId = new HashMap<>();
        this.registerDefaultEventDataDeserializers(eventDataDeserializers, tableMapEventByTableId);

        EventDeserializer eventDeserializer = new EventDeserializer(eventHeaderV4Deserializer, nullEventDataDeserializer,
                eventDataDeserializers, tableMapEventByTableId);

        client.setEventDeserializer(eventDeserializer);
        // 监听器
        client.registerEventListener(binlogListener);

        client.connect();
    }

    /**
     * 源码中的注册事件类型(这里自定义编写了 bit --> Boolean 的反系列化规则),没有这个需求的可以不需要这个函数,而使用框架默认的反系列化配置
     *
     * @param eventDataDeserializers
     * @param tableMapEventByTableId
     * @see EventDeserializer 源码中的注册事件类型
     */
    private void registerDefaultEventDataDeserializers(IdentityHashMap<EventType, EventDataDeserializer> eventDataDeserializers, Map<Long, TableMapEventData> tableMapEventByTableId) {
        // 启动完成后 binlog 日志文件信息事件(日志文件索引,日志文件坐标)
        eventDataDeserializers.put(EventType.ROTATE, new RotateEventDataDeserializer());
        // 启动时连接成功后的版本相关描述信息事件
        eventDataDeserializers.put(EventType.FORMAT_DESCRIPTION, new FormatDescriptionEventDataDeserializer());

        eventDataDeserializers.put(EventType.INTVAR, new IntVarEventDataDeserializer());
        eventDataDeserializers.put(EventType.QUERY, new QueryEventDataDeserializer());
        eventDataDeserializers.put(EventType.TABLE_MAP, new TableMapEventDataDeserializer());
        eventDataDeserializers.put(EventType.XID, new XidEventDataDeserializer());
        eventDataDeserializers.put(EventType.WRITE_ROWS, new WriteRowsEventDataDeserializer(tableMapEventByTableId));
        eventDataDeserializers.put(EventType.UPDATE_ROWS, new UpdateRowsEventDataDeserializer(tableMapEventByTableId));
        eventDataDeserializers.put(EventType.DELETE_ROWS, new DeleteRowsEventDataDeserializer(tableMapEventByTableId));
        // 自定义反系列化类 (读写更新)
        eventDataDeserializers.put(EventType.EXT_WRITE_ROWS, (new WriteRowsDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
        eventDataDeserializers.put(EventType.EXT_UPDATE_ROWS, (new UpdateRowsDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));
        eventDataDeserializers.put(EventType.EXT_DELETE_ROWS, (new DeleteRowsDeserializer(tableMapEventByTableId)).setMayContainExtraInformation(true));

        eventDataDeserializers.put(EventType.ROWS_QUERY, new RowsQueryEventDataDeserializer());
        eventDataDeserializers.put(EventType.GTID, new GtidEventDataDeserializer());
        eventDataDeserializers.put(EventType.PREVIOUS_GTIDS, new PreviousGtidSetDeserializer());
        eventDataDeserializers.put(EventType.XA_PREPARE, new XAPrepareEventDataDeserializer());

        eventDataDeserializers.put(EventType.ANNOTATE_ROWS, new AnnotateRowsEventDataDeserializer());
        eventDataDeserializers.put(EventType.MARIADB_GTID, new MariadbGtidEventDataDeserializer());
        eventDataDeserializers.put(EventType.BINLOG_CHECKPOINT, new BinlogCheckpointEventDataDeserializer());
        eventDataDeserializers.put(EventType.MARIADB_GTID_LIST, new MariadbGtidListEventDataDeserializer());
        eventDataDeserializers.put(EventType.TRANSACTION_PAYLOAD, new TransactionPayloadEventDataDeserializer());
    }
}
复制代码

自定义反系列化编写

(数据库中的  bit  反系列化为  Java 中的  Boolean  如果没有需要的用户可以简化配置)

删除行数据时反系列化
复制代码
/**
 * Java类BitSet 自定义系列化规则
 *
 * @author Alay
 * @date 2022-11-14 13:51
 */
public class DeleteRowsDeserializer extends DeleteRowsEventDataDeserializer {
    public DeleteRowsDeserializer(Map<Long, TableMapEventData> tableMapEventByTableId) {
        super(tableMapEventByTableId);
        this.setMayContainExtraInformation(true);
    }
    /**
     * 自定义系列换中 数据库 Bit 字段转为 Boolean 类型
     *
     * @param meta
     * @param inputStream
     * @return
     * @throws IOException
     */
    @Override
    protected Serializable deserializeBit(int meta, ByteArrayInputStream inputStream) throws IOException {
        int bitSetLength = (meta >> 8) * 8 + (meta & 0xFF);
        BitSet bitSet = inputStream.readBitSet(bitSetLength, false);
        int cardinality = bitSet.cardinality();
        return cardinality == 1;
    }
}
复制代码
更新行数据时自定义系列化

bit --> Boolean

复制代码
/**
 * Java类BitSet 自定义系列化规则
 *
 * @author Alay
 * @date 2022-11-14 13:51
 */
public class UpdateRowsDeserializer extends UpdateRowsEventDataDeserializer {
    public UpdateRowsDeserializer(Map<Long, TableMapEventData> tableMapEventByTableId) {
        super(tableMapEventByTableId);
        this.setMayContainExtraInformation(true);
    }
    /**
     * 自定义系列换中 数据库 Bit 字段转为 Boolean 类型
     *
     * @param meta
     * @param inputStream
     * @return
     * @throws IOException
     */
    @Override
    protected Serializable deserializeBit(int meta, ByteArrayInputStream inputStream) throws IOException {
        int bitSetLength = (meta >> 8) * 8 + (meta & 0xFF);
        BitSet bitSet = inputStream.readBitSet(bitSetLength, false);
        int cardinality = bitSet.cardinality();
        return cardinality == 1;
    }
}
复制代码
插入行数据时自动以反系列

bit  ---->  Boolean 

复制代码
/**
 * Java类BitSet 自定义系列化规则
 *
 * @author Alay
 * @date 2022-11-14 13:51
 */
public class WriteRowsDeserializer extends WriteRowsEventDataDeserializer {
    public WriteRowsDeserializer(Map<Long, TableMapEventData> tableMapEventByTableId) {
        super(tableMapEventByTableId);
        this.setMayContainExtraInformation(true);
    }
    /**
     * 自定义系列换中 数据库 Bit 字段转为 Boolean 类型
     *
     * @param meta
     * @param inputStream
     * @return
     * @throws IOException
     */
    @Override
    protected Serializable deserializeBit(int meta, ByteArrayInputStream inputStream) throws IOException {
        int bitSetLength = (meta >> 8) * 8 + (meta & 0xFF);
        BitSet bitSet = inputStream.readBitSet(bitSetLength, false);
        int cardinality = bitSet.cardinality();
        return cardinality == 1;
    }
}
复制代码

 

4、核心功能代码:

常量类:

复制代码
public interface BinlogConst {
    /**
     * INSERT、UPDATE、DELETE 的事件的 SQL
     */
    String EVENT_SQL_BEGIN = "BEGIN";
    /**
     * 创建表
     */
    String EVENT_SQL_CREATE = "CREATE";
    /**
     * 修改表
     */
    String EVENT_SQL_ALTER = "ALTER";
    /**
     * 删除表
     */
    String EVENT_SQL_DROP = "DROP";
    /**
     * 重命名 RENAME TABLE `database`.`table_name` TO `database`.`new_table_name`
     */
    String EVENT_SQL_RENAME = "RENAME";
}
复制代码

binlog 日志监听入口: 监听器

复制代码
@Component
@RequiredArgsConstructor
public class DefBinlogListener implements BinaryLogClient.EventListener {
    private final ServiceAdapter serviceAdapter;
    /**
     * 一次MySQL的修改、插入、删除,会触发多次事件,会调用方法多次,注意处理好逻辑优化性能
     * 启动时事件顺序:ROTATE、FORMAT_DESCRIPTION
     * 启动后事件触发顺序:
     * 1、每次操作只触发一次(批量操作也只会触发一次):ANONYMOUS_GTID,QUERY,
     * 2、每条记录row 触发一次独立的事件:TABLE_MAP(携带表库字段信息)批量也会触发多次
     * 3、多选一具体事件:EXT_UPDATE_ROWS(执行更新),EXT_DELETE_ROWS,EXT_WRITE_ROWS
     * 4、每次操作只触发一次(批量操作也只会触发一次) XID
     * @param event
     */
    @Override
    public void onEvent(Event event) {
        EventType eventType = event.getHeader().getEventType();
        EventData eventData = event.getData();
        // 无需处理
        if (null == eventData) return;
        // 匹配事件处理器
        EventProcess eventProcess = serviceAdapter.matchProcess(eventType);
        if (null == eventProcess) return;
        // 事件处理
        eventProcess.process(eventData);
    }
}
复制代码

binlog  事件类型适配器 注册

复制代码
/**
 * 服务注册适配器
 * @author Alay
 * @date 2022-11-15 11:01
 */
@Component
public class ServiceAdapter {
    private static final List<EventProcess> EVENT_PROCESSES = new ArrayList<>(1 << 5);
    public synchronized static void registry(EventProcess eventParser) {
        EVENT_PROCESSES.add(eventParser);
    }
    public EventProcess matchProcess(EventType eventType) {
        for (EventProcess eventProcess : EVENT_PROCESSES) {
            boolean support = eventProcess.support(eventType);
            if (support) return eventProcess;
        }
        // 没有定义或注册事件处理器
        return null;
    }
}
复制代码

事件处理器接口:

复制代码
/**
 * 事件处理器
 *
 * @author Alay
 * @date 2022-11-15 11:31
 */
public interface EventProcess<T extends EventData> {
    /**
     * 事件注册器
     */
    @EventListener(value = WebServerInitializedEvent.class)
    default void registry() {
        ServiceAdapter.registry(this);
    }
boolean support(EventType eventType);
void process(T eventData); }
复制代码

 

事件处理器具体实现类(目前仅仅实现了常用的,并不是所有的事件类型都编写了对应的 事件处理器,需要扩展的请自行扩展)

实现类:

1、启动时 binlog 文件初始化读取事件处理器
复制代码
/**
 * 启动时事件顺序:ROTATE、FORMAT_DESCRIPTION
 *
 * @author Alay
 * @date 2022-11-15 10:51
 */
@Component
public class EventRotateProcess implements EventProcess<RotateEventData> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public boolean support(EventType eventType) {
        return EventType.ROTATE == eventType;
    }
    @Override
    public void process(RotateEventData eventData) {
        logger.info("---------------<<< 监听的 binlog 文件名:{} >>>--------------------", eventData.getBinlogFilename());
        logger.info("---------------<<< binlog 坐标 :{} >>>--------------------", eventData.getBinlogPosition());
    }
}
复制代码
2、启动时 binlog  MySQL 版本信息 事件处理器
复制代码
/**
 * 启动时版本信息数据
 * 启动时事件顺序:ROTATE、FORMAT_DESCRIPTION
 * @author Alay
 * @date 2022-11-15 10:53
 */
@Component
public class EventFormatDescProcess implements EventProcess<FormatDescriptionEventData> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final EventType eventType = EventType.FORMAT_DESCRIPTION;

    @Override
    public boolean support(EventType eventType) {
        return this.eventType == eventType;
    }
    @Override
    public void process(FormatDescriptionEventData eventData) {
        logger.info("---------------<<< binlog 启动并连接成功 >>>--------------------");
        logger.info("---------------<<< binlog 版本:{} >>>--------------------", eventData.getBinlogVersion());
        logger.info("---------------<<< MySQL 版本:{} >>>--------------------", eventData.getServerVersion());
    }
}
复制代码
3、     QUERY (2)  事件处理器 
复制代码
/**
 * 此事件操作的是修改表结构,修改后需要将缓存中存储的表结构删除
 * 解析得出修改表结构的 sql 语句进行解析
 * sql='ALTER TABLE `db_name`.`table_name`后面是具体的执行语句
 * @author Alay
 * @date 2022-11-15 11:30
 */
@Component
@RequiredArgsConstructor
public class EventQueryProcess implements EventProcess<QueryEventData> {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final MessagePublisher messagePublisher;
    private final IBinlogProperties binlogProperties;
    private final TableSchemaManager tableSchemaManager;
    private final DropEventParser dropEventParser;
    private final AlterEventParser alterEventParser;
    private final CreateEventParser createEventParser;
    private final RenameEventParser renameEventParser;
    @Override
    public boolean support(EventType eventType) {
        return EventType.QUERY == eventType;
    }
    @Override
    public void process(QueryEventData eventData) {
        String sql = eventData.getSql();
        // INSERT、UPDATE、DELETE 的事件,不做处理
        if (BinlogConst.EVENT_SQL_BEGIN.equals(sql)) return;
        BinlogMessage binlogMessage = null;
        // 修改表事件
        if (sql.startsWith(BinlogConst.EVENT_SQL_ALTER)) {
            sql = this.beautifySql(sql);
            // sql 美化
            eventData.setSql(sql);
            logger.info("binlog 修改表事件 SQL: {}", sql);
            binlogMessage = alterEventParser.parseEvent(eventData);

            // 修改表结构,删除缓存
            tableSchemaManager.removeCache(binlogMessage.getSchema() + "." + binlogMessage.getTableName());
        }
        // 新增表事件
        if (sql.startsWith(BinlogConst.EVENT_SQL_CREATE)) {
            sql = this.beautifySql(sql);
            // sql 美化
            eventData.setSql(sql);
            logger.info("binlog 新增表事件 SQL: {}", sql);
            binlogMessage = createEventParser.parseEvent(eventData);
        }
        // 删除表事件
        if (sql.startsWith(BinlogConst.EVENT_SQL_DROP)) {
            //  DROP TABLE IF EXISTS `t_student` /* generated by server */
            sql = sql.substring(0, sql.indexOf("/*"));
            sql = this.beautifySql(sql);
            // sql 美化
            eventData.setSql(sql);
            logger.info("binlog 删除表事件 SQL: {}", sql);
            binlogMessage = dropEventParser.parseEvent(eventData);
            // 修改表结构,删除缓存
            tableSchemaManager.removeCache(binlogMessage.getSchema() + "." + binlogMessage.getTableName());
        }
        // 表名重命名 RENAME TABLE `database`.`table_name` TO `database`.`new_table_name`
        if (sql.startsWith(BinlogConst.EVENT_SQL_RENAME)) {
            sql = this.beautifySql(sql);
            // sql 美化
            eventData.setSql(sql);
            // RENAME TABLE `database`.`table_name` TO `database`.`new_table_name`
            RenameMessage renameMessage = renameEventParser.parseEvent(eventData);
            // 修改表结构,删除缓存
            tableSchemaManager.removeCache(renameMessage.getSchema() + "." + renameMessage.getBeforeTable());
            binlogMessage = renameMessage;
        }
        // 不是我需要处理的事件,不做处理
        if (null == binlogMessage) return;
        // 判断是否需要处理此表的日志相关
        List<String> tables = binlogProperties.tables();
        // 不需要处理,不属于处理的表名中
        if (!tables.contains(binlogMessage.getSchema() + "." + binlogMessage.getTableName())) return;
        messagePublisher.publish(binlogMessage);
    }
    /**
     * 美化Sql
     * sql = sql.replace("\n", "").replace("\t", "").replace("  ", " ").replace("( ", "(").replace(" )", ")").replace(" ,", ",");
     */
    private String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n]+", " ");
        // 去除尾部空格
        char[] chars = sql.toCharArray();
        char blank = ' ';
        for (int i = chars.length - 1; i >= 0; i--) {
            char c = chars[i];
            if (blank != c) break;
            sql = sql.substring(0, i);
        }
        return sql;
    }
}
复制代码
4、表结构映射关系 事件处理器
复制代码
/**
 * INSERT / UPDATE / DELETE 将会触发此事件
 * @author Alay
 * @date 2022-11-15 11:35
 */
@Component
@RequiredArgsConstructor
public class EventTableMapProcess implements EventProcess<TableMapEventData> {
    private final IBinlogProperties binlogProperties;
    private final TableSchemaManager tableSchemaManager;
    @Override
    public boolean support(EventType eventType) {
        return EventType.TABLE_MAP == eventType;
    }
    @Override
    public void process(TableMapEventData eventData) {
        /**
         * 表结构映射事件
         */
        TableMapEventData mapEventData = eventData;
        // tableId 并非表名
        long tableId = mapEventData.getTableId();
        boolean hasCache = tableSchemaManager.hasCache(tableId);
        // 此表已经不是第一次触发该事件了,不需要重复的处理做准备的工作
        if (hasCache) return;
        // 库名
        String database = mapEventData.getDatabase();
        // 表名
        String tableName = mapEventData.getTable();
        // 是否数据我们需要处理的表,不属于的不处理
        boolean needProcess = binlogProperties.tables().contains(database + "." +tableName);
        if (!needProcess) return;
        // 表结构对象封装
        TableSchema tableSchema = tableSchemaManager.queryTableSchema(tableId, database, tableName);
        tableSchemaManager.cacheTable(tableSchema);
    }
}
复制代码
5、插入行数据事件处理器
复制代码
@Component
@RequiredArgsConstructor
public class EventInsertRowProcess implements EventProcess<WriteRowsEventData> {
    private final MessagePublisher messagePublisher;
    private final TableSchemaManager tableSchemaManager;
    private final InsertEventParser insertEventParser;
    @Override
    public boolean support(EventType eventType) {
        return EventType.EXT_WRITE_ROWS == eventType;
    }
    @Override
    public void process(WriteRowsEventData eventData) {
        // 表结构数据获取
        TableSchema cacheSchema = tableSchemaManager.getCacheSchema(eventData.getTableId());
        // 解析插入的数据事件
        InsertMessage insertMessage = insertEventParser.parseEvent(eventData, cacheSchema);
        messagePublisher.publish(insertMessage);
    }
}
复制代码
6、删除行数据 事件处理器
复制代码
@Component
@RequiredArgsConstructor
public class EventDeleteRowProcess implements EventProcess<DeleteRowsEventData> {
    private final MessagePublisher messagePublisher;
    private final TableSchemaManager tableSchemaManager;
    private final DeleteEventParser deleteEventParser;
    @Override
    public boolean support(EventType eventType) {
        return EventType.EXT_DELETE_ROWS == eventType;
    }
    @Override
    public void process(DeleteRowsEventData eventData) {
        long tableId = eventData.getTableId();
        // 表结构数据
        TableSchema cacheSchema = tableSchemaManager.getCacheSchema(tableId);
        DeleteMessage deleteMessage = deleteEventParser.parseEvent(eventData, cacheSchema);
        // 发送解析后的消息数据
        messagePublisher.publish(deleteMessage);
    }
}
复制代码
7、更行行数据 事件处理器
复制代码
@Component
@RequiredArgsConstructor
public class EventUpdateRowProcess implements EventProcess<UpdateRowsEventData> {
    private final MessagePublisher messagePublisher;
    private final TableSchemaManager tableSchemaManager;
    private final UpdateEventParser updateEventParser;
    @Override
    public boolean support(EventType eventType) {
        return EventType.EXT_UPDATE_ROWS == eventType;
    }
    @Override
    public void process(UpdateRowsEventData eventData) {
        long tableId = eventData.getTableId();
        // 表结构数据获取
        TableSchema cacheSchema = tableSchemaManager.getCacheSchema(tableId);
        // 解析 binlog 事件数据
        UpdateMessage updateMessage = updateEventParser.parseEvent(eventData, cacheSchema);
        // 发送消息数据
        messagePublisher.publish(updateMessage);
    }
}
复制代码

表结构数据信息 schema 获得:

关于表结构获取的方式有两种,    一种是 主动查询数据库,  第二种是 设置 MySQL 配置 , binlog  事件中 携带表结构数据(此法不推荐,每次事件都携带表结构数据将是不小的 IO 开支)

关于两种方案的是有在我的第一篇文章中有叙述:https://www.cnblogs.com/Alay/p/15115543.html

第一种:修改 MySQL配置,binlog 日志数据携带表 schema 信息

不推荐,每次事件都需要携带 表 schema 的数据,但并不是每次事件都需要 schema 信息数据,浪费 IO

第二种:自行编写代码按需查询

 推荐使用;自行编写代码查询数据库 schema 信息并缓存使用,代码如下:

复制代码
@Component
@RequiredArgsConstructor
public class TableSchemaManager {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    /**
     * tableId 与 表全名的映射关系
     */
    private static final Map<Long, String> TABLE_ID_MAP = new HashMap<>(1 << 4);
    /**
     * 表名(全名)与表结构的映射
     */
    private static final Map<String, TableSchema> SCHEMA_MAP = new HashMap<>(1 << 4);

    private final IBinlogProperties binlogProperties;
    private String url;

    @EventListener(value = WebServerInitializedEvent.class)
    public void initSqlUrl() {
        url = String.format("jdbc:mysql://%s:%s/information_schema", binlogProperties.host(), binlogProperties.port());
    }

    private static final String TABLE_SCHEMA_SQL =
            "SELECT " +
                "`TABLE_SCHEMA`," +
                "`TABLE_NAME`," +
                "`COLUMN_NAME`," +
                "`ORDINAL_POSITION`," +
                "`COLUMN_DEFAULT`," +
                "`IS_NULLABLE`," +
                "`DATA_TYPE`," +
                "`CHARACTER_MAXIMUM_LENGTH`," +
                "`CHARACTER_OCTET_LENGTH`," +
                "`NUMERIC_PRECISION`," +
                "`NUMERIC_SCALE`," +
                "`CHARACTER_SET_NAME`," +
                "`COLLATION_NAME` " +
            "FROM" +
                "`information_schema`.`columns` " +
            "WHERE " +
                "`TABLE_SCHEMA` = ? " +
            "AND `TABLE_NAME` = ?";


    public TableSchema queryTableSchema(Long tableId, String database, String tableName) {
        // 内存中获取
        TableSchema tableSchema = SCHEMA_MAP.get(database + "." + tableName);
        if (null != tableSchema) return tableSchema;

        // 数据库中查询获取
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet rs = null;
        try {
            connection = DriverManager.getConnection(url, binlogProperties.username(), binlogProperties.password());
            statement = connection.prepareStatement(TABLE_SCHEMA_SQL);
            statement.setString(1, database);
            statement.setString(2, tableName);

            // 执行 Query 查询 语句
            rs = statement.executeQuery();
            tableSchema = TableSchema.builder(database, tableName);
            while (rs.next()) {
                // 字段创建
                TableSchema.Column column = tableSchema.buildColumn(rs.getString(3));
                column.ordinalPosition(rs.getInt(4));
                column.columnDefault(rs.getString(5));
                column.isNullable(rs.getString(6));
                column.dataType(rs.getString(7));
                column.characterMaximumLength(rs.getString(8));
                column.characterOctetLength(rs.getString(9));
                column.numericPrecision(rs.getString(10));
                column.numericScale(rs.getString(11));
                column.characterSetName(rs.getString(12));
                column.collationName(rs.getString(13));

                // 每个字段
                tableSchema.addColumn(column);
            }
            // 排序
            List<TableSchema.Column> columns = tableSchema.columns().stream().sorted().collect(Collectors.toList());
            // 每个字段的详细数据,临时使用,为了减少存储空间,使用完后,将字段数据设置为 null
            tableSchema.columns(null);

            List<String> columnNames = columns.stream().map(TableSchema.Column::columnName).collect(Collectors.toList());
            tableSchema.columnNames(columnNames);

            tableSchema.tableId(tableId);
            return tableSchema;
        } catch (SQLException e) {
            logger.error("message:{}, case:{}", e.getMessage(), e.getCause());
            throw new RuntimeException(e.getMessage());
        } finally {
            try {
                if (null != rs) rs.close();
            } catch (SQLException e) {
                throw new RuntimeException(e.getMessage());
            }
            try {
                if (null != statement) statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e.getMessage());
            }
            try {
                if (null != connection) connection.close();
            } catch (SQLException e) {
                throw new RuntimeException(e.getMessage());
            }
        }
    }

    public TableSchema getCacheSchema(Long tableId) {
        String tableInfo = TABLE_ID_MAP.get(tableId);
        return SCHEMA_MAP.get(tableInfo);
    }

    public boolean hasCache(Long tableId) {
        return TABLE_ID_MAP.get(tableId) != null;
    }

    public void cacheTable(TableSchema tableSchema) {
        // 本地缓存
        SCHEMA_MAP.put(tableSchema.tableSchema() + "." + tableSchema.tableName(), tableSchema);
        // tableId 映射关系缓存
        TABLE_ID_MAP.put(tableSchema.tableId(), tableSchema.tableSchema() + "." + tableSchema.tableName());
    }


    public void removeCache(String tableInfo) {
        TableSchema tableSchema = SCHEMA_MAP.get(tableInfo);
        if (null != tableSchema) {
            // 移除
            TABLE_ID_MAP.remove(tableSchema.tableId());
            SCHEMA_MAP.remove(tableInfo);
        }
    }

}
复制代码

 

Event 数据解析

核心接口:

复制代码
public interface BinlogEventParser<E extends EventData, T extends BinlogMessage> {
    /**
     * 解析 Binlog 事件
     * @param event Binlog 事件数据
     * @param args  其他参数数据(可能为null,只是部分事件解析器需要使用)
     * @return
     */
    T parseEvent(E event, Object... args);
}
复制代码

实现类:

(并不是所有的 事件类型都编写了实现类,仅仅对常用的事件类型进行了编写,需要扩展的请自行扩展)

1、创建表事件消息解析

复制代码
@Component
public class CreateEventParser implements BinlogEventParser<QueryEventData, BinlogMessage> {
    private static final String ACTION = "CREATE";
    @Override
    public BinlogMessage parseEvent(QueryEventData event, Object... args) {
        // 解析表名
        String tableName = this.parseTableName(event.getSql());
        // 暂时没有过多的数据封装
        return BinlogMessage.builder()
                .sql(event.getSql())
                .schema(event.getDatabase())
                .tableName(tableName)
                .action(ACTION);
    }
    /**
     * CREATE TABLE `t_student` ( `id` INT, `name` VARCHAR ( 20 ), `sex` VARCHAR ( 2 ), `password` VARCHAR ( 32 ), `birthday` DATETIME )
     * @param sql
     * @return
     */
    private String parseTableName(String sql) {
        // 去除 符号 `
        sql = sql.replaceAll("`", "");
        // 去除 CREATE TABLE
        String str = sql.substring(13);
        // 得到表名
        String tableName = str.substring(0, str.indexOf(" "));
        return tableName;
    }
}
复制代码

2、删除表事件 消息解析

复制代码
@Component
public class DropEventParser implements BinlogEventParser<QueryEventData, BinlogMessage> {
    private static final String ACTION = "DROP";
    @Override
    public BinlogMessage parseEvent(QueryEventData event, Object... args) {
        // DROP TABLE IF EXISTS `t_student`
        String tableName = this.parseTableName(event.getSql());
        // 暂时没有过多的数据封装
        return BinlogMessage.builder()
                .sql(event.getSql())
                .schema(event.getDatabase())
                .tableName(tableName)
                .action(ACTION);
    }
    private String parseTableName(String sql) {
        // DROP TABLE IF EXISTS `t_student`
        sql = sql.replaceAll("`", "");

        String tableName = sql.substring(sql.lastIndexOf(" ") + 1);
        return tableName;
    }
}
复制代码

3、修改消息解析器

复制代码
@Component
public class AlterEventParser implements BinlogEventParser<QueryEventData, BinlogMessage> {
    private static final String ACTION = "ALTER";
    @Override
    public BinlogMessage parseEvent(QueryEventData event, Object... args) {
        /**
         * 修改表结构的事件 ( db_name.table_name )
         * ALTER TABLE xtech_office.t_student ADD COLUMN `xxx`(这个是字段名称) varchar(255) NULL AFTER birthday
         * ALTER TABLE xtech_office.t_student MODIFY COLUMN `xxxx`
         * ALTER TABLE xtech_office.t_student DROP COLUMN `xxx` (这个是字段名称)
         */
        String tableName = this.parseTableName(event.getSql());

        return BinlogMessage.builder()
                .schema(event.getDatabase())
                .tableName(tableName)
                .sql(event.getSql()).action(ACTION);
    }
    private String parseTableName(String sql) {
        sql = sql.replaceAll("`", "");
        /**
         * 修改表结构的事件 ( db_name.table_name )
         * ALTER TABLE xtech_office.t_student ADD COLUMN `xxx`(这个是字段名称) varchar(255) NULL AFTER birthday
         * ALTER TABLE xtech_office.t_student MODIFY COLUMN `xxxx`
         * ALTER TABLE xtech_office.t_student DROP COLUMN `xxx` (这个是字段名称)
         */
        String str = sql.substring(sql.indexOf("."));
        String tableName = str.substring(1,str.indexOf(" "));
        return tableName;
    }
}
复制代码

4、重命名表事件 消息解析

复制代码
@Component
public class RenameEventParser implements BinlogEventParser<QueryEventData, RenameMessage> {
    private static final String ACTION = "RENAME";
    @Override
    public RenameMessage parseEvent(QueryEventData event, Object... args) {
        // DROP TABLE IF EXISTS `t_student`
        String[] tableNameArr = this.parseTableName(event.getSql());
        // 暂时没有过多的数据封装
        RenameMessage renameMessage = RenameMessage.builder()
                .beforeTable(tableNameArr[0])
                .afterTable(tableNameArr[1]);

        renameMessage.sql(event.getSql())
                .schema(event.getDatabase())
                .tableName(tableNameArr[1])
                .action(ACTION);
        return renameMessage;
    }
    private String[] parseTableName(String sql) {
        // RENAME TABLE `database`.`table_name` TO `database`.`new_table_name`
        sql = sql.replaceAll("`", "");
        String[] tableNameArr = new String[2];
        // 去除 RENAME TABLE
        String str = sql.substring(13);
        String schema = str.substring(0, str.indexOf(" "));
        String beforeName = schema.substring(schema.indexOf('.') + 1);
        tableNameArr[0] = beforeName;

        // 新的表名
        String afterName = str.substring(str.lastIndexOf(".") + 1);
        tableNameArr[1] = afterName;

        return tableNameArr;
    }
}
复制代码

5、插入数据事件 消息解析

复制代码
@Component
public class InsertEventParser implements BinlogEventParser<WriteRowsEventData, InsertMessage> {
    private static final String ACTION = "INSERT";
    @Override
    public InsertMessage parseEvent(WriteRowsEventData event, Object... args) {
        // 表结构
        TableSchema tableSchema = (TableSchema) args[0];

        // 新增的这一行数据
        List<Serializable[]> newRows = event.getRows();

        Map<String, Serializable> insertData = this.tableDataParse(tableSchema.columnNames(), newRows);

        InsertMessage insertMessage = InsertMessage.builder().insertData(insertData);
        insertMessage.tableId(event.getTableId())
                .tableName(tableSchema.tableName())
                .schema(tableSchema.tableSchema())
                .action(ACTION);

        return insertMessage;
    }
    private Map<String, Serializable> tableDataParse(List<String> columnNames, List<Serializable[]> newRows) {
        Map<String, Serializable> data = new HashMap<>();
        Serializable[] valueArr = newRows.get(0);
        for (int i = 0; i < columnNames.size(); i++) {
            String columName = columnNames.get(i);
            data.put(columName, valueArr[i]);
        }
        return data;
    }
}
复制代码

6、删除数据事件 消息解析

复制代码
@Component
public class DeleteEventParser implements BinlogEventParser<DeleteRowsEventData,DeleteMessage> {
    private static final String ACTION = "DELETE";
    @Override
    public DeleteMessage parseEvent(DeleteRowsEventData event, Object... args) {
        // 表结构
        TableSchema tableSchema = (TableSchema) args[0];
        // 删除的这一行数据
        List<Serializable[]> deleteRows = event.getRows();

        Map<String, Serializable> insertData = this.tableDataParse(tableSchema.columnNames(), deleteRows);

        DeleteMessage deleteMessage = DeleteMessage.builder().deleteData(insertData);
        deleteMessage.tableId(event.getTableId())
                .tableName(tableSchema.tableName())
                .schema(tableSchema.tableSchema())
                .action(ACTION);
        return deleteMessage;
    }
    private Map<String, Serializable> tableDataParse(List<String> columnNames, List<Serializable[]> deleteRows) {
        Map<String, Serializable> data = new HashMap<>();
        Serializable[] valueArr = deleteRows.get(0);
        for (int i = 0; i < columnNames.size(); i++) {
            String columName = columnNames.get(i);
            data.put(columName, valueArr[i]);
        }
        return data;
    }
}
复制代码

7、更新消息解析器

复制代码
@Component
public class UpdateEventParser implements BinlogEventParser<UpdateRowsEventData, UpdateMessage> {
    private static final String ACTION = "UPDATE";
    @Override
    public UpdateMessage parseEvent(UpdateRowsEventData event, Object... args) {
        // 表结构
        TableSchema tableSchema = (TableSchema) args[0];
        List<String> columnNames = tableSchema.columnNames();
        // 修改前的数据
        List<Map.Entry<Serializable[], Serializable[]>> rows = event.getRows();
        // 解析数据
        Map<String, Serializable>[] maps = this.tableDataParse(columnNames, rows);
        UpdateMessage updateMessage = UpdateMessage.builder()
                .beforeData(maps[0])
                .afterData(maps[1]);

        updateMessage.tableId(event.getTableId())
                .tableName(tableSchema.tableName())
                .schema(tableSchema.tableSchema())
                .action(ACTION);
        return updateMessage;
    }
    private Map<String, Serializable>[] tableDataParse(List<String> columnNames, List<Map.Entry<Serializable[], Serializable[]>> rows) {
        Map<String, Serializable>[] dataMapArr = new HashMap[2];
        // 数据体
        Map.Entry<Serializable[], Serializable[]> entry = rows.get(0);
        // 修改前的数据
        Serializable[] before = entry.getKey();
        Map<String, Serializable> beforeData = new HashMap<>();
        for (int i = 0; i < columnNames.size(); i++) {
            String columName = columnNames.get(i);
            beforeData.put(columName, before[i]);
        }
        dataMapArr[0] = beforeData;
        // 修改后的数据
        Serializable[] after = entry.getValue();
        Map<String, Serializable> afterData = new HashMap<>();
        for (int i = 0; i < columnNames.size(); i++) {
            String columName = columnNames.get(i);
            afterData.put(columName, after[i]);
        }
        dataMapArr[1] = afterData;
        return dataMapArr;
    }
}
复制代码

事件数据体示例展示:

1、修改表结构的事件数据实例如下:(以修改表结构为例)

复制代码
Event{
  header=EventHeaderV4{
     timestamp=1609379484000, 
     eventType=QUERY, 
     serverId=1, 
     headerLength=19, 
     dataLength=256, 
     nextPosition=20948893, 
     flags=0
  }, 
  data=QueryEventData{
     threadId=305142, 
     executionTime=1, 
     errorCode=0, 
     database='braineex', 
     sql='ALTER TABLE `database_name`.`table_name`    ------------>> 修改表结构事件
     MODIFY COLUMN 
       `nick_name` varchar(31) 
       CHARACTER SET utf8mb4 
       COLLATE utf8mb4_general_ci NULL 
       DEFAULT NULL 
       COMMENT '昵称' 
       AFTER `age`'
  }
}
复制代码

2、增、删、改 事件的数据体实例如下:

复制代码
Event{
  header=EventHeaderV4{
    timestamp=1609380048000, 
    eventType=QUERY, 
    serverId=1, 
    headerLength=19, 
    dataLength=60, 
    nextPosition=20949876, 
    flags=8
 }, 
 data=QueryEventData{
    threadId=305142, 
    executionTime=0, 
    errorCode=0, 
    database='database_name', 
    sql='BEGIN'
  }
}
复制代码

 

消息发送器

解析后的消息,发送出去

核心发送接口:

public interface MessagePublisher {
    /**
     * 日志消息发送器
     * @param message
     */
    void publish(BinlogMessage message);
}

1、通过 Spring 的事件 发送(默认实现)

这也是一个默认的实现方式, 可通过注入其他方式发送器,替换 此默认发送器、

复制代码
/**
 * Spring 事件发送实现方案(默认实现方案)
 * @author Alay
 * @date 2022-11-17 20:23
 */
@RequiredArgsConstructor
public class EventMessagePublisher implements MessagePublisher {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final ApplicationContext context;

    @Override
    public void publish(BinlogMessage message) {
        logger.info(message.toString());
        MessageEvent messageEvent = new MessageEvent(message);
        context.publishEvent(messageEvent);
    }


    public static class MessageEvent extends ApplicationEvent {
        public MessageEvent(Object source) {
            super(source);
        }
    }
}
复制代码

 

MQ 方式发送消息方案(未实现)

未实现的发送器示例(请根据需求自行实现)

1、Kafka
复制代码
/**
 * Kafka 发送消息实现方案
 * 注入时 请使用注解 @Primary ,使其优先于 EventMessageSender
 * @author Alay
 * @date 2022-11-17 21:54
 */
public class KafkaMessagePublisher implements MessagePublisher {
    @Override
    public void publish(BinlogMessage message) {
        // 这里只是示例,使用者请自行去实现,注入时 请使用注解 @Primary ,使其优先于 EventMessageSender
    }
}
复制代码
2、Rabbit
复制代码
/**
 * RabbitMQ 消息队列发送实现方案
 * 注入时 请使用注解 @Primary ,使其优先于 EventMessageSender
 * @author Alay
 * @date 2022-11-17 21:55
 */
public class RabbitMessagePublisher implements MessagePublisher {
    @Override
    public void publish(BinlogMessage message) {
        // 这里只是示例,使用者请自行去实现,注入时 请使用注解 @Primary ,使其优先于 EventMessageSender
    }
}
复制代码

 实体类消息体:

这里仅仅编写核心字段,为了精简代码,getter / setter  等附带的函数就不在下面代码中展示

1、通用的 消息体:
复制代码
@Getter
@ToString
public class BinlogMessage implements Serializable {
    /**
     * 事件的 TableId, 可能为null( CREATE/ALTER/RENAME时候为 null)
     */
    private Long tableId;
    /**
     * 库名(不会为 null )
     */
    private String schema;
    /**
     * 表名(不会为 null )
     *
     * @return
     */
    private String tableName;

    /**
     * SQL 语句,可能为null( 只有 CREATE TABLE / DROP TABLE / ALTER TABLE / RENAME TABLE 时候才有 )
     *
     * @return
     */
    private String sql;
    /**
     * 操作的动作
     */
    private String action;
}
复制代码
2、表重命名 事件
复制代码
@Getter
public class RenameMessage extends BinlogMessage {
    /**
     * 修改前的表名
     */
    private String beforeTable;
    /**
     * 修改后的表名
     */
    private String afterTable;
}
复制代码
3、插入事件消息
@Setter
public class InsertMessage extends BinlogMessage {
    /**
     * 插入的数据内容
     */
    private Map<String, Serializable> insertData;
}
4、删除事件消息
@Getter
public class DeleteMessage extends BinlogMessage {
    private Map<String, Serializable> deleteData;
}
5、更新数据事件消息
复制代码
@Getter
public class UpdateMessage extends BinlogMessage {
    /**
     * 修改前的数据
     */
    private Map<String, Serializable> beforeData;
    /**
     * 修改后的数据
     */
    private Map<String, Serializable> afterData;
}
复制代码
6、表接口实体:
复制代码
@Getter
@Setter
@Accessors(chain = true, fluent = true)
public class TableSchema implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 给binlog 使用的
     */
    private Long tableId;
    /**
     * 数据库名称
     */
    private String tableSchema;
    /**
     * 数据表名
     */
    private String tableName;
    /**
     * 字段
     */
    private List<Column> columns;
    /**
     * 字段名称
     */
    private List<String> columnNames;
    @Getter
    @Setter
    public static class Column implements Comparable<Column> {
        /**
         * 字段名
         */
        private String columnName;
        /**
         * 表结构字段排序 1,2,3, ....
         */
        private Integer ordinalPosition;
        /**
         * 默认值
         */
        private String columnDefault;
        /**
         * 是否可以为 null (YES/NO)
         */
        private String isNullable;
        /**
         * 数据类型 (varchar / int / date ....)
         */
        private String dataType;
        /**
         * 数据类型长度
         */
        private String characterMaximumLength;
        /**
         * 以字节为单位的最大长度
         */
        private String characterOctetLength;

        private String numericPrecision;
        private String numericScale;
        /**
         * 字符集 utf8
         */
        private String characterSetName;
        /**
         * utf8_general_ci
         */
        private String collationName;

        @Override
        public int compareTo(Column o) {
            return Integer.compare(this.ordinalPosition, o.ordinalPosition);
        }
    }
    public static TableSchema builder(String dbname, String tableName) {
        TableSchema schema = new TableSchema();
        schema.tableSchema = dbname;
        schema.tableName = tableName;
        schema.columns = new ArrayList<>();
        schema.columnNames = new ArrayList<>();
        return schema;
    }
    public Column buildColumn(String columnName) {
        Column column = new Column();
        column.columnName = columnName;
        return column;
    }
    public void addColumn(Column column) {
        this.columns.add(column);
    }
}
复制代码

 


使用示例:

1、 引入依赖 

以上的封装是将其封装为 一个独立的模块

<dependency>
    <groupId>com.kwanyon.starter</groupId>
    <artifactId>kwanyon-common-binlog</artifactId>
</dependency>

2、主启动类或者配置类,加入启动开关

@EnableBinlogListener

即可启动使用

3、配置文件:

复制代码
# 这里是示例的配置,开发中配置已迁移到 nacos
kwanyon:
  # binlog 连接配置
  binlog:
    host: ${MASTER_HOST:spring.datasource.host}
    port: ${spring.datasource.port:3306}
    username: ${spring.datasource.druid.username:root}
    password: ${spring.datasource.druid.password:123456}
    tables:
      # 这里配置的意思是, binlog 日志监听的表千千万,但是我只处理我这里配置的表名(微服务中每个模块单独配置,不同的模块 监听处理的是不同的表事件)
      - database.table_name_1
      - database.table_name_2
复制代码

 

posted @   Vermeer  阅读(634)  评论(2编辑  收藏  举报

喜欢请打赏

扫描二维码打赏

了解更多

点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起