5. 提示、技巧和示例
5.1. 手动分配所有分区
假设您希望始终从所有分区读取所有记录(例如,在使用压缩主题加载分布式缓存时),手动分配分区而不使用 Kafka 的组管理可能会很有用。 当有很多分区时,这样做可能会很笨拙,因为您必须列出分区。 如果分区数随时间变化,这也是一个问题,因为每次分区计数更改时,都必须重新编译应用程序。
下面是一个示例,说明如何在应用程序启动时使用 SpEL 表达式的强大功能动态创建分区列表:
@KafkaListener(topicPartitions = @TopicPartition(topic = "compacted",
            partitions = "#{@finder.partitions('compacted')}"),
            partitionOffsets = @PartitionOffset(partition = "*", initialOffset = "0")))
public void listen(@Header(KafkaHeaders.RECEIVED_MESSAGE_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消费者群体。
但是,从2.5.5版本开始,如上所示,您可以对所有分区应用初始偏移量;有关详细信息,请参阅显式分区分配。
5.2. 与其他事务管理器的 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://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));
对于仅生产者事务,事务同步有效:
@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 事务成功时才提交数据库事务,请使用 nested@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());
}
5.3. 自定义 JsonSerializer 和 JsonDeserializer
序列化程序和反序列化程序支持使用属性进行多种定制,有关详细信息,请参阅 JSON。
这kafka-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;
    }
}