-
Notifications
You must be signed in to change notification settings - Fork 15.4k
KAFKA-4936: Add dynamic routing in Streams #5018
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
a287ef1
4205260
5d689a9
1cec567
b5bd7b5
a6a6ed9
3f7fdf2
74ff612
2cf65e0
2cda250
5cb8802
5979e37
f3c18c6
0519aa5
0c503b9
86fde2f
dc8b9fe
2e7eeb4
55ae4e7
d1ce394
ace38a1
7f2b7af
35d472e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -519,30 +519,32 @@ public synchronized <K, V> Topology addSink(final String name, | |
|
|
||
| /** | ||
| * Add a new sink that forwards records from upstream parent processor and/or source nodes to Kafka topics dynamically. | ||
| * The topics that it may ever send to should be pre-created. | ||
| * The sink will use the {@link StreamsConfig#DEFAULT_KEY_SERDE_CLASS_CONFIG default key serializer} and | ||
| * {@link StreamsConfig#DEFAULT_VALUE_SERDE_CLASS_CONFIG default value serializer} specified in the | ||
| * {@link StreamsConfig stream configuration}. | ||
| * | ||
| * @param name the unique name of the sink | ||
| * @param topicExtractor the mapper to dynamically choose the name of the Kafka topic to which this sink should write per reach record | ||
| * @param topicExtractor the extractor to determine the name of the Kafka topic to which this sink should write for reach record | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo |
||
| * @param parentNames the name of one or more source or processor nodes whose output records this sink should consume | ||
| * and write to its topic | ||
| * and dynamically write to topics | ||
| * @return itself | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is equal to the parent's name | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is not unique | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we throw for all three cases.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so. If
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sense, will add it back. |
||
| * @see #addSink(String, String, StreamPartitioner, String...) | ||
| * @see #addSink(String, String, Serializer, Serializer, String...) | ||
| * @see #addSink(String, String, Serializer, Serializer, StreamPartitioner, String...) | ||
| */ | ||
| public synchronized <K, V> Topology addSink(final String name, | ||
| final TopicNameExtractor<K, V> topicExtractor, | ||
| final String... parentNames) { | ||
| final TopicNameExtractor<K, V> topicExtractor, | ||
| final String... parentNames) { | ||
| internalTopologyBuilder.addSink(name, topicExtractor, null, null, null, parentNames); | ||
| return this; | ||
| } | ||
|
|
||
| /** | ||
| * Add a new sink that forwards records from upstream parent processor and/or source nodes to Kafka topics dynamically, | ||
| * Add a new sink that forwards records from upstream parent processor and/or source nodes to Kafka topics dynamically, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above. |
||
| * using the supplied partitioner. | ||
| * The topics that it may ever send to should be pre-created. | ||
| * The sink will use the {@link StreamsConfig#DEFAULT_KEY_SERDE_CLASS_CONFIG default key serializer} and | ||
| * {@link StreamsConfig#DEFAULT_VALUE_SERDE_CLASS_CONFIG default value serializer} specified in the | ||
| * {@link StreamsConfig stream configuration}. | ||
|
|
@@ -555,12 +557,12 @@ public synchronized <K, V> Topology addSink(final String name, | |
| * records among partitions using Kafka's default partitioning logic. | ||
| * | ||
| * @param name the unique name of the sink | ||
| * @param topicExtractor the mapper to dynamically choose the name of the Kafka topic to which this sink should write per reach record | ||
| * @param topicExtractor the extractor to determine the name of the Kafka topic to which this sink should write for reach record | ||
| * @param partitioner the function that should be used to determine the partition for each record processed by the sink | ||
| * @param parentNames the name of one or more source or processor nodes whose output records this sink should consume | ||
| * and write to its topic | ||
| * and dynamically write to topics | ||
| * @return itself | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is equal to the parent's name | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is not unique | ||
| * @see #addSink(String, String, String...) | ||
| * @see #addSink(String, String, Serializer, Serializer, String...) | ||
| * @see #addSink(String, String, Serializer, Serializer, StreamPartitioner, String...) | ||
|
|
@@ -575,20 +577,21 @@ public synchronized <K, V> Topology addSink(final String name, | |
|
|
||
| /** | ||
| * Add a new sink that forwards records from upstream parent processor and/or source nodes to Kafka topics dynamically. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. as above |
||
| * The topics that it may ever send to should be pre-created. | ||
| * The sink will use the specified key and value serializers. | ||
| * | ||
| * @param name the unique name of the sink | ||
| * @param topicExtractor the mapper to dynamically choose the name of the Kafka topic to which this sink should write per reach record | ||
| * @param topicExtractor the extractor to determine the name of the Kafka topic to which this sink should write for reach record | ||
| * @param keySerializer the {@link Serializer key serializer} used when consuming records; may be null if the sink | ||
| * should use the {@link StreamsConfig#DEFAULT_KEY_SERDE_CLASS_CONFIG default key serializer} specified in the | ||
| * {@link StreamsConfig stream configuration} | ||
| * @param valueSerializer the {@link Serializer value serializer} used when consuming records; may be null if the sink | ||
| * should use the {@link StreamsConfig#DEFAULT_VALUE_SERDE_CLASS_CONFIG default value serializer} specified in the | ||
| * {@link StreamsConfig stream configuration} | ||
| * @param parentNames the name of one or more source or processor nodes whose output records this sink should consume | ||
| * and write to its topic | ||
| * and dynamically write to topics | ||
| * @return itself | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is equal to the parent's name | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is not unique | ||
| * @see #addSink(String, String, String...) | ||
| * @see #addSink(String, String, StreamPartitioner, String...) | ||
| * @see #addSink(String, String, Serializer, Serializer, StreamPartitioner, String...) | ||
|
|
@@ -604,10 +607,11 @@ public synchronized <K, V> Topology addSink(final String name, | |
|
|
||
| /** | ||
| * Add a new sink that forwards records from upstream parent processor and/or source nodes to Kafka topics dynamically. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
| * The topics that it may ever send to should be pre-created. | ||
| * The sink will use the specified key and value serializers, and the supplied partitioner. | ||
| * | ||
| * @param name the unique name of the sink | ||
| * @param topicExtractor the mapper to dynamically choose the name of the Kafka topic to which this sink should write per reach record | ||
| * @param topicExtractor the extractor to determine the name of the Kafka topic to which this sink should write for reach record | ||
| * @param keySerializer the {@link Serializer key serializer} used when consuming records; may be null if the sink | ||
| * should use the {@link StreamsConfig#DEFAULT_KEY_SERDE_CLASS_CONFIG default key serializer} specified in the | ||
| * {@link StreamsConfig stream configuration} | ||
|
|
@@ -616,9 +620,9 @@ public synchronized <K, V> Topology addSink(final String name, | |
| * {@link StreamsConfig stream configuration} | ||
| * @param partitioner the function that should be used to determine the partition for each record processed by the sink | ||
| * @param parentNames the name of one or more source or processor nodes whose output records this sink should consume | ||
| * and write to its topic | ||
| * and dynamically write to topics | ||
| * @return itself | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is equal to the parent's name | ||
| * @throws TopologyException if parent processor is not added yet, or if this processor's name is not unique | ||
| * @see #addSink(String, String, String...) | ||
| * @see #addSink(String, String, StreamPartitioner, String...) | ||
| * @see #addSink(String, String, Serializer, Serializer, String...) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -471,18 +471,17 @@ void to(final String topic, | |
| /** | ||
| * Dynamically materialize this stream to topics using default serializers specified in the config and producer's | ||
| * {@link DefaultPartitioner}. | ||
| * The topic names for each record to send to is dynamically determined based on the {@link KeyValueMapper}. | ||
| * The topic names for each record to send to is dynamically determined based on the {@link TopicNameExtractor}. | ||
| * | ||
| * @param topicExtractor the dynamic topic chooser | ||
| * @param topicExtractor the extractor to determine the name of the Kafka topic to write to for reach record | ||
| */ | ||
| void to(final TopicNameExtractor<K, V> topicExtractor); | ||
|
|
||
| /** | ||
| * Dynamically materialize this stream to topics using default serializers specified in the config and producer's | ||
| * {@link DefaultPartitioner}. | ||
| * The topic names for each record to send to is dynamically determined based on the {@link KeyValueMapper}. | ||
| * Dynamically materialize this stream to topics using default serializers specified in the config. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one doesn't use the default serializers, but instead uses |
||
| * The topic names for each record to send to is dynamically determined based on the {@link TopicNameExtractor}. | ||
| * | ||
| * @param topicExtractor the dynamic topic chooser | ||
| * @param topicExtractor the extractor to determine the name of the Kafka topic to write to for reach record | ||
| * @param produced the options to use when producing to the topic | ||
| */ | ||
| void to(final TopicNameExtractor<K, V> topicExtractor, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,20 +20,19 @@ | |
| import org.apache.kafka.streams.processor.internals.RecordContext; | ||
|
|
||
| /** | ||
| * An interface that allows to dynamically determine the topic name to send to at the sink node of the topology. | ||
| * An interface that allows to dynamically determine the name of the Kafka topic to send at the sink node of the topology. | ||
| */ | ||
| @InterfaceStability.Evolving | ||
| public interface TopicNameExtractor<K, V> { | ||
|
|
||
| /** | ||
| * Extracts the topic name to send to. The topic name must be pre-existed, since the Kafka Streams library will not | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: |
||
| * try to automatically create the topic with the extracted name, and will fail with a timeout exception if the topic | ||
| * does not exist in the Kafka cluster. | ||
| * try to automatically create the topic with the extracted name. | ||
| * | ||
| * @param key the record key | ||
| * @param value the record value payload | ||
| * @param value the record value | ||
| * @param recordContext current context metadata of the record | ||
| * @return the topic name to send to | ||
| * @return the topic name this record should be sent to | ||
| */ | ||
| String extract(K key, V value, RecordContext recordContext); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -232,11 +232,11 @@ public void shouldSendDataToDynamicTopics() { | |
| stream.to(new TopicNameExtractor<String, String>() { | ||
| @Override | ||
| public String extract(String key, String value, RecordContext recordContext) { | ||
| return recordContext.topic() + "-" + key + "-topic"; | ||
| return recordContext.topic() + "-" + key + "-" + value.substring(0, 1); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we're in Java8 now... I think you can do:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will that make this code easier or harder to read... who can say? |
||
| } | ||
| }, Produced.with(Serdes.String(), Serdes.String())); | ||
| builder.stream(input + "-a-topic", stringConsumed).process(processorSupplier); | ||
| builder.stream(input + "-b-topic", stringConsumed).process(processorSupplier); | ||
| builder.stream(input + "-a-v", stringConsumed).process(processorSupplier); | ||
| builder.stream(input + "-b-v", stringConsumed).process(processorSupplier); | ||
|
|
||
| try (final TopologyTestDriver driver = new TopologyTestDriver(builder.build(), props)) { | ||
| driver.pipeInput(recordFactory.create(input, "a", "v1")); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -283,7 +283,7 @@ class KStream[K, V](val inner: KStreamJ[K, V]) { | |
| * // to the through call | ||
| * }}} | ||
| * | ||
| * @param extractor the mapper from key value to topic name | ||
| * @param extractor the extractor to determine the name of the Kafka topic to write to for reach record | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| * @param (implicit) produced the instance of Produced that gives the serdes and `StreamPartitioner` | ||
| * @see `org.apache.kafka.streams.kstream.KStream#to` | ||
| */ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO, "dynamically" need a little bit more explanation. Should we also state, that all those topics must be created by the user manually?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with "dynamically"... maybe "based on the {@code topicExtractor}"?
About the second point, I think that's addressed in the following line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to "based on the {@code topicExtractor}"