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