对于最新稳定版本,请使用 Spring for Apache Kafka 4.0.0spring-doc.cadn.net.cn

技巧、窍门和示例

手动分配所有分区

假设你想始终读取所有分区的所有记录(比如使用压缩主题加载分布式缓存时),手动分配分区而不使用Kafka的组管理会很有用。 当分区很多时,这样做会很麻烦,因为你必须列出分区。 如果分区数量随时间变化,也是一个问题,因为每次分区数量变化时你都得重新编译你的应用程序。spring-doc.cadn.net.cn

以下是如何利用 SpEL 表达式的强大功能在应用程序启动时动态创建分区列表的示例:spring-doc.cadn.net.cn

@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=最早每次启动应用时都会加载所有记录。 你还应该设置容器的AckMode手动以防止容器对消费者集团。 从3.1版本开始,容器会自动强制AckMode手动当使用手动主题分配且无用户时group.id. 然而,从版本 2.5.5 开始,如上所示,你可以对所有分区应用初始偏移;更多信息请参见显式分区分配spring-doc.cadn.net.cn

卡夫卡与其他事务管理器的交易示例

以下 Spring Boot 应用是数据库和 Kafka 交易链式连接的一个示例。 监听器容器启动Kafka事务,且@Transactional注释启动数据库事务。 数据库事务首先提交;如果 Kafka 事务未能提交,记录将被重新交付,因此数据库更新应为幂等元。spring-doc.cadn.net.cn

@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://localhost/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));

对于仅生产者事务,事务同步是:spring-doc.cadn.net.cn

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

卡夫卡模板会将其事务与数据库事务同步,提交/回滚发生在数据库之后。spring-doc.cadn.net.cn

如果你想先提交 Kafka 事务,只有在 Kafka 事务成功时才提交数据库事务,可以使用嵌套@Transactional方法:spring-doc.cadn.net.cn

@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

串行器和反串行器支持多种属性自定义,详见 JSON 相关信息。 这卡夫卡客户端代码而非 Spring 实例化这些对象,除非你直接将它们注入到消费者和生产者工厂中。 如果你想用属性配置(去)串行器,但又想用自定义工具对象映射器,只需创建一个子类并将自定义映射器传递到构造 函数。例如:spring-doc.cadn.net.cn

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;
    }

}