debezium 捕获mysql数据二(CDC)

debezium 捕获mysql数据二(CDC)

项目结构

在这里插入图片描述

引入依赖

        
<debezium.version>2.6.2.Final</debezium.version>

<dependency>
  <groupId>io.debezium</groupId>
  <artifactId>debezium-api</artifactId>
  <version>${debezium.version}</version>
</dependency>
<dependency>
  <groupId>io.debezium</groupId>
  <artifactId>debezium-embedded</artifactId>
  <version>${debezium.version}</version>
</dependency>

配置 MysqlDebeziumConfig:

@Configuration
@ConfigurationProperties(prefix = "dbzium.mysql")
@Data
public class MysqlDebeziumConfig {

    private String host;
    private String port;
    private String user;
    private String passwd;
    private String dbs;
    private String serverId;
}

监听数据变动

@Slf4j
public abstract class AbstractDebeziumDataListener implements ApplicationRunner, AutoCloseable {

    private DebeziumEngine<ChangeEvent<String, String>> debeziumEngine;

    private void init() {
        this.debeziumEngine = DebeziumEngine.
                create(Json.class)
                .using(getProperties())
                .notifying(this::handleEvent)
                .using(new DebeziumEngine.CompletionCallback() {
                    @Override
                    public void handle(boolean b, String s, Throwable throwable) {
                        if (throwable != null) {
                            log.error("{} init error", this.getClass().getSimpleName(), throwable);
                        } else {
                            log.info("{} init success", this.getClass().getSimpleName());
                        }
                    }
                })
                .using(new DebeziumEngine.ConnectorCallback() {
                    @Override
                    public void connectorStarted() {
                        DebeziumEngine.ConnectorCallback.super.connectorStarted();
                        log.info("{} connectorStarted", this.getClass().getSimpleName());
                    }

                    @Override
                    public void connectorStopped() {
                        DebeziumEngine.ConnectorCallback.super.connectorStopped();
                        log.info("{} connectorStopped", this.getClass().getSimpleName());
                    }

                    @Override
                    public void taskStarted() {
                        DebeziumEngine.ConnectorCallback.super.taskStarted();
                        log.info("{} taskStarted", this.getClass().getSimpleName());
                    }

                    @Override
                    public void taskStopped() {
                        DebeziumEngine.ConnectorCallback.super.taskStopped();
                        log.info("{} taskStopped", this.getClass().getSimpleName());
                    }
                }).build();
    }

    protected abstract void handleEvent(List<ChangeEvent<String, String>> changeEvents,
                                        DebeziumEngine.RecordCommitter<ChangeEvent<String, String>> changeEventRecordCommitter);


    protected abstract Executor getExecutor();

    /**
     * 配置信息
     *
     * @return
     */
    protected abstract Properties getProperties();

    @Override
    public void run(ApplicationArguments args) throws Exception {
        init();
        start0();
    }

    private void start0() {
        getExecutor().execute(debeziumEngine);
    }


    @Override
    public void close() throws Exception {
        debeziumEngine.close();
    }
}

mysql 实现:

@Component
@Slf4j
public class MysqlDebeziumDataListener extends AbstractDebeziumDataListener {

    @Autowired
    private MysqlDebeziumConfig config;

    @Override
    protected void handleEvent(List<ChangeEvent<String, String>> changeEvents, DebeziumEngine.RecordCommitter<ChangeEvent<String, String>> changeEventRecordCommitter) {
        Iterator<ChangeEvent<String, String>> iterator = changeEvents.iterator();
        while (iterator.hasNext()) {
            ChangeEvent<String, String> next = iterator.next();
            JSONObject jsonObject = JSON.parseObject(next.value());
            if (jsonObject==null || !jsonObject.containsKey("payload")){
                log.info("opt contains no payload: {}", JSON.toJSONString(JSON.parseObject(next.value()),SerializerFeature.PrettyFormat));
                return;
            }
            JSONObject payload = jsonObject.getJSONObject("payload");
            if (payload == null) {
                log.info("no opt record....");
                return;
            }
            String opt = payload.getString("op");
            JSONObject before = payload.getJSONObject("before");
            JSONObject after = payload.getJSONObject("after");
            log.info("opt: {}", opt);
            log.info("before: {}", before == null ? null : before.toJSONString());
            log.info("after: {}", after == null ? null : after.toJSONString());
             //提交!!!!
            try {
                changeEventRecordCommitter.markProcessed(next);
            } catch (InterruptedException e) {
                log.info("commit change event error: {}", JSON.toJSONString(next), e);
            }
        }
    }

    @Override
    protected Executor getExecutor() {
        return Executors.newSingleThreadExecutor(new ThreadFactory() {
            AtomicInteger cnt = new AtomicInteger(0);

            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r, "mysql-dbzium" + cnt.getAndIncrement());
            }
        });
    }

    @Override
    protected Properties getProperties() {
          final Properties prop = new Properties();
        prop.setProperty("name", "engine");
        prop.setProperty("connector.class", "io.debezium.connector.mysql.MySqlConnector");
        prop.setProperty("offset.storage", "org.apache.kafka.connect.storage.FileOffsetBackingStore");
        prop.setProperty("offset.storage.file.filename", "F:\\abc\\dbzium\\offset1.dat");
        prop.setProperty("offset.flush.interval.ms", "60000");
        /* begin connector properties */
        prop.put("database.hostname", config.getHost());
        prop.put("database.port", config.getPort());
        prop.put("database.user", config.getUser());
        prop.put("database.password", config.getPasswd());
        //与mysql的server_id不一样
        prop.setProperty("database.server.id", config.getServerId());
        prop.setProperty("database.include.list", "test");//要捕获的数据库名
        prop.setProperty("table.include.list", "test.t_user");//要捕获的数据表
        prop.put("include.schema.changes", "false");
        prop.setProperty("topic.prefix", "my-app-connector");
        prop.setProperty("schema.history.internal", "io.debezium.storage.file.history.FileSchemaHistory");
        prop.setProperty("schema.history.internal.file.filename", "F:\\abc\\dbzium\\his1.dat");
        return prop;
    }


}

启动类:

@Slf4j
@SpringBootApplication
public class BootDebeziumApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootDebeziumApplication.class, args);
    }

    @Autowired
    private List<AbstractDebeziumDataListener> listener;

    @PostConstruct
    public void init() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            for (AbstractDebeziumDataListener l : listener) {
                try {
                    log.info("AbstractDebeziumDataListener start close : {}", l.getClass().getSimpleName());
                    l.close();
                } catch (Exception e) {
                    log.info("AbstractDebeziumDataListener close error: {}", l.getClass().getSimpleName(), e);
                }
            }
        }));
    }
}

结果:

MysqlDebeziumDataListener  : opt: u
MysqlDebeziumDataListener  : before: {"name":"dsfsdf5","id":8}
MysqlDebeziumDataListener  : after: {"name":"tom","id":8}
MysqlDebeziumDataListener  : opt: d
MysqlDebeziumDataListener  : before: {"name":"ddd","id":6}
MysqlDebeziumDataListener  : after: null

注意

高版本创建 FileOffsetBackingStore 时,已经没有带参数的构造函数了,所以需要 重写KafkaConnectUtil

public class KafkaConnectUtil {

    public static final Converter converterForOffsetStore() {
        final JsonConverter converter = new JsonConverter();
        converter.configure(Collections.singletonMap(JsonConverterConfig.SCHEMAS_ENABLE_CONFIG, "false"), true);
        return converter;
    }

    public static final FileOffsetBackingStore fileOffsetBackingStore() {
        //改成无参就行
        return new FileOffsetBackingStore();
    }

    public static final MemoryOffsetBackingStore memoryOffsetBackingStore() {
        return new MemoryOffsetBackingStore();
    }

    public static final KafkaOffsetBackingStore kafkaOffsetBackingStore(Map<String, String> config) {
        final String clientId = "debezium-server";
        final Map<String, Object> adminProps = new HashMap<>(config);
        adminProps.put(CLIENT_ID_CONFIG, clientId + "shared-admin");

        Stream.of(
                DistributedConfig.BOOTSTRAP_SERVERS_CONFIG,
                DistributedConfig.OFFSET_STORAGE_TOPIC_CONFIG,
                DistributedConfig.OFFSET_STORAGE_PARTITIONS_CONFIG,
                DistributedConfig.OFFSET_STORAGE_REPLICATION_FACTOR_CONFIG).forEach(prop -> {
            if (!adminProps.containsKey(prop)) {
                throw new DebeziumException(String.format(
                        "Cannot initialize Kafka offset storage, mandatory configuration option '%s' is missing",
                        prop));
            }
        });
        SharedTopicAdmin sharedAdmin = new SharedTopicAdmin(adminProps);
        return new KafkaOffsetBackingStore(sharedAdmin);
    }

}

mysql配置

$ cat conf/my.cnf 
[mysqld]
log_bin = mysql-bin
server_id = 1
binlog_format     = ROW                         #必须是ROW
binlog_row_image  = FULL                        #必须是
expire_logs_days  = 10                          #依据实际情况而定
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值