提示、技巧和示例

手動分配所有分割槽

假設您希望始終讀取所有分割槽中的所有記錄(例如在使用壓縮主題載入分散式快取時),手動分配分割槽而不使用 Kafka 的組管理可能會很有用。當分割槽很多時,這樣做可能很麻煩,因為您必須列出所有分割槽。如果分割槽數量隨著時間而變化,這也是一個問題,因為每次分割槽計數變化時,您都必須重新編譯您的應用程式。

下面是一個示例,說明如何在應用程式啟動時利用 SpEL 表示式的強大功能動態建立分割槽列表

@KafkaListener(topicPartitions = @TopicPartition(topic = "compacted",
            partitions = "#{@finder.partitions('compacted')}",
            partitionOffsets = @PartitionOffset(partition = "*", initialOffset = "0")))
public void listen(@Header(KafkaHeaders.RECEIVED_KEY) String key, String payload) {
    ...
}

@Bean
public PartitionFinder finder(ConsumerFactory<String, String> consumerFactory) {
    return new PartitionFinder(consumerFactory);
}

public static class PartitionFinder {

    private final ConsumerFactory<String, String> consumerFactory;

    public PartitionFinder(ConsumerFactory<String, String> consumerFactory) {
        this.consumerFactory = consumerFactory;
    }

    public String[] partitions(String topic) {
        try (Consumer<String, String> consumer = consumerFactory.createConsumer()) {
            return consumer.partitionsFor(topic).stream()
                .map(pi -> "" + pi.partition())
                .toArray(String[]::new);
        }
    }

}

結合使用 ConsumerConfig.AUTO_OFFSET_RESET_CONFIG=earliest,每次應用程式啟動時都會載入所有記錄。您還應該將容器的 AckMode 設定為 MANUAL,以防止容器為 null 消費者組提交偏移量。從版本 3.1 開始,當手動主題分配與無消費者 group.id 一起使用時,容器將自動將 AckMode 強制設定為 MANUAL。然而,從版本 2.5.5 開始,如上所示,您可以將初始偏移量應用於所有分割槽;有關更多資訊,請參閱顯式分割槽分配

Kafka 事務與其他事務管理器的示例

以下 Spring Boot 應用程式是連結資料庫和 Kafka 事務的示例。監聽器容器啟動 Kafka 事務,@Transactional 註解啟動資料庫事務。資料庫事務首先提交;如果 Kafka 事務提交失敗,記錄將被重新投遞,因此資料庫更新應該是冪等的。

@SpringBootApplication
public class Application {

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

    @Bean
    public ApplicationRunner runner(KafkaTemplate<String, String> template) {
        return args -> template.executeInTransaction(t -> t.send("topic1", "test"));
    }

    @Bean
    public DataSourceTransactionManager dstm(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Component
    public static class Listener {

        private final JdbcTemplate jdbcTemplate;

        private final KafkaTemplate<String, String> kafkaTemplate;

        public Listener(JdbcTemplate jdbcTemplate, KafkaTemplate<String, String> kafkaTemplate) {
            this.jdbcTemplate = jdbcTemplate;
            this.kafkaTemplate = kafkaTemplate;
        }

        @KafkaListener(id = "group1", topics = "topic1")
        @Transactional("dstm")
        public void listen1(String in) {
            this.kafkaTemplate.send("topic2", in.toUpperCase());
            this.jdbcTemplate.execute("insert into mytable (data) values ('" + in + "')");
        }

        @KafkaListener(id = "group2", topics = "topic2")
        public void listen2(String in) {
            System.out.println(in);
        }

    }

    @Bean
    public NewTopic topic1() {
        return TopicBuilder.name("topic1").build();
    }

    @Bean
    public NewTopic topic2() {
        return TopicBuilder.name("topic2").build();
    }

}
spring.datasource.url=jdbc:mysql:///integration?serverTimezone=UTC
spring.datasource.username=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.kafka.consumer.auto-offset-reset=earliest
spring.kafka.consumer.enable-auto-commit=false
spring.kafka.consumer.properties.isolation.level=read_committed

spring.kafka.producer.transaction-id-prefix=tx-

#logging.level.org.springframework.transaction=trace
#logging.level.org.springframework.kafka.transaction=debug
#logging.level.org.springframework.jdbc=debug
create table mytable (data varchar(20));

對於僅生產者事務,事務同步有效

@Transactional("dstm")
public void someMethod(String in) {
    this.kafkaTemplate.send("topic2", in.toUpperCase());
    this.jdbcTemplate.execute("insert into mytable (data) values ('" + in + "')");
}

KafkaTemplate 將其事務與資料庫事務同步,提交/回滾發生在資料庫之後。

如果您希望先提交 Kafka 事務,並且只有在 Kafka 事務成功後才提交資料庫事務,請使用巢狀的 @Transactional 方法

@Transactional("dstm")
public void someMethod(String in) {
    this.jdbcTemplate.execute("insert into mytable (data) values ('" + in + "')");
    sendToKafka(in);
}

@Transactional("kafkaTransactionManager")
public void sendToKafka(String in) {
    this.kafkaTemplate.send("topic2", in.toUpperCase());
}

自定義 JsonSerializer 和 JsonDeserializer

序列化器和反序列化器支援使用屬性進行多項自定義,有關更多資訊,請參閱JSONkafka-clients 程式碼,而不是 Spring,會例項化這些物件,除非您將它們直接注入消費者和生產者工廠。如果您希望使用屬性配置(反)序列化器,但希望使用(例如)自定義 ObjectMapper,只需建立一個子類並將自定義對映器傳遞給 super 建構函式。例如

public class CustomJsonSerializer extends JsonSerializer<Object> {

    public CustomJsonSerializer() {
        super(customizedObjectMapper());
    }

    private static ObjectMapper customizedObjectMapper() {
        ObjectMapper mapper = JacksonUtils.enhancedObjectMapper();
        mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }

}
© . This site is unofficial and not affiliated with VMware.