Streaming 当检查点还原时,flink kafkaproducer以一次模式发送重复消息

Streaming 当检查点还原时,flink kafkaproducer以一次模式发送重复消息,streaming,apache-flink,flink-streaming,blink,Streaming,Apache Flink,Flink Streaming,Blink,我正在写一个测试flink两步提交的案例,下面是概述 辛克·卡夫卡曾经是卡夫卡的制作人。接收步骤是mysql接收扩展两步提交。sink compare是mysql sink extend两步提交,该接收器偶尔会抛出一个exeption来模拟检查点失败 当检查点失败并恢复时,我发现mysql两步提交可以正常工作,但kafka使用者将读取上次成功的偏移量,而kafka生产者将生成消息,即使他在检查点失败之前已经这样做了 在这种情况下如何避免重复消息 谢谢你的帮助 环境: 弗林克1.9.1 java

我正在写一个测试flink两步提交的案例,下面是概述

辛克·卡夫卡曾经是卡夫卡的制作人。接收步骤是mysql接收扩展两步提交。sink compare是mysql sink extend两步提交,该接收器偶尔会抛出一个exeption来模拟检查点失败

当检查点失败并恢复时,我发现mysql两步提交可以正常工作,但kafka使用者将读取上次成功的偏移量,而kafka生产者将生成消息,即使他在检查点失败之前已经这样做了

在这种情况下如何避免重复消息

谢谢你的帮助

环境:

弗林克1.9.1

java 1.8

卡夫卡2.11

卡夫卡制作人代码:

mysql接收器:


我不太确定我是否正确理解了这个问题:

当检查点失败并恢复时,我发现mysql两步提交可以正常工作,但kafka producer将读取上次成功的偏移量并生成消息,即使他在检查点失败之前已经完成了

卡夫卡制作人没有读取任何数据。所以,我假设您的整个管道重新读取旧的偏移量并生成重复的偏移量。如果是这样的话,你需要了解弗林克是如何确保一次

创建定期检查点以在出现故障时保持一致状态。 这些检查点包含检查点时上次成功读取记录的偏移量。 恢复后,Flink将从上次成功检查点中存储的偏移量中重新读取所有记录。因此,将重放在最后一个检查点和失败之间生成的相同记录。 重放的记录将恢复故障前的状态。 它将产生源自重放输入记录的重复输出。 接收器有责任确保没有副本有效写入目标系统。 对于最后一点,有两个选项:

当写入检查点时,仅输出数据,这样目标中就不会出现有效的副本。这种简单的方法是非常通用的,与接收器无关,但会将检查点间隔添加到延迟中。 让接收器消除输出中的重复数据。
后一个选项用于卡夫卡水槽。它使用Kafka事务来消除重复数据。为了避免消费者端的重复,您需要确保它没有读取。还要确保事务超时足够大,不会在故障和恢复之间丢弃数据。

我不太确定我是否正确理解了这个问题:

当检查点失败并恢复时,我发现mysql两步提交可以正常工作,但kafka producer将读取上次成功的偏移量并生成消息,即使他在检查点失败之前已经完成了

卡夫卡制作人没有读取任何数据。所以,我假设您的整个管道重新读取旧的偏移量并生成重复的偏移量。如果是这样的话,你需要了解弗林克是如何确保一次

创建定期检查点以在出现故障时保持一致状态。 这些检查点包含检查点时上次成功读取记录的偏移量。 恢复后,Flink将从上次成功检查点中存储的偏移量中重新读取所有记录。因此,将重放在最后一个检查点和失败之间生成的相同记录。 重放的记录将恢复故障前的状态。 它将产生源自重放输入记录的重复输出。 接收器有责任确保没有副本有效写入目标系统。 对于最后一点,有两个选项:

当写入检查点时,仅输出数据,这样目标中就不会出现有效的副本。这种简单的方法是非常通用的,与接收器无关,但会将检查点间隔添加到延迟中。 让接收器消除输出中的重复数据。
后一个选项用于卡夫卡水槽。它使用Kafka事务来消除重复数据。为了避免消费者端的重复,您需要确保它没有读取。还要确保您的事务超时足够大,不会在失败和恢复之间丢弃数据。

我很高兴看到完美的答案!我错过了kafak使用者中的-isolation level=read_committed配置。它提醒我要更仔细地阅读文件。非常感谢。你的MySQL 2pc代码有效吗?我很高兴看到完美的答案!我错过了kafak使用者中的-isolation level=read_committed配置。它提醒我要更仔细地阅读文件。非常感谢。你的MySQL 2pc代码有效吗?
        dataStreamReduce.addSink(new FlinkKafkaProducer<>(
                "flink_output",
                new KafkaSerializationSchema<Tuple4<String, String, String, Long>>() {
                    @Override
                    public ProducerRecord<byte[], byte[]> serialize(Tuple4<String, String, String, Long> element, @Nullable Long timestamp) {
                        UUID uuid = UUID.randomUUID();
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put("uuid", uuid.toString());
                        jsonObject.put("key1", element.f0);
                        jsonObject.put("key2", element.f1);
                        jsonObject.put("key3", element.f2);
                        jsonObject.put("indicate", element.f3);
                        return new ProducerRecord<>("flink_output", jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8));
                    }
                },
                kafkaProps,
                FlinkKafkaProducer.Semantic.EXACTLY_ONCE
        )).name("sink kafka");
        StreamExecutionEnvironment executionEnvironment = StreamExecutionEnvironment.getExecutionEnvironment();
        executionEnvironment.enableCheckpointing(10000);
        executionEnvironment.getCheckpointConfig().setTolerableCheckpointFailureNumber(0);
        executionEnvironment.getCheckpointConfig().setPreferCheckpointForRecovery(true);
dataStreamReduce.addSink(
                new TwoPhaseCommitSinkFunction<Tuple4<String, String, String, Long>,
                        Connection, Void>
                        (new KryoSerializer<>(Connection.class, new ExecutionConfig()), VoidSerializer.INSTANCE) {

                    int count = 0;
                    Connection connection;

                    @Override
                    protected void invoke(Connection transaction, Tuple4<String, String, String, Long> value, Context context) throws Exception {
                        if (count > 10) {
                            throw new Exception("compare test exception.");
                        }
                        PreparedStatement ps = transaction.prepareStatement(
                                " insert into test_two_step_compare(slot_time, key1, key2, key3, indicate) " +
                                        " values(?, ?, ?, ?, ?) " +
                                        " ON DUPLICATE KEY UPDATE indicate = indicate + values(indicate) "
                        );
                        ps.setString(1, context.timestamp().toString());
                        ps.setString(2, value.f0);
                        ps.setString(3, value.f1);
                        ps.setString(4, value.f1);
                        ps.setLong(5, value.f3);
                        ps.execute();
                        ps.close();
                        count += 1;
                    }

                    @Override
                    protected Connection beginTransaction() throws Exception {
                        LOGGER.error("compare in begin transaction");
                        try {
                            if (connection.isClosed()) {
                                throw new Exception("mysql connection closed");
                            }
                        }catch (Exception e) {
                            LOGGER.error("mysql connection is error: " + e.toString());
                            LOGGER.error("reconnect mysql connection");
                            String jdbcURI = "jdbc:mysql://";
                            Class.forName("com.mysql.jdbc.Driver");
                            Connection connection = DriverManager.getConnection(jdbcURI);
                            connection.setAutoCommit(false);
                            this.connection = connection;
                        }
                        return this.connection;
                    }

                    @Override
                    protected void preCommit(Connection transaction) throws Exception {
                        LOGGER.error("compare in pre Commit");
                    }

                    @Override
                    protected void commit(Connection transaction) {
                        LOGGER.error("compare in commit");
                        try {
                            transaction.commit();
                        } catch (Exception e) {
                            LOGGER.error("compare Commit error: " + e.toString());
                        }
                    }

                    @Override
                    protected void abort(Connection transaction) {
                        LOGGER.error("compare in abort");
                        try {
                            transaction.rollback();
                        } catch (Exception e) {
                            LOGGER.error("compare abort error." + e.toString());
                        }
                    }

                    @Override
                    protected void recoverAndCommit(Connection transaction) {
                        super.recoverAndCommit(transaction);
                        LOGGER.error("compare in recover And Commit");
                    }

                    @Override
                    protected void recoverAndAbort(Connection transaction) {
                        super.recoverAndAbort(transaction);
                        LOGGER.error("compare in recover And Abort");
                    }
                })
                .setParallelism(1).name("sink compare");