即使没有密钥更改,groupByKey也会创建重新分区主题

时间:2018-04-07 06:42:10

标签: apache-kafka apache-kafka-streams spring-cloud-stream

我试图在kafka流(Kafka 1.0.1)和spring cloud stream(2.0.0-build-snapshot)的帮助下实现一个简单的事件源服务。我的StreamListener方法只读取与我的聚合的状态更改相对应的Kstream事件,并将它们应用于聚合并将最新状态保存在本地状态存储(kafka提供的状态存储)中。域事件消息也具有与聚合的uuid(String)相同的密钥。这是代码:

@StreamListener(Channels.EVENTS_INPUT_CHANNEL)
public void listen(KStream<String, DomainEvent> stream) {
    Serde<DomainEvent> domainEventSerde = new JsonSerde<>(DomainEvent.class);
    Serde<Slot> slotSerde = new JsonSerde<>(Slot.class);
    stream
        .groupByKey(Serialized.with(Serdes.String(), domainEventSerde))
        .aggregate(
                Slot::new, 
                (s, domainEvent, slot) -> slot.handle(domainEvent),
                Materialized.<String, Slot, KeyValueStore<Bytes, byte[]>>
                as(Repository.SNAPSHOTS_FOR_SLOTS)
                    .withKeySerde(Serdes.String()).withValueSerde(slotSerde)
        );
}

上面的代码产生了一个changelog主题(如预期的那样): slot-service-slots-changelog 。虽然它还创建了一个重新分区主题: slot-service-slots-repartition 。这两个主题似乎具有完全相同的消息(键和值)。我的理解是,如果在流上没有完成密钥修改操作,则不需要重新分区。我在这里错过了什么吗?

更新 这可能不再需要了,因为sobychacko已经提供了解释,但是我确实尝试了没有云流绑定,如下所示,它没有创建重新分区主题:

@Configuration
@EnableKafka
@EnableKafkaStreams
public class KafkaConfiguration {

    @Bean
    KafkaTemplate<String, DomainEvent> kafkaTemplate() {
        return new KafkaTemplate<>(producerFactory());
    }

    @Bean
    ProducerFactory<String,DomainEvent> producerFactory() {
        return new DefaultKafkaProducerFactory<>(config());
    }

    private Map<String, Object> config() {
        Map<String, Object> config = new HashMap<>();
        config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
        config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
        return config;
    }

    @Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
    StreamsConfig streamsConfig() {
        Map<String, Object> config = new HashMap<>();
        config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        config.put(StreamsConfig.APPLICATION_ID_CONFIG, "slot-service");
        return new StreamsConfig(config);
    }

    @Bean
    KTable<String, Slot> kTable(KStreamBuilder builder) {
        Serde<DomainEvent> domainEventSerde = new JsonSerde<>(DomainEvent.class);
        Serde<Slot> slotSerde = new JsonSerde<>(Slot.class);

        return
                builder
                .stream(Serdes.String(), domainEventSerde, Repository.SLOT_EVENTS)
                .groupByKey(Serdes.String(), domainEventSerde)
                .aggregate(
                    Slot::new, 
                    (s, domainEvent, slot) -> slot.handle(domainEvent),
                    slotSerde,
                    Repository.SNAPSHOTS_FOR_SLOTS);
    }

    }

此外,制作人如下:

@Autowired
    public Repository(KafkaTemplate<String, DomainEvent> kafkaTemplate, KStreamBuilderFactoryBean kStreamBuilderFactoryBean) {
        this.kafkaTemplate = kafkaTemplate;
        this.kStreamBuilderFactoryBean = kStreamBuilderFactoryBean;
    }

    public void save(Slot slot) {
        List<DomainEvent> newEvents = slot.getDirtyEvents();
        newEvents.forEach(
            domainEvent -> kafkaTemplate.send(SLOT_EVENTS, domainEvent.aggregateUUID().toString(),domainEvent) 
        );
        slot.flushEvents();
    }

更新2:

以下是云流的生产者代码:

public void save(Slot slot) {
        List<DomainEvent> newEvents = slot.getDirtyEvents();
        newEvents.forEach(domainEvent -> channels.eventsOutputChannel().send(MessageBuilder.withPayload(domainEvent)
                .setHeader(KafkaHeaders.MESSAGE_KEY, slot.getUuid().toString()).build()));
        slot.flushEvents();
    }

1 个答案:

答案 0 :(得分:1)

在调用方法之前有一个map()操作,我们在这里进行入站反序列化(我假设在上面的例子中禁用了本机反序列化)。正如Matthias指出的那样,如果有map()操作,则设置一个标志,并在随后的groupByKey()中创建一个重新分区主题。因此,这可能是在您的情况下发生的事情,因为框架在您作为入站邮件转换的一部分执行此map操作。如果您确实想避免创建此重新分区主题,可以启用nativeDecoding,然后使用Kafka提供的Serde。这样,框架不会调用map操作。问题是您的代码中使用的JsonSerde不容易在Spring Cloud Stream中用作Serde属性,因为它需要类信息。在下一版Spring Cloud Stream中,我们将改善这种情况。在此期间,您可以提供自定义Serde。希望这可以帮助。