Sync docs from v2.1.0.RC2 to gh-pages
This commit is contained in:
@@ -0,0 +1,360 @@
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>1. Apache Kafka Binder</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="up" href="multi__reference_guide.html" title="Part I. Reference Guide"><link rel="prev" href="multi__reference_guide.html" title="Part I. Reference Guide"><link rel="next" href="multi__kafka_streams_binder.html" title="2. Kafka Streams Binder"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">1. Apache Kafka Binder</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__reference_guide.html">Prev</a> </td><th width="60%" align="center">Part I. Reference Guide</th><td width="20%" align="right"> <a accesskey="n" href="multi__kafka_streams_binder.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h2 class="title"><a name="_apache_kafka_binder" href="#_apache_kafka_binder"></a>1. Apache Kafka Binder</h2></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_usage" href="#_usage"></a>1.1 Usage</h2></div></div></div><p>To use Apache Kafka binder, you need to add <code class="literal">spring-cloud-stream-binder-kafka</code> as a dependency to your Spring Cloud Stream application, as shown in the following example for Maven:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-stream-binder-kafka<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre><p>Alternatively, you can also use the Spring Cloud Stream Kafka Starter, as shown inn the following example for Maven:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-starter-stream-kafka<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_overview" href="#_overview"></a>1.2 Overview</h2></div></div></div><p>The following image shows a simplified diagram of how the Apache Kafka binder operates:</p><div class="figure"><a name="d0e45" href="#d0e45"></a><p class="title"><b>Figure 1.1. Kafka Binder</b></p><div class="figure-contents"><div class="mediaobject"><img src="http://raw.github.com/spring-cloud/spring-cloud-stream-binder-kafka/master/docs/src/main/asciidoc/images/kafka-binder.png" alt="kafka binder"></div></div></div><br class="figure-break"><p>The Apache Kafka Binder implementation maps each destination to an Apache Kafka topic.
|
||||
The consumer group maps directly to the same Apache Kafka concept.
|
||||
Partitioning also maps directly to Apache Kafka partitions as well.</p><p>The binder currently uses the Apache Kafka <code class="literal">kafka-clients</code> 1.0.0 jar and is designed to be used with a broker of at least that version.
|
||||
This client can communicate with older brokers (see the Kafka documentation), but certain features may not be available.
|
||||
For example, with versions earlier than 0.11.x.x, native headers are not supported.
|
||||
Also, 0.11.x.x does not support the <code class="literal">autoAddPartitions</code> property.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_configuration_options" href="#_configuration_options"></a>1.3 Configuration Options</h2></div></div></div><p>This section contains the configuration options used by the Apache Kafka binder.</p><p>For common configuration options and properties pertaining to binder, see the <a class="link" href="">core documentation</a>.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_kafka_binder_properties" href="#_kafka_binder_properties"></a>1.3.1 Kafka Binder Properties</h3></div></div></div><div class="variablelist"><dl class="variablelist"><dt><span class="term">spring.cloud.stream.kafka.binder.brokers</span></dt><dd><p class="simpara">A list of brokers to which the Kafka binder connects.</p><p class="simpara">Default: <code class="literal">localhost</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.defaultBrokerPort</span></dt><dd><p class="simpara"><code class="literal">brokers</code> allows hosts specified with or without port information (for example, <code class="literal">host1,host2:port2</code>).
|
||||
This sets the default port when no port is configured in the broker list.</p><p class="simpara">Default: <code class="literal">9092</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.configuration</span></dt><dd><p class="simpara">Key/Value map of client properties (both producers and consumer) passed to all clients created by the binder.
|
||||
Due to the fact that these properties are used by both producers and consumers, usage should be restricted to common properties — for example, security settings.
|
||||
Unknown Kafka producer or consumer properties provided through this configuration are filtered out and not allowed to propagate.
|
||||
Properties here supersede any properties set in boot.</p><p class="simpara">Default: Empty map.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.consumerProperties</span></dt><dd><p class="simpara">Key/Value map of arbitrary Kafka client consumer properties.
|
||||
In addition to support known Kafka consumer properties, unknown consumer properties are allowed here as well.
|
||||
Properties here supersede any properties set in boot and in the <code class="literal">configuration</code> property above.</p><p class="simpara">Default: Empty map.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.headers</span></dt><dd><p class="simpara">The list of custom headers that are transported by the binder.
|
||||
Only required when communicating with older applications (⇐ 1.3.x) with a <code class="literal">kafka-clients</code> version < 0.11.0.0. Newer versions support headers natively.</p><p class="simpara">Default: empty.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.healthTimeout</span></dt><dd><p class="simpara">The time to wait to get partition information, in seconds.
|
||||
Health reports as down if this timer expires.</p><p class="simpara">Default: 10.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.requiredAcks</span></dt><dd><p class="simpara">The number of required acks on the broker.
|
||||
See the Kafka documentation for the producer <code class="literal">acks</code> property.</p><p class="simpara">Default: <code class="literal">1</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.minPartitionCount</span></dt><dd><p class="simpara">Effective only if <code class="literal">autoCreateTopics</code> or <code class="literal">autoAddPartitions</code> is set.
|
||||
The global minimum number of partitions that the binder configures on topics on which it produces or consumes data.
|
||||
It can be superseded by the <code class="literal">partitionCount</code> setting of the producer or by the value of <code class="literal">instanceCount * concurrency</code> settings of the producer (if either is larger).</p><p class="simpara">Default: <code class="literal">1</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.producerProperties</span></dt><dd><p class="simpara">Key/Value map of arbitrary Kafka client producer properties.
|
||||
In addition to support known Kafka producer properties, unknown producer properties are allowed here as well.
|
||||
Properties here supersede any properties set in boot and in the <code class="literal">configuration</code> property above.</p><p class="simpara">Default: Empty map.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.replicationFactor</span></dt><dd><p class="simpara">The replication factor of auto-created topics if <code class="literal">autoCreateTopics</code> is active.
|
||||
Can be overridden on each binding.</p><p class="simpara">Default: <code class="literal">1</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.autoCreateTopics</span></dt><dd><p class="simpara">If set to <code class="literal">true</code>, the binder creates new topics automatically.
|
||||
If set to <code class="literal">false</code>, the binder relies on the topics being already configured.
|
||||
In the latter case, if the topics do not exist, the binder fails to start.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>This setting is independent of the <code class="literal">auto.topic.create.enable</code> setting of the broker and does not influence it.
|
||||
If the server is set to auto-create topics, they may be created as part of the metadata retrieval request, with default broker settings.</p></td></tr></table></div><p class="simpara">Default: <code class="literal">true</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.autoAddPartitions</span></dt><dd><p class="simpara">If set to <code class="literal">true</code>, the binder creates new partitions if required.
|
||||
If set to <code class="literal">false</code>, the binder relies on the partition size of the topic being already configured.
|
||||
If the partition count of the target topic is smaller than the expected value, the binder fails to start.</p><p class="simpara">Default: <code class="literal">false</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.transaction.transactionIdPrefix</span></dt><dd><p class="simpara">Enables transactions in the binder. See <code class="literal">transaction.id</code> in the Kafka documentation and <a class="link" href="https://docs.spring.io/spring-kafka/reference/html/_reference.html#transactions" target="_top">Transactions</a> in the <code class="literal">spring-kafka</code> documentation.
|
||||
When transactions are enabled, individual <code class="literal">producer</code> properties are ignored and all producers use the <code class="literal">spring.cloud.stream.kafka.binder.transaction.producer.*</code> properties.</p><p class="simpara">Default <code class="literal">null</code> (no transactions)</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.transaction.producer.*</span></dt><dd><p class="simpara">Global producer properties for producers in a transactional binder.
|
||||
See <code class="literal">spring.cloud.stream.kafka.binder.transaction.transactionIdPrefix</code> and <a class="xref" href="multi__apache_kafka_binder.html#kafka-producer-properties" title="1.3.3 Kafka Producer Properties">Section 1.3.3, “Kafka Producer Properties”</a> and the general producer properties supported by all binders.</p><p class="simpara">Default: See individual producer properties.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.headerMapperBeanName</span></dt><dd><p class="simpara">The bean name of a <code class="literal">KafkaHeaderMapper</code> used for mapping <code class="literal">spring-messaging</code> headers to and from Kafka headers.
|
||||
Use this, for example, if you wish to customize the trusted packages in a <code class="literal">DefaultKafkaHeaderMapper</code> that uses JSON deserialization for the headers.</p><p class="simpara">Default: none.</p></dd></dl></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="kafka-consumer-properties" href="#kafka-consumer-properties"></a>1.3.2 Kafka Consumer Properties</h3></div></div></div><p>The following properties are available for Kafka consumers only and
|
||||
must be prefixed with <code class="literal">spring.cloud.stream.kafka.bindings.<channelName>.consumer.</code>.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">admin.configuration</span></dt><dd><p class="simpara">A <code class="literal">Map</code> of Kafka topic properties used when provisioning topics — for example, <code class="literal">spring.cloud.stream.kafka.bindings.input.consumer.admin.configuration.message.format.version=0.9.0.0</code></p><p class="simpara">Default: none.</p></dd><dt><span class="term">admin.replicas-assignment</span></dt><dd><p class="simpara">A Map<Integer, List<Integer>> of replica assignments, with the key being the partition and the value being the assignments.
|
||||
Used when provisioning new topics.
|
||||
See the <code class="literal">NewTopic</code> Javadocs in the <code class="literal">kafka-clients</code> jar.</p><p class="simpara">Default: none.</p></dd><dt><span class="term">admin.replication-factor</span></dt><dd><p class="simpara">The replication factor to use when provisioning topics. Overrides the binder-wide setting.
|
||||
Ignored if <code class="literal">replicas-assignments</code> is present.</p><p class="simpara">Default: none (the binder-wide default of 1 is used).</p></dd><dt><span class="term">autoRebalanceEnabled</span></dt><dd><p class="simpara">When <code class="literal">true</code>, topic partitions is automatically rebalanced between the members of a consumer group.
|
||||
When <code class="literal">false</code>, each consumer is assigned a fixed set of partitions based on <code class="literal">spring.cloud.stream.instanceCount</code> and <code class="literal">spring.cloud.stream.instanceIndex</code>.
|
||||
This requires both the <code class="literal">spring.cloud.stream.instanceCount</code> and <code class="literal">spring.cloud.stream.instanceIndex</code> properties to be set appropriately on each launched instance.
|
||||
The value of the <code class="literal">spring.cloud.stream.instanceCount</code> property must typically be greater than 1 in this case.</p><p class="simpara">Default: <code class="literal">true</code>.</p></dd><dt><span class="term">ackEachRecord</span></dt><dd><p class="simpara">When <code class="literal">autoCommitOffset</code> is <code class="literal">true</code>, this setting dictates whether to commit the offset after each record is processed.
|
||||
By default, offsets are committed after all records in the batch of records returned by <code class="literal">consumer.poll()</code> have been processed.
|
||||
The number of records returned by a poll can be controlled with the <code class="literal">max.poll.records</code> Kafka property, which is set through the consumer <code class="literal">configuration</code> property.
|
||||
Setting this to <code class="literal">true</code> may cause a degradation in performance, but doing so reduces the likelihood of redelivered records when a failure occurs.
|
||||
Also, see the binder <code class="literal">requiredAcks</code> property, which also affects the performance of committing offsets.</p><p class="simpara">Default: <code class="literal">false</code>.</p></dd><dt><span class="term">autoCommitOffset</span></dt><dd><p class="simpara">Whether to autocommit offsets when a message has been processed.
|
||||
If set to <code class="literal">false</code>, a header with the key <code class="literal">kafka_acknowledgment</code> of the type <code class="literal">org.springframework.kafka.support.Acknowledgment</code> header is present in the inbound message.
|
||||
Applications may use this header for acknowledging messages.
|
||||
See the examples section for details.
|
||||
When this property is set to <code class="literal">false</code>, Kafka binder sets the ack mode to <code class="literal">org.springframework.kafka.listener.AbstractMessageListenerContainer.AckMode.MANUAL</code> and the application is responsible for acknowledging records.
|
||||
Also see <code class="literal">ackEachRecord</code>.</p><p class="simpara">Default: <code class="literal">true</code>.</p></dd><dt><span class="term">autoCommitOnError</span></dt><dd><p class="simpara">Effective only if <code class="literal">autoCommitOffset</code> is set to <code class="literal">true</code>.
|
||||
If set to <code class="literal">false</code>, it suppresses auto-commits for messages that result in errors and commits only for successful messages. It allows a stream to automatically replay from the last successfully processed message, in case of persistent failures.
|
||||
If set to <code class="literal">true</code>, it always auto-commits (if auto-commit is enabled).
|
||||
If not set (the default), it effectively has the same value as <code class="literal">enableDlq</code>, auto-committing erroneous messages if they are sent to a DLQ and not committing them otherwise.</p><p class="simpara">Default: not set.</p></dd><dt><span class="term">resetOffsets</span></dt><dd><p class="simpara">Whether to reset offsets on the consumer to the value provided by startOffset.
|
||||
Must be false if a <code class="literal">KafkaRebalanceListener</code> is provided; see <a class="xref" href="multi__apache_kafka_binder.html#rebalance-listener" title="1.7 Using a KafkaRebalanceListener">Section 1.7, “Using a KafkaRebalanceListener”</a>.</p><p class="simpara">Default: <code class="literal">false</code>.</p></dd><dt><span class="term">startOffset</span></dt><dd><p class="simpara">The starting offset for new groups.
|
||||
Allowed values: <code class="literal">earliest</code> and <code class="literal">latest</code>.
|
||||
If the consumer group is set explicitly for the consumer 'binding' (through <code class="literal">spring.cloud.stream.bindings.<channelName>.group</code>), 'startOffset' is set to <code class="literal">earliest</code>. Otherwise, it is set to <code class="literal">latest</code> for the <code class="literal">anonymous</code> consumer group.
|
||||
Also see <code class="literal">resetOffsets</code> (earlier in this list).</p><p class="simpara">Default: null (equivalent to <code class="literal">earliest</code>).</p></dd><dt><span class="term">enableDlq</span></dt><dd><p class="simpara">When set to true, it enables DLQ behavior for the consumer.
|
||||
By default, messages that result in errors are forwarded to a topic named <code class="literal">error.<destination>.<group></code>.
|
||||
The DLQ topic name can be configurable by setting the <code class="literal">dlqName</code> property.
|
||||
This provides an alternative option to the more common Kafka replay scenario for the case when the number of errors is relatively small and replaying the entire original topic may be too cumbersome.
|
||||
See <a class="xref" href="multi__apache_kafka_binder.html#kafka-dlq-processing" title="1.8 Dead-Letter Topic Processing">Section 1.8, “Dead-Letter Topic Processing”</a> processing for more information.
|
||||
Starting with version 2.0, messages sent to the DLQ topic are enhanced with the following headers: <code class="literal">x-original-topic</code>, <code class="literal">x-exception-message</code>, and <code class="literal">x-exception-stacktrace</code> as <code class="literal">byte[]</code>.
|
||||
<span class="strong"><strong>Not allowed when <code class="literal">destinationIsPattern</code> is <code class="literal">true</code>.</strong></span></p><p class="simpara">Default: <code class="literal">false</code>.</p></dd><dt><span class="term">configuration</span></dt><dd><p class="simpara">Map with a key/value pair containing generic Kafka consumer properties.
|
||||
In addition to having Kafka consumer properties, other configuration properties can be passed here.
|
||||
For example some properties needed by the application such as <code class="literal">spring.cloud.stream.kafka.bindings.input.consumer.configuration.foo=bar</code>.</p><p class="simpara">Default: Empty map.</p></dd><dt><span class="term">dlqName</span></dt><dd><p class="simpara">The name of the DLQ topic to receive the error messages.</p><p class="simpara">Default: null (If not specified, messages that result in errors are forwarded to a topic named <code class="literal">error.<destination>.<group></code>).</p></dd><dt><span class="term">dlqProducerProperties</span></dt><dd><p class="simpara">Using this, DLQ-specific producer properties can be set.
|
||||
All the properties available through kafka producer properties can be set through this property.</p><p class="simpara">Default: Default Kafka producer properties.</p></dd><dt><span class="term">standardHeaders</span></dt><dd><p class="simpara">Indicates which standard headers are populated by the inbound channel adapter.
|
||||
Allowed values: <code class="literal">none</code>, <code class="literal">id</code>, <code class="literal">timestamp</code>, or <code class="literal">both</code>.
|
||||
Useful if using native deserialization and the first component to receive a message needs an <code class="literal">id</code> (such as an aggregator that is configured to use a JDBC message store).</p><p class="simpara">Default: <code class="literal">none</code></p></dd><dt><span class="term">converterBeanName</span></dt><dd><p class="simpara">The name of a bean that implements <code class="literal">RecordMessageConverter</code>. Used in the inbound channel adapter to replace the default <code class="literal">MessagingMessageConverter</code>.</p><p class="simpara">Default: <code class="literal">null</code></p></dd><dt><span class="term">idleEventInterval</span></dt><dd><p class="simpara">The interval, in milliseconds, between events indicating that no messages have recently been received.
|
||||
Use an <code class="literal">ApplicationListener<ListenerContainerIdleEvent></code> to receive these events.
|
||||
See <a class="xref" href="multi__apache_kafka_binder.html#pause-resume" title="Example: Pausing and Resuming the Consumer">the section called “Example: Pausing and Resuming the Consumer”</a> for a usage example.</p><p class="simpara">Default: <code class="literal">30000</code></p></dd><dt><span class="term">destinationIsPattern</span></dt><dd><p class="simpara">When true, the destination is treated as a regular expression <code class="literal">Pattern</code> used to match topic names by the broker.
|
||||
When true, topics are not provisioned, and <code class="literal">enableDlq</code> is not allowed, because the binder does not know the topic names during the provisioning phase.
|
||||
Note, the time taken to detect new topics that match the pattern is controlled by the consumer property <code class="literal">metadata.max.age.ms</code>, which (at the time of writing) defaults to 300,000ms (5 minutes).
|
||||
This can be configured using the <code class="literal">configuration</code> property above.</p><p class="simpara">Default: <code class="literal">false</code></p></dd></dl></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="kafka-producer-properties" href="#kafka-producer-properties"></a>1.3.3 Kafka Producer Properties</h3></div></div></div><p>The following properties are available for Kafka producers only and
|
||||
must be prefixed with <code class="literal">spring.cloud.stream.kafka.bindings.<channelName>.producer.</code>.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">admin.configuration</span></dt><dd><p class="simpara">A <code class="literal">Map</code> of Kafka topic properties used when provisioning new topics — for example, <code class="literal">spring.cloud.stream.kafka.bindings.input.consumer.admin.configuration.message.format.version=0.9.0.0</code></p><p class="simpara">Default: none.</p></dd><dt><span class="term">admin.replicas-assignment</span></dt><dd><p class="simpara">A Map<Integer, List<Integer>> of replica assignments, with the key being the partition and the value being the assignments.
|
||||
Used when provisioning new topics.
|
||||
See <code class="literal">NewTopic</code> javadocs in the <code class="literal">kafka-clients</code> jar.</p><p class="simpara">Default: none.</p></dd><dt><span class="term">admin.replication-factor</span></dt><dd><p class="simpara">The replication factor to use when provisioning new topics. Overrides the binder-wide setting.
|
||||
Ignored if <code class="literal">replicas-assignments</code> is present.</p><p class="simpara">Default: none (the binder-wide default of 1 is used).</p></dd><dt><span class="term">bufferSize</span></dt><dd><p class="simpara">Upper limit, in bytes, of how much data the Kafka producer attempts to batch before sending.</p><p class="simpara">Default: <code class="literal">16384</code>.</p></dd><dt><span class="term">sync</span></dt><dd><p class="simpara">Whether the producer is synchronous.</p><p class="simpara">Default: <code class="literal">false</code>.</p></dd><dt><span class="term">batchTimeout</span></dt><dd><p class="simpara">How long the producer waits to allow more messages to accumulate in the same batch before sending the messages.
|
||||
(Normally, the producer does not wait at all and simply sends all the messages that accumulated while the previous send was in progress.) A non-zero value may increase throughput at the expense of latency.</p><p class="simpara">Default: <code class="literal">0</code>.</p></dd><dt><span class="term">messageKeyExpression</span></dt><dd><p class="simpara">A SpEL expression evaluated against the outgoing message used to populate the key of the produced Kafka message — for example, <code class="literal">headers['myKey']</code>.
|
||||
The payload cannot be used because, by the time this expression is evaluated, the payload is already in the form of a <code class="literal">byte[]</code>.</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">headerPatterns</span></dt><dd><p class="simpara">A comma-delimited list of simple patterns to match Spring messaging headers to be mapped to the Kafka <code class="literal">Headers</code> in the <code class="literal">ProducerRecord</code>.
|
||||
Patterns can begin or end with the wildcard character (asterisk).
|
||||
Patterns can be negated by prefixing with <code class="literal">!</code>.
|
||||
Matching stops after the first match (positive or negative).
|
||||
For example <code class="literal">!ask,as*</code> will pass <code class="literal">ash</code> but not <code class="literal">ask</code>.
|
||||
<code class="literal">id</code> and <code class="literal">timestamp</code> are never mapped.</p><p class="simpara">Default: <code class="literal">*</code> (all headers - except the <code class="literal">id</code> and <code class="literal">timestamp</code>)</p></dd><dt><span class="term">configuration</span></dt><dd><p class="simpara">Map with a key/value pair containing generic Kafka producer properties.</p><p class="simpara">Default: Empty map.</p></dd></dl></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>The Kafka binder uses the <code class="literal">partitionCount</code> setting of the producer as a hint to create a topic with the given partition count (in conjunction with the <code class="literal">minPartitionCount</code>, the maximum of the two being the value being used).
|
||||
Exercise caution when configuring both <code class="literal">minPartitionCount</code> for a binder and <code class="literal">partitionCount</code> for an application, as the larger value is used.
|
||||
If a topic already exists with a smaller partition count and <code class="literal">autoAddPartitions</code> is disabled (the default), the binder fails to start.
|
||||
If a topic already exists with a smaller partition count and <code class="literal">autoAddPartitions</code> is enabled, new partitions are added.
|
||||
If a topic already exists with a larger number of partitions than the maximum of (<code class="literal">minPartitionCount</code> or <code class="literal">partitionCount</code>), the existing partition count is used.</p></td></tr></table></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_usage_examples" href="#_usage_examples"></a>1.3.4 Usage examples</h3></div></div></div><p>In this section, we show the use of the preceding properties for specific scenarios.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_example_setting_autocommitoffset_to_false_and_relying_on_manual_acking" href="#_example_setting_autocommitoffset_to_false_and_relying_on_manual_acking"></a>Example: Setting <code class="literal">autoCommitOffset</code> to <code class="literal">false</code> and Relying on Manual Acking</h4></div></div></div><p>This example illustrates how one may manually acknowledge offsets in a consumer application.</p><p>This example requires that <code class="literal">spring.cloud.stream.kafka.bindings.input.consumer.autoCommitOffset</code> be set to <code class="literal">false</code>.
|
||||
Use the corresponding input channel name for your example.</p><pre class="screen">@SpringBootApplication
|
||||
@EnableBinding(Sink.class)
|
||||
public class ManuallyAcknowdledgingConsumer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(ManuallyAcknowdledgingConsumer.class, args);
|
||||
}
|
||||
|
||||
@StreamListener(Sink.INPUT)
|
||||
public void process(Message<?> message) {
|
||||
Acknowledgment acknowledgment = message.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class);
|
||||
if (acknowledgment != null) {
|
||||
System.out.println("Acknowledgment provided");
|
||||
acknowledgment.acknowledge();
|
||||
}
|
||||
}
|
||||
}</pre></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="_example_security_configuration" href="#_example_security_configuration"></a>Example: Security Configuration</h4></div></div></div><p>Apache Kafka 0.9 supports secure connections between client and brokers.
|
||||
To take advantage of this feature, follow the guidelines in the <a class="link" href="http://kafka.apache.org/090/documentation.html#security_configclients" target="_top">Apache Kafka Documentation</a> as well as the Kafka 0.9 <a class="link" href="http://docs.confluent.io/2.0.0/kafka/security.html" target="_top">security guidelines from the Confluent documentation</a>.
|
||||
Use the <code class="literal">spring.cloud.stream.kafka.binder.configuration</code> option to set security properties for all clients created by the binder.</p><p>For example, to set <code class="literal">security.protocol</code> to <code class="literal">SASL_SSL</code>, set the following property:</p><pre class="screen">spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_SSL</pre><p>All the other security properties can be set in a similar manner.</p><p>When using Kerberos, follow the instructions in the <a class="link" href="http://kafka.apache.org/090/documentation.html#security_sasl_clientconfig" target="_top">reference documentation</a> for creating and referencing the JAAS configuration.</p><p>Spring Cloud Stream supports passing JAAS configuration information to the application by using a JAAS configuration file and using Spring Boot properties.</p><div class="section"><div class="titlepage"><div><div><h5 class="title"><a name="_using_jaas_configuration_files" href="#_using_jaas_configuration_files"></a>Using JAAS Configuration Files</h5></div></div></div><p>The JAAS and (optionally) krb5 file locations can be set for Spring Cloud Stream applications by using system properties.
|
||||
The following example shows how to launch a Spring Cloud Stream application with SASL and Kerberos by using a JAAS configuration file:</p><pre class="programlisting"> java -Djava.security.auth.login.config=/path.to/kafka_client_jaas.conf -jar log.jar \
|
||||
--spring.cloud.stream.kafka.binder.brokers=secure.server:<span class="hl-number">9092</span> \
|
||||
--spring.cloud.stream.bindings.input.destination=stream.ticktock \
|
||||
--spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT</pre></div><div class="section"><div class="titlepage"><div><div><h5 class="title"><a name="_using_spring_boot_properties" href="#_using_spring_boot_properties"></a>Using Spring Boot Properties</h5></div></div></div><p>As an alternative to having a JAAS configuration file, Spring Cloud Stream provides a mechanism for setting up the JAAS configuration for Spring Cloud Stream applications by using Spring Boot properties.</p><p>The following properties can be used to configure the login context of the Kafka client:</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">spring.cloud.stream.kafka.binder.jaas.loginModule</span></dt><dd><p class="simpara">The login module name. Not necessary to be set in normal cases.</p><p class="simpara">Default: <code class="literal">com.sun.security.auth.module.Krb5LoginModule</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.jaas.controlFlag</span></dt><dd><p class="simpara">The control flag of the login module.</p><p class="simpara">Default: <code class="literal">required</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.binder.jaas.options</span></dt><dd><p class="simpara">Map with a key/value pair containing the login module options.</p><p class="simpara">Default: Empty map.</p></dd></dl></div><p>The following example shows how to launch a Spring Cloud Stream application with SASL and Kerberos by using Spring Boot configuration properties:</p><pre class="programlisting"> java --spring.cloud.stream.kafka.binder.brokers=secure.server:<span class="hl-number">9092</span> \
|
||||
--spring.cloud.stream.bindings.input.destination=stream.ticktock \
|
||||
--spring.cloud.stream.kafka.binder.autoCreateTopics=false \
|
||||
--spring.cloud.stream.kafka.binder.configuration.security.protocol=SASL_PLAINTEXT \
|
||||
--spring.cloud.stream.kafka.binder.jaas.options.useKeyTab=true \
|
||||
--spring.cloud.stream.kafka.binder.jaas.options.storeKey=true \
|
||||
--spring.cloud.stream.kafka.binder.jaas.options.keyTab=/etc/security/keytabs/kafka_client.keytab \
|
||||
--spring.cloud.stream.kafka.binder.jaas.options.principal=kafka-client-<span class="hl-number">1</span>@EXAMPLE.COM</pre><p>The preceding example represents the equivalent of the following JAAS file:</p><pre class="screen">KafkaClient {
|
||||
com.sun.security.auth.module.Krb5LoginModule required
|
||||
useKeyTab=true
|
||||
storeKey=true
|
||||
keyTab="/etc/security/keytabs/kafka_client.keytab"
|
||||
principal="kafka-client-1@EXAMPLE.COM";
|
||||
};</pre><p>If the topics required already exist on the broker or will be created by an administrator, autocreation can be turned off and only client JAAS properties need to be sent.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Do not mix JAAS configuration files and Spring Boot properties in the same application.
|
||||
If the <code class="literal">-Djava.security.auth.login.config</code> system property is already present, Spring Cloud Stream ignores the Spring Boot properties.</p></td></tr></table></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>Be careful when using the <code class="literal">autoCreateTopics</code> and <code class="literal">autoAddPartitions</code> with Kerberos.
|
||||
Usually, applications may use principals that do not have administrative rights in Kafka and Zookeeper.
|
||||
Consequently, relying on Spring Cloud Stream to create/modify topics may fail.
|
||||
In secure environments, we strongly recommend creating topics and managing ACLs administratively by using Kafka tooling.</p></td></tr></table></div></div></div><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="pause-resume" href="#pause-resume"></a>Example: Pausing and Resuming the Consumer</h4></div></div></div><p>If you wish to suspend consumption but not cause a partition rebalance, you can pause and resume the consumer.
|
||||
This is facilitated by adding the <code class="literal">Consumer</code> as a parameter to your <code class="literal">@StreamListener</code>.
|
||||
To resume, you need an <code class="literal">ApplicationListener</code> for <code class="literal">ListenerContainerIdleEvent</code> instances.
|
||||
The frequency at which events are published is controlled by the <code class="literal">idleEventInterval</code> property.
|
||||
Since the consumer is not thread-safe, you must call these methods on the calling thread.</p><p>The following simple application shows how to pause and resume:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@SpringBootApplication</span></em>
|
||||
<em><span class="hl-annotation" style="color: gray">@EnableBinding(Sink.class)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> Application {
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> main(String[] args) {
|
||||
SpringApplication.run(Application.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, args);
|
||||
}
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@StreamListener(Sink.INPUT)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> in(String in, <em><span class="hl-annotation" style="color: gray">@Header(KafkaHeaders.CONSUMER)</span></em> Consumer<?, ?> consumer) {
|
||||
System.out.println(in);
|
||||
consumer.pause(Collections.singleton(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> TopicPartition(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"myTopic"</span>, <span class="hl-number">0</span>)));
|
||||
}
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@Bean</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> ApplicationListener<ListenerContainerIdleEvent> idleListener() {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> event -> {
|
||||
System.out.println(event);
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">if</span> (event.getConsumer().paused().size() > <span class="hl-number">0</span>) {
|
||||
event.getConsumer().resume(event.getConsumer().paused());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}</pre></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="kafka-error-channels" href="#kafka-error-channels"></a>1.4 Error Channels</h2></div></div></div><p>Starting with version 1.3, the binder unconditionally sends exceptions to an error channel for each consumer destination and can also be configured to send async producer send failures to an error channel.
|
||||
See <a class="xref" href="">???</a> for more information.</p><p>The payload of the <code class="literal">ErrorMessage</code> for a send failure is a <code class="literal">KafkaSendFailureException</code> with properties:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem"><code class="literal">failedMessage</code>: The Spring Messaging <code class="literal">Message<?></code> that failed to be sent.</li><li class="listitem"><code class="literal">record</code>: The raw <code class="literal">ProducerRecord</code> that was created from the <code class="literal">failedMessage</code></li></ul></div><p>There is no automatic handling of producer exceptions (such as sending to a <a class="link" href="multi__apache_kafka_binder.html#kafka-dlq-processing" title="1.8 Dead-Letter Topic Processing">Dead-Letter queue</a>).
|
||||
You can consume these exceptions with your own Spring Integration flow.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="kafka-metrics" href="#kafka-metrics"></a>1.5 Kafka Metrics</h2></div></div></div><p>Kafka binder module exposes the following metrics:</p><p><code class="literal">spring.cloud.stream.binder.kafka.offset</code>: This metric indicates how many messages have not been yet consumed from a given binder’s topic by a given consumer group.
|
||||
The metrics provided are based on the Mircometer metrics library. The metric contains the consumer group information, topic and the actual lag in committed offset from the latest offset on the topic.
|
||||
This metric is particularly useful for providing auto-scaling feedback to a PaaS platform.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="kafka-tombstones" href="#kafka-tombstones"></a>1.6 Tombstone Records (null record values)</h2></div></div></div><p>When using compacted topics, a record with a <code class="literal">null</code> value (also called a tombstone record) represents the deletion of a key.
|
||||
To receive such messages in a <code class="literal">@StreamListener</code> method, the parameter must be marked as not required to receive a <code class="literal">null</code> value argument.</p><div class="informalexample"><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@StreamListener(Sink.INPUT)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> in(<em><span class="hl-annotation" style="color: gray">@Header(KafkaHeaders.RECEIVED_MESSAGE_KEY)</span></em> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">byte</span>[] key,
|
||||
<em><span class="hl-annotation" style="color: gray">@Payload(required = false)</span></em> Customer customer) {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-comment">// customer is null if a tombstone record</span>
|
||||
...
|
||||
}</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="rebalance-listener" href="#rebalance-listener"></a>1.7 Using a KafkaRebalanceListener</h2></div></div></div><p>Applications may wish to seek topics/partitions to arbitrary offsets when the partitions are initially assigned, or perform other operations on the consumer.
|
||||
Starting with version 2.1, if you provide a single <code class="literal">KafkaRebalanceListener</code> bean in the application context, it will be wired into all Kafka consumer bindings.</p><div class="informalexample"><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> KafkaBindingRebalanceListener {
|
||||
|
||||
<strong class="hl-tag" style="color: blue">/**
|
||||
* Invoked by the container before any pending offsets are committed.
|
||||
* @param bindingName the name of the binding.
|
||||
* @param consumer the consumer.
|
||||
* @param partitions the partitions.
|
||||
*/</strong>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">default</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> onPartitionsRevokedBeforeCommit(String bindingName, Consumer<?, ?> consumer,
|
||||
Collection<TopicPartition> partitions) {
|
||||
|
||||
}
|
||||
|
||||
<strong class="hl-tag" style="color: blue">/**
|
||||
* Invoked by the container after any pending offsets are committed.
|
||||
* @param bindingName the name of the binding.
|
||||
* @param consumer the consumer.
|
||||
* @param partitions the partitions.
|
||||
*/</strong>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">default</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> onPartitionsRevokedAfterCommit(String bindingName, Consumer<?, ?> consumer, Collection<TopicPartition> partitions) {
|
||||
|
||||
}
|
||||
|
||||
<strong class="hl-tag" style="color: blue">/**
|
||||
* Invoked when partitions are initially assigned or after a rebalance.
|
||||
* Applications might only want to perform seek operations on an initial assignment.
|
||||
* @param bindingName the name of the binding.
|
||||
* @param consumer the consumer.
|
||||
* @param partitions the partitions.
|
||||
* @param initial true if this is the initial assignment.
|
||||
*/</strong>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">default</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> onPartitionsAssigned(String bindingName, Consumer<?, ?> consumer, Collection<TopicPartition> partitions,
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">boolean</span> initial) {
|
||||
|
||||
}
|
||||
|
||||
}</pre></div><p>You cannot set the <code class="literal">resetOffsets</code> consumer property to <code class="literal">true</code> when you provide a rebalance listener.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="kafka-dlq-processing" href="#kafka-dlq-processing"></a>1.8 Dead-Letter Topic Processing</h2></div></div></div><p>Because you cannot anticipate how users would want to dispose of dead-lettered messages, the framework does not provide any standard mechanism to handle them.
|
||||
If the reason for the dead-lettering is transient, you may wish to route the messages back to the original topic.
|
||||
However, if the problem is a permanent issue, that could cause an infinite loop.
|
||||
The sample Spring Boot application within this topic is an example of how to route those messages back to the original topic, but it moves them to a <span class="quote">“<span class="quote">parking lot</span>”</span> topic after three attempts.
|
||||
The application is another spring-cloud-stream application that reads from the dead-letter topic.
|
||||
It terminates when no messages are received for 5 seconds.</p><p>The examples assume the original destination is <code class="literal">so8400out</code> and the consumer group is <code class="literal">so8400</code>.</p><p>There are a couple of strategies to consider:</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">Consider running the rerouting only when the main application is not running.
|
||||
Otherwise, the retries for transient errors are used up very quickly.</li><li class="listitem">Alternatively, use a two-stage approach: Use this application to route to a third topic and another to route from there back to the main topic.</li></ul></div><p>The following code listings show the sample application:</p><p><b>application.properties. </b>
|
||||
</p><pre class="screen">spring.cloud.stream.bindings.input.group=so8400replay
|
||||
spring.cloud.stream.bindings.input.destination=error.so8400out.so8400
|
||||
|
||||
spring.cloud.stream.bindings.output.destination=so8400out
|
||||
spring.cloud.stream.bindings.output.producer.partitioned=true
|
||||
|
||||
spring.cloud.stream.bindings.parkingLot.destination=so8400in.parkingLot
|
||||
spring.cloud.stream.bindings.parkingLot.producer.partitioned=true
|
||||
|
||||
spring.cloud.stream.kafka.binder.configuration.auto.offset.reset=earliest
|
||||
|
||||
spring.cloud.stream.kafka.binder.headers=x-retries</pre><p>
|
||||
</p><p><b>Application. </b>
|
||||
</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@SpringBootApplication</span></em>
|
||||
<em><span class="hl-annotation" style="color: gray">@EnableBinding(TwoOutputProcessor.class)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> ReRouteDlqKApplication <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">implements</span> CommandLineRunner {
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> String X_RETRIES_HEADER = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"x-retries"</span>;
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> main(String[] args) {
|
||||
SpringApplication.run(ReRouteDlqKApplication.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>, args).close();
|
||||
}
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> AtomicInteger processed = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> AtomicInteger();
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@Autowired</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> MessageChannel parkingLot;
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@StreamListener(Processor.INPUT)</span></em>
|
||||
<em><span class="hl-annotation" style="color: gray">@SendTo(Processor.OUTPUT)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Message<?> reRoute(Message<?> failed) {
|
||||
processed.incrementAndGet();
|
||||
Integer retries = failed.getHeaders().get(X_RETRIES_HEADER, Integer.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>);
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">if</span> (retries == null) {
|
||||
System.out.println(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"First retry for "</span> + failed);
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> MessageBuilder.fromMessage(failed)
|
||||
.setHeader(X_RETRIES_HEADER, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Integer(<span class="hl-number">1</span>))
|
||||
.setHeader(BinderHeaders.PARTITION_OVERRIDE,
|
||||
failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID))
|
||||
.build();
|
||||
}
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">else</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">if</span> (retries.intValue() < <span class="hl-number">3</span>) {
|
||||
System.out.println(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Another retry for "</span> + failed);
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> MessageBuilder.fromMessage(failed)
|
||||
.setHeader(X_RETRIES_HEADER, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Integer(retries.intValue() + <span class="hl-number">1</span>))
|
||||
.setHeader(BinderHeaders.PARTITION_OVERRIDE,
|
||||
failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID))
|
||||
.build();
|
||||
}
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">else</span> {
|
||||
System.out.println(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Retries exhausted for "</span> + failed);
|
||||
parkingLot.send(MessageBuilder.fromMessage(failed)
|
||||
.setHeader(BinderHeaders.PARTITION_OVERRIDE,
|
||||
failed.getHeaders().get(KafkaHeaders.RECEIVED_PARTITION_ID))
|
||||
.build());
|
||||
}
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> null;
|
||||
}
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@Override</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> run(String... args) <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">throws</span> Exception {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">while</span> (true) {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> count = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.processed.get();
|
||||
Thread.sleep(<span class="hl-number">5000</span>);
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">if</span> (count == <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">this</span>.processed.get()) {
|
||||
System.out.println(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Idle, terminating"</span>);
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">interface</span> TwoOutputProcessor <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">extends</span> Processor {
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@Output("parkingLot")</span></em>
|
||||
MessageChannel parkingLot();
|
||||
|
||||
}
|
||||
|
||||
}</pre><p>
|
||||
</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_partitioning_with_the_kafka_binder" href="#_partitioning_with_the_kafka_binder"></a>1.9 Partitioning with the Kafka Binder</h2></div></div></div><p>Apache Kafka supports topic partitioning natively.</p><p>Sometimes it is advantageous to send data to specific partitions — for example, when you want to strictly order message processing (all messages for a particular customer should go to the same partition).</p><p>The following example shows how to configure the producer and consumer side:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@SpringBootApplication</span></em>
|
||||
<em><span class="hl-annotation" style="color: gray">@EnableBinding(Source.class)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> KafkaPartitionProducerApplication {
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> Random RANDOM = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> Random(System.currentTimeMillis());
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">private</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">final</span> String[] data = <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> String[] {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo1"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar1"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"qux1"</span>,
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo2"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar2"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"qux2"</span>,
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo3"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar3"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"qux3"</span>,
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"foo4"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"bar4"</span>, <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"qux4"</span>,
|
||||
};
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> main(String[] args) {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> SpringApplicationBuilder(KafkaPartitionProducerApplication.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)
|
||||
.web(false)
|
||||
.run(args);
|
||||
}
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@InboundChannelAdapter(channel = Source.OUTPUT, poller = @Poller(fixedRate = "5000"))</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> Message<?> generate() {
|
||||
String value = data[RANDOM.nextInt(data.length)];
|
||||
System.out.println(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"Sending: "</span> + value);
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">return</span> MessageBuilder.withPayload(value)
|
||||
.setHeader(<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">"partitionKey"</span>, value)
|
||||
.build();
|
||||
}
|
||||
|
||||
}</pre><p><b>application.yml. </b>
|
||||
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">spring</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> cloud</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> stream</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> bindings</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> output</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> destination</span>: partitioned.topic
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> producer</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> partitioned</span>: <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">true</span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> partition-key-expression</span>: headers[<span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">'partitionKey'</span><span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">]</span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> partition-count</span>: <span class="hl-number">12</span></pre><p>
|
||||
</p><div class="important" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Important"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Important]" src="images/important.png"></td><th align="left">Important</th></tr><tr><td align="left" valign="top"><p>The topic must be provisioned to have enough partitions to achieve the desired concurrency for all consumer groups.
|
||||
The above configuration supports up to 12 consumer instances (6 if their <code class="literal">concurrency</code> is 2, 4 if their concurrency is 3, and so on).
|
||||
It is generally best to <span class="quote">“<span class="quote">over-provision</span>”</span> the partitions to allow for future increases in consumers or concurrency.</p></td></tr></table></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><table border="0" summary="Note"><tr><td rowspan="2" align="center" valign="top" width="25"><img alt="[Note]" src="images/note.png"></td><th align="left">Note</th></tr><tr><td align="left" valign="top"><p>The preceding configuration uses the default partitioning (<code class="literal">key.hashCode() % partitionCount</code>).
|
||||
This may or may not provide a suitably balanced algorithm, depending on the key values.
|
||||
You can override this default by using the <code class="literal">partitionSelectorExpression</code> or <code class="literal">partitionSelectorClass</code> properties.</p></td></tr></table></div><p>Since partitions are natively handled by Kafka, no special configuration is needed on the consumer side.
|
||||
Kafka allocates partitions across the instances.</p><p>The following Spring Boot application listens to a Kafka stream and prints (to the console) the partition ID to which each message goes:</p><pre class="programlisting"><em><span class="hl-annotation" style="color: gray">@SpringBootApplication</span></em>
|
||||
<em><span class="hl-annotation" style="color: gray">@EnableBinding(Sink.class)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span> KafkaPartitionConsumerApplication {
|
||||
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">static</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> main(String[] args) {
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">new</span> SpringApplicationBuilder(KafkaPartitionConsumerApplication.<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">class</span>)
|
||||
.web(false)
|
||||
.run(args);
|
||||
}
|
||||
|
||||
<em><span class="hl-annotation" style="color: gray">@StreamListener(Sink.INPUT)</span></em>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">public</span> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">void</span> listen(<em><span class="hl-annotation" style="color: gray">@Payload</span></em> String in, <em><span class="hl-annotation" style="color: gray">@Header(KafkaHeaders.RECEIVED_PARTITION_ID)</span></em> <span xmlns:d="http://docbook.org/ns/docbook" class="hl-keyword">int</span> partition) {
|
||||
System.out.println(in + <span xmlns:d="http://docbook.org/ns/docbook" class="hl-string">" received from partition "</span> + partition);
|
||||
}
|
||||
|
||||
}</pre><p><b>application.yml. </b>
|
||||
</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute">spring</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> cloud</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> stream</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> bindings</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> input</span>:
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> destination</span>: partitioned.topic
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-attribute"> group</span>: myGroup</pre><p>
|
||||
</p><p>You can add instances as needed.
|
||||
Kafka rebalances the partition allocations.
|
||||
If the instance count (or <code class="literal">instance count * concurrency</code>) exceeds the number of partitions, some consumers are idle.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__reference_guide.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="multi__reference_guide.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="multi__kafka_streams_binder.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Part I. Reference Guide </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-stream-binder-kafka.html">Home</a></td><td width="40%" align="right" valign="top"> 2. Kafka Streams Binder</td></tr></table></div></body></html>
|
||||
@@ -1,3 +1,3 @@
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>Part II. Appendices</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="up" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="prev" href="multi__partitioning_with_the_kafka_binder.html" title="9. Partitioning with the Kafka Binder"><link rel="next" href="multi_building.html" title="Appendix A. Building"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Part II. Appendices</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__partitioning_with_the_kafka_binder.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="multi_building.html">Next</a></td></tr></table><hr></div><div class="part"><div class="titlepage"><div><div><h1 class="title"><a name="_appendices" href="#_appendices"></a>Part II. Appendices</h1></div></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__partitioning_with_the_kafka_binder.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="multi_building.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">9. Partitioning with the Kafka Binder </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-stream-binder-kafka.html">Home</a></td><td width="40%" align="right" valign="top"> Appendix A. Building</td></tr></table></div></body></html>
|
||||
<title>Part II. Appendices</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="up" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="prev" href="multi__kafka_streams_binder.html" title="2. Kafka Streams Binder"><link rel="next" href="multi_building.html" title="Appendix A. Building"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Part II. Appendices</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__kafka_streams_binder.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="multi_building.html">Next</a></td></tr></table><hr></div><div class="part"><div class="titlepage"><div><div><h1 class="title"><a name="_appendices" href="#_appendices"></a>Part II. Appendices</h1></div></div></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__kafka_streams_binder.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="multi_building.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">2. Kafka Streams Binder </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-stream-binder-kafka.html">Home</a></td><td width="40%" align="right" valign="top"> Appendix A. Building</td></tr></table></div></body></html>
|
||||
@@ -0,0 +1,277 @@
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>2. Kafka Streams Binder</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="up" href="multi__reference_guide.html" title="Part I. Reference Guide"><link rel="prev" href="multi__apache_kafka_binder.html" title="1. Apache Kafka Binder"><link rel="next" href="multi__appendices.html" title="Part II. Appendices"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">2. Kafka Streams Binder</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi__apache_kafka_binder.html">Prev</a> </td><th width="60%" align="center">Part I. Reference Guide</th><td width="20%" align="right"> <a accesskey="n" href="multi__appendices.html">Next</a></td></tr></table><hr></div><div class="chapter"><div class="titlepage"><div><div><h2 class="title"><a name="_kafka_streams_binder" href="#_kafka_streams_binder"></a>2. Kafka Streams Binder</h2></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_usage_2" href="#_usage_2"></a>2.1 Usage</h2></div></div></div><p>For using the Kafka Streams binder, you just need to add it to your Spring Cloud Stream application, using the following
|
||||
Maven coordinates:</p><pre class="programlisting"><span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><dependency></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><groupId></span>org.springframework.cloud<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></groupId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"><artifactId></span>spring-cloud-stream-binder-kafka-streams<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></artifactId></span>
|
||||
<span xmlns:d="http://docbook.org/ns/docbook" class="hl-tag"></dependency></span></pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_overview_2" href="#_overview_2"></a>2.2 Overview</h2></div></div></div><p>Spring Cloud Stream’s Apache Kafka support also includes a binder implementation designed explicitly for Apache Kafka
|
||||
Streams binding. With this native integration, a Spring Cloud Stream "processor" application can directly use the
|
||||
<a class="link" href="https://kafka.apache.org/documentation/streams/developer-guide" target="_top">Apache Kafka Streams</a> APIs in the core business logic.</p><p>Kafka Streams binder implementation builds on the foundation provided by the <a class="link" href="http://docs.spring.io/spring-kafka/reference/html/_reference.html#kafka-streams" target="_top">Kafka Streams in Spring Kafka</a>
|
||||
project.</p><p>Kafka Streams binder provides binding capabilities for the three major types in Kafka Streams - KStream, KTable and GlobalKTable.</p><p>As part of this native integration, the high-level <a class="link" href="https://docs.confluent.io/current/streams/developer-guide/dsl-api.html" target="_top">Streams DSL</a>
|
||||
provided by the Kafka Streams API is available for use in the business logic.</p><p>An early version of the <a class="link" href="https://docs.confluent.io/current/streams/developer-guide/processor-api.html" target="_top">Processor API</a>
|
||||
support is available as well.</p><p>As noted early-on, Kafka Streams support in Spring Cloud Stream is strictly only available for use in the Processor model.
|
||||
A model in which the messages read from an inbound topic, business processing can be applied, and the transformed messages
|
||||
can be written to an outbound topic. It can also be used in Processor applications with a no-outbound destination.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_streams_dsl" href="#_streams_dsl"></a>2.2.1 Streams DSL</h3></div></div></div><p>This application consumes data from a Kafka topic (e.g., <code class="literal">words</code>), computes word count for each unique word in a 5 seconds
|
||||
time window, and the computed results are sent to a downstream topic (e.g., <code class="literal">counts</code>) for further processing.</p><pre class="screen">@SpringBootApplication
|
||||
@EnableBinding(KStreamProcessor.class)
|
||||
public class WordCountProcessorApplication {
|
||||
|
||||
@StreamListener("input")
|
||||
@SendTo("output")
|
||||
public KStream<?, WordCount> process(KStream<?, String> input) {
|
||||
return input
|
||||
.flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
|
||||
.groupBy((key, value) -> value)
|
||||
.windowedBy(TimeWindows.of(5000))
|
||||
.count(Materialized.as("WordCounts-multi"))
|
||||
.toStream()
|
||||
.map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end()))));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WordCountProcessorApplication.class, args);
|
||||
}</pre><p>Once built as a uber-jar (e.g., <code class="literal">wordcount-processor.jar</code>), you can run the above example like the following.</p><pre class="screen">java -jar wordcount-processor.jar --spring.cloud.stream.bindings.input.destination=words --spring.cloud.stream.bindings.output.destination=counts</pre><p>This application will consume messages from the Kafka topic <code class="literal">words</code> and the computed results are published to an output
|
||||
topic <code class="literal">counts</code>.</p><p>Spring Cloud Stream will ensure that the messages from both the incoming and outgoing topics are automatically bound as
|
||||
KStream objects. As a developer, you can exclusively focus on the business aspects of the code, i.e. writing the logic
|
||||
required in the processor. Setting up the Streams DSL specific configuration required by the Kafka Streams infrastructure
|
||||
is automatically handled by the framework.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_configuration_options_2" href="#_configuration_options_2"></a>2.3 Configuration Options</h2></div></div></div><p>This section contains the configuration options used by the Kafka Streams binder.</p><p>For common configuration options and properties pertaining to binder, refer to the <a class="link" href="">core documentation</a>.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_kafka_streams_properties" href="#_kafka_streams_properties"></a>2.3.1 Kafka Streams Properties</h3></div></div></div><p>The following properties are available at the binder level and must be prefixed with <code class="literal">spring.cloud.stream.kafka.streams.binder.</code></p><div class="variablelist"><dl class="variablelist"><dt><span class="term">configuration</span></dt><dd> Map with a key/value pair containing properties pertaining to Apache Kafka Streams API.
|
||||
This property must be prefixed with <code class="literal">spring.cloud.stream.kafka.streams.binder.</code>.
|
||||
Following are some examples of using this property.</dd></dl></div><pre class="screen">spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde=org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms=1000</pre><p>For more information about all the properties that may go into streams configuration, see StreamsConfig JavaDocs in
|
||||
Apache Kafka Streams docs.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">brokers</span></dt><dd><p class="simpara">Broker URL</p><p class="simpara">Default: <code class="literal">localhost</code></p></dd><dt><span class="term">zkNodes</span></dt><dd><p class="simpara">Zookeeper URL</p><p class="simpara">Default: <code class="literal">localhost</code></p></dd><dt><span class="term">serdeError</span></dt><dd><p class="simpara">Deserialization error handler type.
|
||||
Possible values are - <code class="literal">logAndContinue</code>, <code class="literal">logAndFail</code> or <code class="literal">sendToDlq</code></p><p class="simpara">Default: <code class="literal">logAndFail</code></p></dd><dt><span class="term">applicationId</span></dt><dd><p class="simpara">Convenient way to set the application.id for the Kafka Streams application globally at the binder level.
|
||||
If the application contains multiple <code class="literal">StreamListener</code> methods, then application.id should be set at the binding level per input binding.</p><p class="simpara">Default: <code class="literal">none</code></p></dd></dl></div><p>The following properties are <span class="emphasis"><em>only</em></span> available for Kafka Streams producers and must be prefixed with <code class="literal">spring.cloud.stream.kafka.streams.bindings.<binding name>.producer.</code>
|
||||
For convenience, if there multiple output bindings and they all require a common value, that can be configured by using the prefix <code class="literal">spring.cloud.stream.kafka.streams.default.producer.</code>.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">keySerde</span></dt><dd><p class="simpara">key serde to use</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">valueSerde</span></dt><dd><p class="simpara">value serde to use</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">useNativeEncoding</span></dt><dd><p class="simpara">flag to enable native encoding</p><p class="simpara">Default: <code class="literal">false</code>.</p></dd></dl></div><p>The following properties are available for Kafka Streams consumers and must be prefixed with <code class="literal">spring.cloud.stream.kafka.streams.bindings.<binding-name>.consumer.</code>
|
||||
For convenience, if there are multiple input bindings and they all require a common value, that can be configured by using the prefix <code class="literal">spring.cloud.stream.kafka.streams.default.consumer.</code>.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">applicationId</span></dt><dd><p class="simpara">Setting application.id per input binding.</p><p class="simpara">Default: <code class="literal">none</code></p></dd><dt><span class="term">keySerde</span></dt><dd><p class="simpara">key serde to use</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">valueSerde</span></dt><dd><p class="simpara">value serde to use</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">materializedAs</span></dt><dd><p class="simpara">state store to materialize when using incoming KTable types</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">useNativeDecoding</span></dt><dd><p class="simpara">flag to enable native decoding</p><p class="simpara">Default: <code class="literal">false</code>.</p></dd><dt><span class="term">dlqName</span></dt><dd><p class="simpara">DLQ topic name.</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">startOffset</span></dt><dd><p class="simpara">Offset to start from if there is no committed offset to consume from.
|
||||
This is mostly used when the consumer is consuming from a topic for the first time. Kafka Streams uses <code class="literal">earliest</code> as the default strategy and
|
||||
the binder uses the same default. This can be overridden to <code class="literal">latest</code> using this property.</p><p class="simpara">Default: <code class="literal">earliest</code>.</p></dd></dl></div><p>Note: Using <code class="literal">resetOffsets</code> on the consumer does not have any effect on Kafka Streams binder.
|
||||
Unlike the message channel based binder, Kafka Streams binder does not seek to beginning or end on demand.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_timewindow_properties" href="#_timewindow_properties"></a>2.3.2 TimeWindow properties:</h3></div></div></div><p>Windowing is an important concept in stream processing applications. Following properties are available to configure
|
||||
time-window computations.</p><div class="variablelist"><dl class="variablelist"><dt><span class="term">spring.cloud.stream.kafka.streams.timeWindow.length</span></dt><dd><p class="simpara">When this property is given, you can autowire a <code class="literal">TimeWindows</code> bean into the application.
|
||||
The value is expressed in milliseconds.</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd><dt><span class="term">spring.cloud.stream.kafka.streams.timeWindow.advanceBy</span></dt><dd><p class="simpara">Value is given in milliseconds.</p><p class="simpara">Default: <code class="literal">none</code>.</p></dd></dl></div></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_multiple_input_bindings" href="#_multiple_input_bindings"></a>2.4 Multiple Input Bindings</h2></div></div></div><p>For use cases that requires multiple incoming KStream objects or a combination of KStream and KTable objects, the Kafka
|
||||
Streams binder provides multiple bindings support.</p><p>Let’s see it in action.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_multiple_input_bindings_as_a_sink" href="#_multiple_input_bindings_as_a_sink"></a>2.4.1 Multiple Input Bindings as a Sink</h3></div></div></div><pre class="screen">@EnableBinding(KStreamKTableBinding.class)
|
||||
.....
|
||||
.....
|
||||
@StreamListener
|
||||
public void process(@Input("inputStream") KStream<String, PlayEvent> playEvents,
|
||||
@Input("inputTable") KTable<Long, Song> songTable) {
|
||||
....
|
||||
....
|
||||
}
|
||||
|
||||
interface KStreamKTableBinding {
|
||||
|
||||
@Input("inputStream")
|
||||
KStream<?, ?> inputStream();
|
||||
|
||||
@Input("inputTable")
|
||||
KTable<?, ?> inputTable();
|
||||
}</pre><p>In the above example, the application is written as a sink, i.e. there are no output bindings and the application has to
|
||||
decide concerning downstream processing. When you write applications in this style, you might want to send the information
|
||||
downstream or store them in a state store (See below for Queryable State Stores).</p><p>In the case of incoming KTable, if you want to materialize the computations to a state store, you have to express it
|
||||
through the following property.</p><pre class="screen">spring.cloud.stream.kafka.streams.bindings.inputTable.consumer.materializedAs: all-songs</pre><p>The above example shows the use of KTable as an input binding.
|
||||
The binder also supports input bindings for GlobalKTable.
|
||||
GlobalKTable binding is useful when you have to ensure that all instances of your application has access to the data updates from the topic.
|
||||
KTable and GlobalKTable bindings are only available on the input.
|
||||
Binder supports both input and output bindings for KStream.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_multiple_input_bindings_as_a_processor" href="#_multiple_input_bindings_as_a_processor"></a>2.5 Multiple Input Bindings as a Processor</h2></div></div></div><pre class="screen">@EnableBinding(KStreamKTableBinding.class)
|
||||
....
|
||||
....
|
||||
|
||||
@StreamListener
|
||||
@SendTo("output")
|
||||
public KStream<String, Long> process(@Input("input") KStream<String, Long> userClicksStream,
|
||||
@Input("inputTable") KTable<String, String> userRegionsTable) {
|
||||
....
|
||||
....
|
||||
}
|
||||
|
||||
interface KStreamKTableBinding extends KafkaStreamsProcessor {
|
||||
|
||||
@Input("inputX")
|
||||
KTable<?, ?> inputTable();
|
||||
}</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_multiple_output_bindings_aka_branching" href="#_multiple_output_bindings_aka_branching"></a>2.6 Multiple Output Bindings (aka Branching)</h2></div></div></div><p>Kafka Streams allow outbound data to be split into multiple topics based on some predicates. The Kafka Streams binder provides
|
||||
support for this feature without compromising the programming model exposed through <code class="literal">StreamListener</code> in the end user application.</p><p>You can write the application in the usual way as demonstrated above in the word count example. However, when using the
|
||||
branching feature, you are required to do a few things. First, you need to make sure that your return type is <code class="literal">KStream[]</code>
|
||||
instead of a regular <code class="literal">KStream</code>. Second, you need to use the <code class="literal">SendTo</code> annotation containing the output bindings in the order
|
||||
(see example below). For each of these output bindings, you need to configure destination, content-type etc., complying with
|
||||
the standard Spring Cloud Stream expectations.</p><p>Here is an example:</p><pre class="screen">@EnableBinding(KStreamProcessorWithBranches.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class WordCountProcessorApplication {
|
||||
|
||||
@Autowired
|
||||
private TimeWindows timeWindows;
|
||||
|
||||
@StreamListener("input")
|
||||
@SendTo({"output1","output2","output3})
|
||||
public KStream<?, WordCount>[] process(KStream<Object, String> input) {
|
||||
|
||||
Predicate<Object, WordCount> isEnglish = (k, v) -> v.word.equals("english");
|
||||
Predicate<Object, WordCount> isFrench = (k, v) -> v.word.equals("french");
|
||||
Predicate<Object, WordCount> isSpanish = (k, v) -> v.word.equals("spanish");
|
||||
|
||||
return input
|
||||
.flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
|
||||
.groupBy((key, value) -> value)
|
||||
.windowedBy(timeWindows)
|
||||
.count(Materialized.as("WordCounts-1"))
|
||||
.toStream()
|
||||
.map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end()))))
|
||||
.branch(isEnglish, isFrench, isSpanish);
|
||||
}
|
||||
|
||||
interface KStreamProcessorWithBranches {
|
||||
|
||||
@Input("input")
|
||||
KStream<?, ?> input();
|
||||
|
||||
@Output("output1")
|
||||
KStream<?, ?> output1();
|
||||
|
||||
@Output("output2")
|
||||
KStream<?, ?> output2();
|
||||
|
||||
@Output("output3")
|
||||
KStream<?, ?> output3();
|
||||
}
|
||||
}</pre><p>Properties:</p><pre class="screen">spring.cloud.stream.bindings.output1.contentType: application/json
|
||||
spring.cloud.stream.bindings.output2.contentType: application/json
|
||||
spring.cloud.stream.bindings.output3.contentType: application/json
|
||||
spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms: 1000
|
||||
spring.cloud.stream.kafka.streams.binder.configuration:
|
||||
default.key.serde: org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
default.value.serde: org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
spring.cloud.stream.bindings.output1:
|
||||
destination: foo
|
||||
producer:
|
||||
headerMode: raw
|
||||
spring.cloud.stream.bindings.output2:
|
||||
destination: bar
|
||||
producer:
|
||||
headerMode: raw
|
||||
spring.cloud.stream.bindings.output3:
|
||||
destination: fox
|
||||
producer:
|
||||
headerMode: raw
|
||||
spring.cloud.stream.bindings.input:
|
||||
destination: words
|
||||
consumer:
|
||||
headerMode: raw</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_record_value_conversion" href="#_record_value_conversion"></a>2.7 Record Value Conversion</h2></div></div></div><p>Kafka Streams binder can marshal producer/consumer values based on a content type and the converters provided out of the box in Spring Cloud Stream.</p><p>It is typical for Kafka Streams applications to provide <code class="literal">Serde</code> classes.
|
||||
Therefore, it may be more natural to rely on the SerDe facilities provided by the Apache Kafka Streams library itself for data conversion on inbound and outbound
|
||||
rather than rely on the content-type conversions offered by the binder.
|
||||
On the other hand, you might be already familiar with the content-type conversion patterns provided by Spring Cloud Stream and
|
||||
would like to continue using that for inbound and outbound conversions.</p><p>Both the options are supported in the Kafka Streams binder implementation. See below for more details.</p><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_outbound_serialization" href="#_outbound_serialization"></a>2.7.1 Outbound serialization</h3></div></div></div><p>If native encoding is disabled (which is the default), then the framework will convert the message using the contentType
|
||||
set by the user (otherwise, the default <code class="literal">application/json</code> will be applied). It will ignore any SerDe set on the outbound
|
||||
in this case for outbound serialization.</p><p>Here is the property to set the contentType on the outbound.</p><pre class="screen">spring.cloud.stream.bindings.output.contentType: application/json</pre><p>Here is the property to enable native encoding.</p><pre class="screen">spring.cloud.stream.bindings.output.nativeEncoding: true</pre><p>If native encoding is enabled on the output binding (user has to enable it as above explicitly), then the framework will
|
||||
skip any form of automatic message conversion on the outbound. In that case, it will switch to the Serde set by the user.
|
||||
The <code class="literal">valueSerde</code> property set on the actual output binding will be used. Here is an example.</p><pre class="screen">spring.cloud.stream.kafka.streams.bindings.output.producer.valueSerde: org.apache.kafka.common.serialization.Serdes$StringSerde</pre><p>If this property is not set, then it will use the "default" SerDe: <code class="literal">spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde</code>.</p><p>It is worth to mention that Kafka Streams binder does not serialize the keys on outbound - it simply relies on Kafka itself.
|
||||
Therefore, you either have to specify the <code class="literal">keySerde</code> property on the binding or it will default to the application-wide common
|
||||
<code class="literal">keySerde</code>.</p><p>Binding level key serde:</p><pre class="screen">spring.cloud.stream.kafka.streams.bindings.output.producer.keySerde</pre><p>Common Key serde:</p><pre class="screen">spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde</pre><p>If branching is used, then you need to use multiple output bindings. For example,</p><pre class="screen">interface KStreamProcessorWithBranches {
|
||||
|
||||
@Input("input")
|
||||
KStream<?, ?> input();
|
||||
|
||||
@Output("output1")
|
||||
KStream<?, ?> output1();
|
||||
|
||||
@Output("output2")
|
||||
KStream<?, ?> output2();
|
||||
|
||||
@Output("output3")
|
||||
KStream<?, ?> output3();
|
||||
}</pre><p>If <code class="literal">nativeEncoding</code> is set, then you can set different SerDe’s on individual output bindings as below.</p><pre class="screen">spring.cloud.stream.kafka.streams.bindings.output1.producer.valueSerde=IntegerSerde
|
||||
spring.cloud.stream.kafka.streams.bindings.output2.producer.valueSerde=StringSerde
|
||||
spring.cloud.stream.kafka.streams.bindings.output3.producer.valueSerde=JsonSerde</pre><p>Then if you have <code class="literal">SendTo</code> like this, @SendTo({"output1", "output2", "output3"}), the <code class="literal">KStream[]</code> from the branches are
|
||||
applied with proper SerDe objects as defined above. If you are not enabling <code class="literal">nativeEncoding</code>, you can then set different
|
||||
contentType values on the output bindings as below. In that case, the framework will use the appropriate message converter
|
||||
to convert the messages before sending to Kafka.</p><pre class="screen">spring.cloud.stream.bindings.output1.contentType: application/json
|
||||
spring.cloud.stream.bindings.output2.contentType: application/java-serialzied-object
|
||||
spring.cloud.stream.bindings.output3.contentType: application/octet-stream</pre></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_inbound_deserialization" href="#_inbound_deserialization"></a>2.7.2 Inbound Deserialization</h3></div></div></div><p>Similar rules apply to data deserialization on the inbound.</p><p>If native decoding is disabled (which is the default), then the framework will convert the message using the contentType
|
||||
set by the user (otherwise, the default <code class="literal">application/json</code> will be applied). It will ignore any SerDe set on the inbound
|
||||
in this case for inbound deserialization.</p><p>Here is the property to set the contentType on the inbound.</p><pre class="screen">spring.cloud.stream.bindings.input.contentType: application/json</pre><p>Here is the property to enable native decoding.</p><pre class="screen">spring.cloud.stream.bindings.input.nativeDecoding: true</pre><p>If native decoding is enabled on the input binding (user has to enable it as above explicitly), then the framework will
|
||||
skip doing any message conversion on the inbound. In that case, it will switch to the SerDe set by the user. The <code class="literal">valueSerde</code>
|
||||
property set on the actual output binding will be used. Here is an example.</p><pre class="screen">spring.cloud.stream.kafka.streams.bindings.input.consumer.valueSerde: org.apache.kafka.common.serialization.Serdes$StringSerde</pre><p>If this property is not set, it will use the default SerDe: <code class="literal">spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde</code>.</p><p>It is worth to mention that Kafka Streams binder does not deserialize the keys on inbound - it simply relies on Kafka itself.
|
||||
Therefore, you either have to specify the <code class="literal">keySerde</code> property on the binding or it will default to the application-wide common
|
||||
<code class="literal">keySerde</code>.</p><p>Binding level key serde:</p><pre class="screen">spring.cloud.stream.kafka.streams.bindings.input.consumer.keySerde</pre><p>Common Key serde:</p><pre class="screen">spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde</pre><p>As in the case of KStream branching on the outbound, the benefit of setting value SerDe per binding is that if you have
|
||||
multiple input bindings (multiple KStreams object) and they all require separate value SerDe’s, then you can configure
|
||||
them individually. If you use the common configuration approach, then this feature won’t be applicable.</p></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_error_handling" href="#_error_handling"></a>2.8 Error Handling</h2></div></div></div><p>Apache Kafka Streams provide the capability for natively handling exceptions from deserialization errors.
|
||||
For details on this support, please see <a class="link" href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-161%3A+streams+deserialization+exception+handlers" target="_top">this</a>
|
||||
Out of the box, Apache Kafka Streams provide two kinds of deserialization exception handlers - <code class="literal">logAndContinue</code> and <code class="literal">logAndFail</code>.
|
||||
As the name indicates, the former will log the error and continue processing the next records and the latter will log the
|
||||
error and fail. <code class="literal">LogAndFail</code> is the default deserialization exception handler.</p></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_handling_deserialization_exceptions" href="#_handling_deserialization_exceptions"></a>2.9 Handling Deserialization Exceptions</h2></div></div></div><p>Kafka Streams binder supports a selection of exception handlers through the following properties.</p><pre class="screen">spring.cloud.stream.kafka.streams.binder.serdeError: logAndContinue</pre><p>In addition to the above two deserialization exception handlers, the binder also provides a third one for sending the erroneous
|
||||
records (poison pills) to a DLQ topic. Here is how you enable this DLQ exception handler.</p><pre class="screen">spring.cloud.stream.kafka.streams.binder.serdeError: sendToDlq</pre><p>When the above property is set, all the deserialization error records are automatically sent to the DLQ topic.</p><pre class="screen">spring.cloud.stream.kafka.streams.bindings.input.consumer.dlqName: foo-dlq</pre><p>If this is set, then the error records are sent to the topic <code class="literal">foo-dlq</code>. If this is not set, then it will create a DLQ
|
||||
topic with the name <code class="literal">error.<input-topic-name>.<group-name></code>.</p><p>A couple of things to keep in mind when using the exception handling feature in Kafka Streams binder.</p><div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; "><li class="listitem">The property <code class="literal">spring.cloud.stream.kafka.streams.binder.serdeError</code> is applicable for the entire application. This implies
|
||||
that if there are multiple <code class="literal">StreamListener</code> methods in the same application, this property is applied to all of them.</li><li class="listitem">The exception handling for deserialization works consistently with native deserialization and framework provided message
|
||||
conversion.</li></ul></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="_handling_non_deserialization_exceptions" href="#_handling_non_deserialization_exceptions"></a>2.9.1 Handling Non-Deserialization Exceptions</h3></div></div></div><p>For general error handling in Kafka Streams binder, it is up to the end user applications to handle application level errors.
|
||||
As a side effect of providing a DLQ for deserialization exception handlers, Kafka Streams binder provides a way to get
|
||||
access to the DLQ sending bean directly from your application.
|
||||
Once you get access to that bean, you can programmatically send any exception records from your application to the DLQ.</p><p>It continues to remain hard to robust error handling using the high-level DSL; Kafka Streams doesn’t natively support error
|
||||
handling yet.</p><p>However, when you use the low-level Processor API in your application, there are options to control this behavior. See
|
||||
below.</p><pre class="screen">@Autowired
|
||||
private SendToDlqAndContinue dlqHandler;
|
||||
|
||||
@StreamListener("input")
|
||||
@SendTo("output")
|
||||
public KStream<?, WordCount> process(KStream<Object, String> input) {
|
||||
|
||||
input.process(() -> new Processor() {
|
||||
ProcessorContext context;
|
||||
|
||||
@Override
|
||||
public void init(ProcessorContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Object o, Object o2) {
|
||||
|
||||
try {
|
||||
.....
|
||||
.....
|
||||
}
|
||||
catch(Exception e) {
|
||||
//explicitly provide the kafka topic corresponding to the input binding as the first argument.
|
||||
//DLQ handler will correctly map to the dlq topic from the actual incoming destination.
|
||||
dlqHandler.sendToDlq("topic-name", (byte[]) o1, (byte[]) o2, context.partition());
|
||||
}
|
||||
}
|
||||
|
||||
.....
|
||||
.....
|
||||
});
|
||||
}</pre></div></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_state_store" href="#_state_store"></a>2.10 State Store</h2></div></div></div><p>State store is created automatically by Kafka Streams when the DSL is used.
|
||||
When processor API is used, you need to register a state store manually. In order to do so, you can use <code class="literal">KafkaStreamsStateStore</code> annotation.
|
||||
You can specify the name and type of the store, flags to control log and disabling cache, etc.
|
||||
Once the store is created by the binder during the bootstrapping phase, you can access this state store through the processor API.
|
||||
Below are some primitives for doing this.</p><p>Creating a state store:</p><pre class="screen">@KafkaStreamsStateStore(name="mystate", type= KafkaStreamsStateStoreProperties.StoreType.WINDOW, lengthMs=300000)
|
||||
public void process(KStream<Object, Product> input) {
|
||||
...
|
||||
}</pre><p>Accessing the state store:</p><pre class="screen">Processor<Object, Product>() {
|
||||
|
||||
WindowStore<Object, String> state;
|
||||
|
||||
@Override
|
||||
public void init(ProcessorContext processorContext) {
|
||||
state = (WindowStore)processorContext.getStateStore("mystate");
|
||||
}
|
||||
...
|
||||
}</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_interactive_queries" href="#_interactive_queries"></a>2.11 Interactive Queries</h2></div></div></div><p>As part of the public Kafka Streams binder API, we expose a class called <code class="literal">InteractiveQueryService</code>.
|
||||
You can access this as a Spring bean in your application. An easy way to get access to this bean from your application is to "autowire" the bean.</p><pre class="screen">@Autowired
|
||||
private InteractiveQueryService interactiveQueryService;</pre><p>Once you gain access to this bean, then you can query for the particular state-store that you are interested. See below.</p><pre class="screen">ReadOnlyKeyValueStore<Object, Object> keyValueStore =
|
||||
interactiveQueryService.getQueryableStoreType("my-store", QueryableStoreTypes.keyValueStore());</pre><p>If there are multiple instances of the kafka streams application running, then before you can query them interactively, you need to identify which application instance hosts the key.
|
||||
<code class="literal">InteractiveQueryService</code> API provides methods for identifying the host information.</p><p>In order for this to work, you must configure the property <code class="literal">application.server</code> as below:</p><pre class="screen">spring.cloud.stream.kafka.streams.binder.configuration.application.server: <server>:<port></pre><p>Here are some code snippets:</p><pre class="screen">org.apache.kafka.streams.state.HostInfo hostInfo = interactiveQueryService.getHostInfo("store-name",
|
||||
key, keySerializer);
|
||||
|
||||
if (interactiveQueryService.getCurrentHostInfo().equals(hostInfo)) {
|
||||
|
||||
//query from the store that is locally available
|
||||
}
|
||||
else {
|
||||
//query from the remote host
|
||||
}</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_accessing_the_underlying_kafkastreams_object" href="#_accessing_the_underlying_kafkastreams_object"></a>2.12 Accessing the underlying KafkaStreams object</h2></div></div></div><p><code class="literal">StreamBuilderFactoryBean</code> from spring-kafka that is responsible for constructing the <code class="literal">KafkaStreams</code> object can be accessed programmatically.
|
||||
Each <code class="literal">StreamBuilderFactoryBean</code> is registered as <code class="literal">stream-builder</code> and appended with the <code class="literal">StreamListener</code> method name.
|
||||
If your <code class="literal">StreamListener</code> method is named as <code class="literal">process</code> for example, the stream builder bean is named as <code class="literal">stream-builder-process</code>.
|
||||
Since this is a factory bean, it should be accessed by prepending an ampersand (<code class="literal">&</code>) when accessing it programmatically.
|
||||
Following is an example and it assumes the <code class="literal">StreamListener</code> method is named as <code class="literal">process</code></p><pre class="screen">StreamsBuilderFactoryBean streamsBuilderFactoryBean = context.getBean("&stream-builder-process", StreamsBuilderFactoryBean.class);
|
||||
KafkaStreams kafkaStreams = streamsBuilderFactoryBean.getKafkaStreams();</pre></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="_state_cleanup" href="#_state_cleanup"></a>2.13 State Cleanup</h2></div></div></div><p>By default, the <code class="literal">Kafkastreams.cleanup()</code> method is called when the binding is stopped.
|
||||
See <a class="link" href="https://docs.spring.io/spring-kafka/reference/html/_reference.html#_configuration" target="_top">the Spring Kafka documentation</a>.
|
||||
To modify this behavior simply add a single <code class="literal">CleanupConfig</code> <code class="literal">@Bean</code> (configured to clean up on start, stop, or neither) to the application context; the bean will be detected and wired into the factory bean.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi__apache_kafka_binder.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="multi__reference_guide.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="multi__appendices.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">1. Apache Kafka Binder </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-stream-binder-kafka.html">Home</a></td><td width="40%" align="right" valign="top"> Part II. Appendices</td></tr></table></div></body></html>
|
||||
@@ -1,5 +1,5 @@
|
||||
<html><head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
|
||||
<title>Part I. Reference Guide</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="up" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="prev" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="next" href="multi__usage.html" title="1. Usage"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Part I. Reference Guide</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi_spring-cloud-stream-binder-kafka.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="multi__usage.html">Next</a></td></tr></table><hr></div><div class="part"><div class="titlepage"><div><div><h1 class="title"><a name="_reference_guide" href="#_reference_guide"></a>Part I. Reference Guide</h1></div></div></div><div class="partintro"><div></div><p>This guide describes the Apache Kafka implementation of the Spring Cloud Stream Binder.
|
||||
<title>Part I. Reference Guide</title><link rel="stylesheet" type="text/css" href="css/manual-multipage.css"><meta name="generator" content="DocBook XSL Stylesheets V1.79.1"><link rel="home" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="up" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="prev" href="multi_spring-cloud-stream-binder-kafka.html" title="Spring Cloud Stream Kafka Binder Reference Guide"><link rel="next" href="multi__apache_kafka_binder.html" title="1. Apache Kafka Binder"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">Part I. Reference Guide</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="multi_spring-cloud-stream-binder-kafka.html">Prev</a> </td><th width="60%" align="center"> </th><td width="20%" align="right"> <a accesskey="n" href="multi__apache_kafka_binder.html">Next</a></td></tr></table><hr></div><div class="part"><div class="titlepage"><div><div><h1 class="title"><a name="_reference_guide" href="#_reference_guide"></a>Part I. Reference Guide</h1></div></div></div><div class="partintro"><div></div><p>This guide describes the Apache Kafka implementation of the Spring Cloud Stream Binder.
|
||||
It contains information about its design, usage, and configuration options, as well as information on how the Stream Cloud Stream concepts map onto Apache Kafka specific constructs.
|
||||
In addition, this guide explains the Kafka Streams binding capabilities of Spring Cloud Stream.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi_spring-cloud-stream-binder-kafka.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="multi__usage.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Spring Cloud Stream Kafka Binder Reference Guide </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-stream-binder-kafka.html">Home</a></td><td width="40%" align="right" valign="top"> 1. Usage</td></tr></table></div></body></html>
|
||||
In addition, this guide explains the Kafka Streams binding capabilities of Spring Cloud Stream.</p></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="multi_spring-cloud-stream-binder-kafka.html">Prev</a> </td><td width="20%" align="center"> </td><td width="40%" align="right"> <a accesskey="n" href="multi__apache_kafka_binder.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Spring Cloud Stream Kafka Binder Reference Guide </td><td width="20%" align="center"><a accesskey="h" href="multi_spring-cloud-stream-binder-kafka.html">Home</a></td><td width="40%" align="right" valign="top"> 1. Apache Kafka Binder</td></tr></table></div></body></html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -19,7 +19,9 @@
|
||||
It contains information about its design, usage, and configuration options, as well as information on how the Stream Cloud Stream concepts map onto Apache Kafka specific constructs.
|
||||
In addition, this guide explains the Kafka Streams binding capabilities of Spring Cloud Stream.</simpara>
|
||||
</partintro>
|
||||
<chapter xml:id="_usage">
|
||||
<chapter xml:id="_apache_kafka_binder">
|
||||
<title>Apache Kafka Binder</title>
|
||||
<section xml:id="_usage">
|
||||
<title>Usage</title>
|
||||
<simpara>To use Apache Kafka binder, you need to add <literal>spring-cloud-stream-binder-kafka</literal> as a dependency to your Spring Cloud Stream application, as shown in the following example for Maven:</simpara>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><dependency>
|
||||
@@ -31,9 +33,9 @@ In addition, this guide explains the Kafka Streams binding capabilities of Sprin
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-stream-kafka</artifactId>
|
||||
</dependency></programlisting>
|
||||
</chapter>
|
||||
<chapter xml:id="_apache_kafka_binder_overview">
|
||||
<title>Apache Kafka Binder Overview</title>
|
||||
</section>
|
||||
<section xml:id="_overview">
|
||||
<title>Overview</title>
|
||||
<simpara>The following image shows a simplified diagram of how the Apache Kafka binder operates:</simpara>
|
||||
<figure>
|
||||
<title>Kafka Binder</title>
|
||||
@@ -51,8 +53,8 @@ Partitioning also maps directly to Apache Kafka partitions as well.</simpara>
|
||||
This client can communicate with older brokers (see the Kafka documentation), but certain features may not be available.
|
||||
For example, with versions earlier than 0.11.x.x, native headers are not supported.
|
||||
Also, 0.11.x.x does not support the <literal>autoAddPartitions</literal> property.</simpara>
|
||||
</chapter>
|
||||
<chapter xml:id="_configuration_options">
|
||||
</section>
|
||||
<section xml:id="_configuration_options">
|
||||
<title>Configuration Options</title>
|
||||
<simpara>This section contains the configuration options used by the Apache Kafka binder.</simpara>
|
||||
<simpara>For common configuration options and properties pertaining to binder, see the <link linkend="binding-properties">core documentation</link>.</simpara>
|
||||
@@ -579,8 +581,8 @@ public class Application {
|
||||
}</programlisting>
|
||||
</section>
|
||||
</section>
|
||||
</chapter>
|
||||
<chapter xml:id="kafka-error-channels">
|
||||
</section>
|
||||
<section xml:id="kafka-error-channels">
|
||||
<title>Error Channels</title>
|
||||
<simpara>Starting with version 1.3, the binder unconditionally sends exceptions to an error channel for each consumer destination and can also be configured to send async producer send failures to an error channel.
|
||||
See <xref linkend="spring-cloud-stream-overview-error-handling"/> for more information.</simpara>
|
||||
@@ -595,15 +597,15 @@ See <xref linkend="spring-cloud-stream-overview-error-handling"/> for more infor
|
||||
</itemizedlist>
|
||||
<simpara>There is no automatic handling of producer exceptions (such as sending to a <link linkend="kafka-dlq-processing">Dead-Letter queue</link>).
|
||||
You can consume these exceptions with your own Spring Integration flow.</simpara>
|
||||
</chapter>
|
||||
<chapter xml:id="kafka-metrics">
|
||||
</section>
|
||||
<section xml:id="kafka-metrics">
|
||||
<title>Kafka Metrics</title>
|
||||
<simpara>Kafka binder module exposes the following metrics:</simpara>
|
||||
<simpara><literal>spring.cloud.stream.binder.kafka.offset</literal>: This metric indicates how many messages have not been yet consumed from a given binder’s topic by a given consumer group.
|
||||
The metrics provided are based on the Mircometer metrics library. The metric contains the consumer group information, topic and the actual lag in committed offset from the latest offset on the topic.
|
||||
This metric is particularly useful for providing auto-scaling feedback to a PaaS platform.</simpara>
|
||||
</chapter>
|
||||
<chapter xml:id="kafka-tombstones">
|
||||
</section>
|
||||
<section xml:id="kafka-tombstones">
|
||||
<title>Tombstone Records (null record values)</title>
|
||||
<simpara>When using compacted topics, a record with a <literal>null</literal> value (also called a tombstone record) represents the deletion of a key.
|
||||
To receive such messages in a <literal>@StreamListener</literal> method, the parameter must be marked as not required to receive a <literal>null</literal> value argument.</simpara>
|
||||
@@ -615,8 +617,8 @@ public void in(@Header(KafkaHeaders.RECEIVED_MESSAGE_KEY) byte[] key,
|
||||
...
|
||||
}</programlisting>
|
||||
</informalexample>
|
||||
</chapter>
|
||||
<chapter xml:id="rebalance-listener">
|
||||
</section>
|
||||
<section xml:id="rebalance-listener">
|
||||
<title>Using a KafkaRebalanceListener</title>
|
||||
<simpara>Applications may wish to seek topics/partitions to arbitrary offsets when the partitions are initially assigned, or perform other operations on the consumer.
|
||||
Starting with version 2.1, if you provide a single <literal>KafkaRebalanceListener</literal> bean in the application context, it will be wired into all Kafka consumer bindings.</simpara>
|
||||
@@ -660,8 +662,8 @@ Starting with version 2.1, if you provide a single <literal>KafkaRebalanceListen
|
||||
}</programlisting>
|
||||
</informalexample>
|
||||
<simpara>You cannot set the <literal>resetOffsets</literal> consumer property to <literal>true</literal> when you provide a rebalance listener.</simpara>
|
||||
</chapter>
|
||||
<chapter xml:id="kafka-dlq-processing">
|
||||
</section>
|
||||
<section xml:id="kafka-dlq-processing">
|
||||
<title>Dead-Letter Topic Processing</title>
|
||||
<simpara>Because you cannot anticipate how users would want to dispose of dead-lettered messages, the framework does not provide any standard mechanism to handle them.
|
||||
If the reason for the dead-lettering is transient, you may wish to route the messages back to the original topic.
|
||||
@@ -769,8 +771,8 @@ public class ReRouteDlqKApplication implements CommandLineRunner {
|
||||
}</programlisting>
|
||||
</para>
|
||||
</formalpara>
|
||||
</chapter>
|
||||
<chapter xml:id="_partitioning_with_the_kafka_binder">
|
||||
</section>
|
||||
<section xml:id="_partitioning_with_the_kafka_binder">
|
||||
<title>Partitioning with the Kafka Binder</title>
|
||||
<simpara>Apache Kafka supports topic partitioning natively.</simpara>
|
||||
<simpara>Sometimes it is advantageous to send data to specific partitions — for example, when you want to strictly order message processing (all messages for a particular customer should go to the same partition).</simpara>
|
||||
@@ -863,6 +865,584 @@ public class KafkaPartitionConsumerApplication {
|
||||
<simpara>You can add instances as needed.
|
||||
Kafka rebalances the partition allocations.
|
||||
If the instance count (or <literal>instance count * concurrency</literal>) exceeds the number of partitions, some consumers are idle.</simpara>
|
||||
</section>
|
||||
</chapter>
|
||||
<chapter xml:id="_kafka_streams_binder">
|
||||
<title>Kafka Streams Binder</title>
|
||||
<section xml:id="_usage_2">
|
||||
<title>Usage</title>
|
||||
<simpara>For using the Kafka Streams binder, you just need to add it to your Spring Cloud Stream application, using the following
|
||||
Maven coordinates:</simpara>
|
||||
<programlisting language="xml" linenumbering="unnumbered"><dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-stream-binder-kafka-streams</artifactId>
|
||||
</dependency></programlisting>
|
||||
</section>
|
||||
<section xml:id="_overview_2">
|
||||
<title>Overview</title>
|
||||
<simpara>Spring Cloud Stream’s Apache Kafka support also includes a binder implementation designed explicitly for Apache Kafka
|
||||
Streams binding. With this native integration, a Spring Cloud Stream "processor" application can directly use the
|
||||
<link xl:href="https://kafka.apache.org/documentation/streams/developer-guide">Apache Kafka Streams</link> APIs in the core business logic.</simpara>
|
||||
<simpara>Kafka Streams binder implementation builds on the foundation provided by the <link xl:href="http://docs.spring.io/spring-kafka/reference/html/_reference.html#kafka-streams">Kafka Streams in Spring Kafka</link>
|
||||
project.</simpara>
|
||||
<simpara>Kafka Streams binder provides binding capabilities for the three major types in Kafka Streams - KStream, KTable and GlobalKTable.</simpara>
|
||||
<simpara>As part of this native integration, the high-level <link xl:href="https://docs.confluent.io/current/streams/developer-guide/dsl-api.html">Streams DSL</link>
|
||||
provided by the Kafka Streams API is available for use in the business logic.</simpara>
|
||||
<simpara>An early version of the <link xl:href="https://docs.confluent.io/current/streams/developer-guide/processor-api.html">Processor API</link>
|
||||
support is available as well.</simpara>
|
||||
<simpara>As noted early-on, Kafka Streams support in Spring Cloud Stream is strictly only available for use in the Processor model.
|
||||
A model in which the messages read from an inbound topic, business processing can be applied, and the transformed messages
|
||||
can be written to an outbound topic. It can also be used in Processor applications with a no-outbound destination.</simpara>
|
||||
<section xml:id="_streams_dsl">
|
||||
<title>Streams DSL</title>
|
||||
<simpara>This application consumes data from a Kafka topic (e.g., <literal>words</literal>), computes word count for each unique word in a 5 seconds
|
||||
time window, and the computed results are sent to a downstream topic (e.g., <literal>counts</literal>) for further processing.</simpara>
|
||||
<screen>@SpringBootApplication
|
||||
@EnableBinding(KStreamProcessor.class)
|
||||
public class WordCountProcessorApplication {
|
||||
|
||||
@StreamListener("input")
|
||||
@SendTo("output")
|
||||
public KStream<?, WordCount> process(KStream<?, String> input) {
|
||||
return input
|
||||
.flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
|
||||
.groupBy((key, value) -> value)
|
||||
.windowedBy(TimeWindows.of(5000))
|
||||
.count(Materialized.as("WordCounts-multi"))
|
||||
.toStream()
|
||||
.map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end()))));
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(WordCountProcessorApplication.class, args);
|
||||
}</screen>
|
||||
<simpara>Once built as a uber-jar (e.g., <literal>wordcount-processor.jar</literal>), you can run the above example like the following.</simpara>
|
||||
<screen>java -jar wordcount-processor.jar --spring.cloud.stream.bindings.input.destination=words --spring.cloud.stream.bindings.output.destination=counts</screen>
|
||||
<simpara>This application will consume messages from the Kafka topic <literal>words</literal> and the computed results are published to an output
|
||||
topic <literal>counts</literal>.</simpara>
|
||||
<simpara>Spring Cloud Stream will ensure that the messages from both the incoming and outgoing topics are automatically bound as
|
||||
KStream objects. As a developer, you can exclusively focus on the business aspects of the code, i.e. writing the logic
|
||||
required in the processor. Setting up the Streams DSL specific configuration required by the Kafka Streams infrastructure
|
||||
is automatically handled by the framework.</simpara>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="_configuration_options_2">
|
||||
<title>Configuration Options</title>
|
||||
<simpara>This section contains the configuration options used by the Kafka Streams binder.</simpara>
|
||||
<simpara>For common configuration options and properties pertaining to binder, refer to the <link linkend="binding-properties">core documentation</link>.</simpara>
|
||||
<section xml:id="_kafka_streams_properties">
|
||||
<title>Kafka Streams Properties</title>
|
||||
<simpara>The following properties are available at the binder level and must be prefixed with <literal>spring.cloud.stream.kafka.streams.binder.</literal></simpara>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>configuration</term>
|
||||
<listitem>
|
||||
<simpara> Map with a key/value pair containing properties pertaining to Apache Kafka Streams API.
|
||||
This property must be prefixed with <literal>spring.cloud.stream.kafka.streams.binder.</literal>.
|
||||
Following are some examples of using this property.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<screen>spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde=org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms=1000</screen>
|
||||
<simpara>For more information about all the properties that may go into streams configuration, see StreamsConfig JavaDocs in
|
||||
Apache Kafka Streams docs.</simpara>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>brokers</term>
|
||||
<listitem>
|
||||
<simpara>Broker URL</simpara>
|
||||
<simpara>Default: <literal>localhost</literal></simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>zkNodes</term>
|
||||
<listitem>
|
||||
<simpara>Zookeeper URL</simpara>
|
||||
<simpara>Default: <literal>localhost</literal></simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>serdeError</term>
|
||||
<listitem>
|
||||
<simpara>Deserialization error handler type.
|
||||
Possible values are - <literal>logAndContinue</literal>, <literal>logAndFail</literal> or <literal>sendToDlq</literal></simpara>
|
||||
<simpara>Default: <literal>logAndFail</literal></simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>applicationId</term>
|
||||
<listitem>
|
||||
<simpara>Convenient way to set the application.id for the Kafka Streams application globally at the binder level.
|
||||
If the application contains multiple <literal>StreamListener</literal> methods, then application.id should be set at the binding level per input binding.</simpara>
|
||||
<simpara>Default: <literal>none</literal></simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<simpara>The following properties are <emphasis>only</emphasis> available for Kafka Streams producers and must be prefixed with <literal>spring.cloud.stream.kafka.streams.bindings.<binding name>.producer.</literal>
|
||||
For convenience, if there multiple output bindings and they all require a common value, that can be configured by using the prefix <literal>spring.cloud.stream.kafka.streams.default.producer.</literal>.</simpara>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>keySerde</term>
|
||||
<listitem>
|
||||
<simpara>key serde to use</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>valueSerde</term>
|
||||
<listitem>
|
||||
<simpara>value serde to use</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>useNativeEncoding</term>
|
||||
<listitem>
|
||||
<simpara>flag to enable native encoding</simpara>
|
||||
<simpara>Default: <literal>false</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<simpara>The following properties are available for Kafka Streams consumers and must be prefixed with <literal>spring.cloud.stream.kafka.streams.bindings.<binding-name>.consumer.</literal>
|
||||
For convenience, if there are multiple input bindings and they all require a common value, that can be configured by using the prefix <literal>spring.cloud.stream.kafka.streams.default.consumer.</literal>.</simpara>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>applicationId</term>
|
||||
<listitem>
|
||||
<simpara>Setting application.id per input binding.</simpara>
|
||||
<simpara>Default: <literal>none</literal></simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>keySerde</term>
|
||||
<listitem>
|
||||
<simpara>key serde to use</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>valueSerde</term>
|
||||
<listitem>
|
||||
<simpara>value serde to use</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>materializedAs</term>
|
||||
<listitem>
|
||||
<simpara>state store to materialize when using incoming KTable types</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>useNativeDecoding</term>
|
||||
<listitem>
|
||||
<simpara>flag to enable native decoding</simpara>
|
||||
<simpara>Default: <literal>false</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>dlqName</term>
|
||||
<listitem>
|
||||
<simpara>DLQ topic name.</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>startOffset</term>
|
||||
<listitem>
|
||||
<simpara>Offset to start from if there is no committed offset to consume from.
|
||||
This is mostly used when the consumer is consuming from a topic for the first time. Kafka Streams uses <literal>earliest</literal> as the default strategy and
|
||||
the binder uses the same default. This can be overridden to <literal>latest</literal> using this property.</simpara>
|
||||
<simpara>Default: <literal>earliest</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
<simpara>Note: Using <literal>resetOffsets</literal> on the consumer does not have any effect on Kafka Streams binder.
|
||||
Unlike the message channel based binder, Kafka Streams binder does not seek to beginning or end on demand.</simpara>
|
||||
</section>
|
||||
<section xml:id="_timewindow_properties">
|
||||
<title>TimeWindow properties:</title>
|
||||
<simpara>Windowing is an important concept in stream processing applications. Following properties are available to configure
|
||||
time-window computations.</simpara>
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term>spring.cloud.stream.kafka.streams.timeWindow.length</term>
|
||||
<listitem>
|
||||
<simpara>When this property is given, you can autowire a <literal>TimeWindows</literal> bean into the application.
|
||||
The value is expressed in milliseconds.</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term>spring.cloud.stream.kafka.streams.timeWindow.advanceBy</term>
|
||||
<listitem>
|
||||
<simpara>Value is given in milliseconds.</simpara>
|
||||
<simpara>Default: <literal>none</literal>.</simpara>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="_multiple_input_bindings">
|
||||
<title>Multiple Input Bindings</title>
|
||||
<simpara>For use cases that requires multiple incoming KStream objects or a combination of KStream and KTable objects, the Kafka
|
||||
Streams binder provides multiple bindings support.</simpara>
|
||||
<simpara>Let’s see it in action.</simpara>
|
||||
<section xml:id="_multiple_input_bindings_as_a_sink">
|
||||
<title>Multiple Input Bindings as a Sink</title>
|
||||
<screen>@EnableBinding(KStreamKTableBinding.class)
|
||||
.....
|
||||
.....
|
||||
@StreamListener
|
||||
public void process(@Input("inputStream") KStream<String, PlayEvent> playEvents,
|
||||
@Input("inputTable") KTable<Long, Song> songTable) {
|
||||
....
|
||||
....
|
||||
}
|
||||
|
||||
interface KStreamKTableBinding {
|
||||
|
||||
@Input("inputStream")
|
||||
KStream<?, ?> inputStream();
|
||||
|
||||
@Input("inputTable")
|
||||
KTable<?, ?> inputTable();
|
||||
}</screen>
|
||||
<simpara>In the above example, the application is written as a sink, i.e. there are no output bindings and the application has to
|
||||
decide concerning downstream processing. When you write applications in this style, you might want to send the information
|
||||
downstream or store them in a state store (See below for Queryable State Stores).</simpara>
|
||||
<simpara>In the case of incoming KTable, if you want to materialize the computations to a state store, you have to express it
|
||||
through the following property.</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.bindings.inputTable.consumer.materializedAs: all-songs</screen>
|
||||
<simpara>The above example shows the use of KTable as an input binding.
|
||||
The binder also supports input bindings for GlobalKTable.
|
||||
GlobalKTable binding is useful when you have to ensure that all instances of your application has access to the data updates from the topic.
|
||||
KTable and GlobalKTable bindings are only available on the input.
|
||||
Binder supports both input and output bindings for KStream.</simpara>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="_multiple_input_bindings_as_a_processor">
|
||||
<title>Multiple Input Bindings as a Processor</title>
|
||||
<screen>@EnableBinding(KStreamKTableBinding.class)
|
||||
....
|
||||
....
|
||||
|
||||
@StreamListener
|
||||
@SendTo("output")
|
||||
public KStream<String, Long> process(@Input("input") KStream<String, Long> userClicksStream,
|
||||
@Input("inputTable") KTable<String, String> userRegionsTable) {
|
||||
....
|
||||
....
|
||||
}
|
||||
|
||||
interface KStreamKTableBinding extends KafkaStreamsProcessor {
|
||||
|
||||
@Input("inputX")
|
||||
KTable<?, ?> inputTable();
|
||||
}</screen>
|
||||
</section>
|
||||
<section xml:id="_multiple_output_bindings_aka_branching">
|
||||
<title>Multiple Output Bindings (aka Branching)</title>
|
||||
<simpara>Kafka Streams allow outbound data to be split into multiple topics based on some predicates. The Kafka Streams binder provides
|
||||
support for this feature without compromising the programming model exposed through <literal>StreamListener</literal> in the end user application.</simpara>
|
||||
<simpara>You can write the application in the usual way as demonstrated above in the word count example. However, when using the
|
||||
branching feature, you are required to do a few things. First, you need to make sure that your return type is <literal>KStream[]</literal>
|
||||
instead of a regular <literal>KStream</literal>. Second, you need to use the <literal>SendTo</literal> annotation containing the output bindings in the order
|
||||
(see example below). For each of these output bindings, you need to configure destination, content-type etc., complying with
|
||||
the standard Spring Cloud Stream expectations.</simpara>
|
||||
<simpara>Here is an example:</simpara>
|
||||
<screen>@EnableBinding(KStreamProcessorWithBranches.class)
|
||||
@EnableAutoConfiguration
|
||||
public static class WordCountProcessorApplication {
|
||||
|
||||
@Autowired
|
||||
private TimeWindows timeWindows;
|
||||
|
||||
@StreamListener("input")
|
||||
@SendTo({"output1","output2","output3})
|
||||
public KStream<?, WordCount>[] process(KStream<Object, String> input) {
|
||||
|
||||
Predicate<Object, WordCount> isEnglish = (k, v) -> v.word.equals("english");
|
||||
Predicate<Object, WordCount> isFrench = (k, v) -> v.word.equals("french");
|
||||
Predicate<Object, WordCount> isSpanish = (k, v) -> v.word.equals("spanish");
|
||||
|
||||
return input
|
||||
.flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
|
||||
.groupBy((key, value) -> value)
|
||||
.windowedBy(timeWindows)
|
||||
.count(Materialized.as("WordCounts-1"))
|
||||
.toStream()
|
||||
.map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end()))))
|
||||
.branch(isEnglish, isFrench, isSpanish);
|
||||
}
|
||||
|
||||
interface KStreamProcessorWithBranches {
|
||||
|
||||
@Input("input")
|
||||
KStream<?, ?> input();
|
||||
|
||||
@Output("output1")
|
||||
KStream<?, ?> output1();
|
||||
|
||||
@Output("output2")
|
||||
KStream<?, ?> output2();
|
||||
|
||||
@Output("output3")
|
||||
KStream<?, ?> output3();
|
||||
}
|
||||
}</screen>
|
||||
<simpara>Properties:</simpara>
|
||||
<screen>spring.cloud.stream.bindings.output1.contentType: application/json
|
||||
spring.cloud.stream.bindings.output2.contentType: application/json
|
||||
spring.cloud.stream.bindings.output3.contentType: application/json
|
||||
spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms: 1000
|
||||
spring.cloud.stream.kafka.streams.binder.configuration:
|
||||
default.key.serde: org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
default.value.serde: org.apache.kafka.common.serialization.Serdes$StringSerde
|
||||
spring.cloud.stream.bindings.output1:
|
||||
destination: foo
|
||||
producer:
|
||||
headerMode: raw
|
||||
spring.cloud.stream.bindings.output2:
|
||||
destination: bar
|
||||
producer:
|
||||
headerMode: raw
|
||||
spring.cloud.stream.bindings.output3:
|
||||
destination: fox
|
||||
producer:
|
||||
headerMode: raw
|
||||
spring.cloud.stream.bindings.input:
|
||||
destination: words
|
||||
consumer:
|
||||
headerMode: raw</screen>
|
||||
</section>
|
||||
<section xml:id="_record_value_conversion">
|
||||
<title>Record Value Conversion</title>
|
||||
<simpara>Kafka Streams binder can marshal producer/consumer values based on a content type and the converters provided out of the box in Spring Cloud Stream.</simpara>
|
||||
<simpara>It is typical for Kafka Streams applications to provide <literal>Serde</literal> classes.
|
||||
Therefore, it may be more natural to rely on the SerDe facilities provided by the Apache Kafka Streams library itself for data conversion on inbound and outbound
|
||||
rather than rely on the content-type conversions offered by the binder.
|
||||
On the other hand, you might be already familiar with the content-type conversion patterns provided by Spring Cloud Stream and
|
||||
would like to continue using that for inbound and outbound conversions.</simpara>
|
||||
<simpara>Both the options are supported in the Kafka Streams binder implementation. See below for more details.</simpara>
|
||||
<section xml:id="_outbound_serialization">
|
||||
<title>Outbound serialization</title>
|
||||
<simpara>If native encoding is disabled (which is the default), then the framework will convert the message using the contentType
|
||||
set by the user (otherwise, the default <literal>application/json</literal> will be applied). It will ignore any SerDe set on the outbound
|
||||
in this case for outbound serialization.</simpara>
|
||||
<simpara>Here is the property to set the contentType on the outbound.</simpara>
|
||||
<screen>spring.cloud.stream.bindings.output.contentType: application/json</screen>
|
||||
<simpara>Here is the property to enable native encoding.</simpara>
|
||||
<screen>spring.cloud.stream.bindings.output.nativeEncoding: true</screen>
|
||||
<simpara>If native encoding is enabled on the output binding (user has to enable it as above explicitly), then the framework will
|
||||
skip any form of automatic message conversion on the outbound. In that case, it will switch to the Serde set by the user.
|
||||
The <literal>valueSerde</literal> property set on the actual output binding will be used. Here is an example.</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.bindings.output.producer.valueSerde: org.apache.kafka.common.serialization.Serdes$StringSerde</screen>
|
||||
<simpara>If this property is not set, then it will use the "default" SerDe: <literal>spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde</literal>.</simpara>
|
||||
<simpara>It is worth to mention that Kafka Streams binder does not serialize the keys on outbound - it simply relies on Kafka itself.
|
||||
Therefore, you either have to specify the <literal>keySerde</literal> property on the binding or it will default to the application-wide common
|
||||
<literal>keySerde</literal>.</simpara>
|
||||
<simpara>Binding level key serde:</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.bindings.output.producer.keySerde</screen>
|
||||
<simpara>Common Key serde:</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde</screen>
|
||||
<simpara>If branching is used, then you need to use multiple output bindings. For example,</simpara>
|
||||
<screen>interface KStreamProcessorWithBranches {
|
||||
|
||||
@Input("input")
|
||||
KStream<?, ?> input();
|
||||
|
||||
@Output("output1")
|
||||
KStream<?, ?> output1();
|
||||
|
||||
@Output("output2")
|
||||
KStream<?, ?> output2();
|
||||
|
||||
@Output("output3")
|
||||
KStream<?, ?> output3();
|
||||
}</screen>
|
||||
<simpara>If <literal>nativeEncoding</literal> is set, then you can set different SerDe’s on individual output bindings as below.</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.bindings.output1.producer.valueSerde=IntegerSerde
|
||||
spring.cloud.stream.kafka.streams.bindings.output2.producer.valueSerde=StringSerde
|
||||
spring.cloud.stream.kafka.streams.bindings.output3.producer.valueSerde=JsonSerde</screen>
|
||||
<simpara>Then if you have <literal>SendTo</literal> like this, @SendTo({"output1", "output2", "output3"}), the <literal>KStream[]</literal> from the branches are
|
||||
applied with proper SerDe objects as defined above. If you are not enabling <literal>nativeEncoding</literal>, you can then set different
|
||||
contentType values on the output bindings as below. In that case, the framework will use the appropriate message converter
|
||||
to convert the messages before sending to Kafka.</simpara>
|
||||
<screen>spring.cloud.stream.bindings.output1.contentType: application/json
|
||||
spring.cloud.stream.bindings.output2.contentType: application/java-serialzied-object
|
||||
spring.cloud.stream.bindings.output3.contentType: application/octet-stream</screen>
|
||||
</section>
|
||||
<section xml:id="_inbound_deserialization">
|
||||
<title>Inbound Deserialization</title>
|
||||
<simpara>Similar rules apply to data deserialization on the inbound.</simpara>
|
||||
<simpara>If native decoding is disabled (which is the default), then the framework will convert the message using the contentType
|
||||
set by the user (otherwise, the default <literal>application/json</literal> will be applied). It will ignore any SerDe set on the inbound
|
||||
in this case for inbound deserialization.</simpara>
|
||||
<simpara>Here is the property to set the contentType on the inbound.</simpara>
|
||||
<screen>spring.cloud.stream.bindings.input.contentType: application/json</screen>
|
||||
<simpara>Here is the property to enable native decoding.</simpara>
|
||||
<screen>spring.cloud.stream.bindings.input.nativeDecoding: true</screen>
|
||||
<simpara>If native decoding is enabled on the input binding (user has to enable it as above explicitly), then the framework will
|
||||
skip doing any message conversion on the inbound. In that case, it will switch to the SerDe set by the user. The <literal>valueSerde</literal>
|
||||
property set on the actual output binding will be used. Here is an example.</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.bindings.input.consumer.valueSerde: org.apache.kafka.common.serialization.Serdes$StringSerde</screen>
|
||||
<simpara>If this property is not set, it will use the default SerDe: <literal>spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde</literal>.</simpara>
|
||||
<simpara>It is worth to mention that Kafka Streams binder does not deserialize the keys on inbound - it simply relies on Kafka itself.
|
||||
Therefore, you either have to specify the <literal>keySerde</literal> property on the binding or it will default to the application-wide common
|
||||
<literal>keySerde</literal>.</simpara>
|
||||
<simpara>Binding level key serde:</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.bindings.input.consumer.keySerde</screen>
|
||||
<simpara>Common Key serde:</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde</screen>
|
||||
<simpara>As in the case of KStream branching on the outbound, the benefit of setting value SerDe per binding is that if you have
|
||||
multiple input bindings (multiple KStreams object) and they all require separate value SerDe’s, then you can configure
|
||||
them individually. If you use the common configuration approach, then this feature won’t be applicable.</simpara>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="_error_handling">
|
||||
<title>Error Handling</title>
|
||||
<simpara>Apache Kafka Streams provide the capability for natively handling exceptions from deserialization errors.
|
||||
For details on this support, please see <link xl:href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-161%3A+streams+deserialization+exception+handlers">this</link>
|
||||
Out of the box, Apache Kafka Streams provide two kinds of deserialization exception handlers - <literal>logAndContinue</literal> and <literal>logAndFail</literal>.
|
||||
As the name indicates, the former will log the error and continue processing the next records and the latter will log the
|
||||
error and fail. <literal>LogAndFail</literal> is the default deserialization exception handler.</simpara>
|
||||
</section>
|
||||
<section xml:id="_handling_deserialization_exceptions">
|
||||
<title>Handling Deserialization Exceptions</title>
|
||||
<simpara>Kafka Streams binder supports a selection of exception handlers through the following properties.</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.binder.serdeError: logAndContinue</screen>
|
||||
<simpara>In addition to the above two deserialization exception handlers, the binder also provides a third one for sending the erroneous
|
||||
records (poison pills) to a DLQ topic. Here is how you enable this DLQ exception handler.</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.binder.serdeError: sendToDlq</screen>
|
||||
<simpara>When the above property is set, all the deserialization error records are automatically sent to the DLQ topic.</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.bindings.input.consumer.dlqName: foo-dlq</screen>
|
||||
<simpara>If this is set, then the error records are sent to the topic <literal>foo-dlq</literal>. If this is not set, then it will create a DLQ
|
||||
topic with the name <literal>error.<input-topic-name>.<group-name></literal>.</simpara>
|
||||
<simpara>A couple of things to keep in mind when using the exception handling feature in Kafka Streams binder.</simpara>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<simpara>The property <literal>spring.cloud.stream.kafka.streams.binder.serdeError</literal> is applicable for the entire application. This implies
|
||||
that if there are multiple <literal>StreamListener</literal> methods in the same application, this property is applied to all of them.</simpara>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<simpara>The exception handling for deserialization works consistently with native deserialization and framework provided message
|
||||
conversion.</simpara>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
<section xml:id="_handling_non_deserialization_exceptions">
|
||||
<title>Handling Non-Deserialization Exceptions</title>
|
||||
<simpara>For general error handling in Kafka Streams binder, it is up to the end user applications to handle application level errors.
|
||||
As a side effect of providing a DLQ for deserialization exception handlers, Kafka Streams binder provides a way to get
|
||||
access to the DLQ sending bean directly from your application.
|
||||
Once you get access to that bean, you can programmatically send any exception records from your application to the DLQ.</simpara>
|
||||
<simpara>It continues to remain hard to robust error handling using the high-level DSL; Kafka Streams doesn’t natively support error
|
||||
handling yet.</simpara>
|
||||
<simpara>However, when you use the low-level Processor API in your application, there are options to control this behavior. See
|
||||
below.</simpara>
|
||||
<screen>@Autowired
|
||||
private SendToDlqAndContinue dlqHandler;
|
||||
|
||||
@StreamListener("input")
|
||||
@SendTo("output")
|
||||
public KStream<?, WordCount> process(KStream<Object, String> input) {
|
||||
|
||||
input.process(() -> new Processor() {
|
||||
ProcessorContext context;
|
||||
|
||||
@Override
|
||||
public void init(ProcessorContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Object o, Object o2) {
|
||||
|
||||
try {
|
||||
.....
|
||||
.....
|
||||
}
|
||||
catch(Exception e) {
|
||||
//explicitly provide the kafka topic corresponding to the input binding as the first argument.
|
||||
//DLQ handler will correctly map to the dlq topic from the actual incoming destination.
|
||||
dlqHandler.sendToDlq("topic-name", (byte[]) o1, (byte[]) o2, context.partition());
|
||||
}
|
||||
}
|
||||
|
||||
.....
|
||||
.....
|
||||
});
|
||||
}</screen>
|
||||
</section>
|
||||
</section>
|
||||
<section xml:id="_state_store">
|
||||
<title>State Store</title>
|
||||
<simpara>State store is created automatically by Kafka Streams when the DSL is used.
|
||||
When processor API is used, you need to register a state store manually. In order to do so, you can use <literal>KafkaStreamsStateStore</literal> annotation.
|
||||
You can specify the name and type of the store, flags to control log and disabling cache, etc.
|
||||
Once the store is created by the binder during the bootstrapping phase, you can access this state store through the processor API.
|
||||
Below are some primitives for doing this.</simpara>
|
||||
<simpara>Creating a state store:</simpara>
|
||||
<screen>@KafkaStreamsStateStore(name="mystate", type= KafkaStreamsStateStoreProperties.StoreType.WINDOW, lengthMs=300000)
|
||||
public void process(KStream<Object, Product> input) {
|
||||
...
|
||||
}</screen>
|
||||
<simpara>Accessing the state store:</simpara>
|
||||
<screen>Processor<Object, Product>() {
|
||||
|
||||
WindowStore<Object, String> state;
|
||||
|
||||
@Override
|
||||
public void init(ProcessorContext processorContext) {
|
||||
state = (WindowStore)processorContext.getStateStore("mystate");
|
||||
}
|
||||
...
|
||||
}</screen>
|
||||
</section>
|
||||
<section xml:id="_interactive_queries">
|
||||
<title>Interactive Queries</title>
|
||||
<simpara>As part of the public Kafka Streams binder API, we expose a class called <literal>InteractiveQueryService</literal>.
|
||||
You can access this as a Spring bean in your application. An easy way to get access to this bean from your application is to "autowire" the bean.</simpara>
|
||||
<screen>@Autowired
|
||||
private InteractiveQueryService interactiveQueryService;</screen>
|
||||
<simpara>Once you gain access to this bean, then you can query for the particular state-store that you are interested. See below.</simpara>
|
||||
<screen>ReadOnlyKeyValueStore<Object, Object> keyValueStore =
|
||||
interactiveQueryService.getQueryableStoreType("my-store", QueryableStoreTypes.keyValueStore());</screen>
|
||||
<simpara>If there are multiple instances of the kafka streams application running, then before you can query them interactively, you need to identify which application instance hosts the key.
|
||||
<literal>InteractiveQueryService</literal> API provides methods for identifying the host information.</simpara>
|
||||
<simpara>In order for this to work, you must configure the property <literal>application.server</literal> as below:</simpara>
|
||||
<screen>spring.cloud.stream.kafka.streams.binder.configuration.application.server: <server>:<port></screen>
|
||||
<simpara>Here are some code snippets:</simpara>
|
||||
<screen>org.apache.kafka.streams.state.HostInfo hostInfo = interactiveQueryService.getHostInfo("store-name",
|
||||
key, keySerializer);
|
||||
|
||||
if (interactiveQueryService.getCurrentHostInfo().equals(hostInfo)) {
|
||||
|
||||
//query from the store that is locally available
|
||||
}
|
||||
else {
|
||||
//query from the remote host
|
||||
}</screen>
|
||||
</section>
|
||||
<section xml:id="_accessing_the_underlying_kafkastreams_object">
|
||||
<title>Accessing the underlying KafkaStreams object</title>
|
||||
<simpara><literal>StreamBuilderFactoryBean</literal> from spring-kafka that is responsible for constructing the <literal>KafkaStreams</literal> object can be accessed programmatically.
|
||||
Each <literal>StreamBuilderFactoryBean</literal> is registered as <literal>stream-builder</literal> and appended with the <literal>StreamListener</literal> method name.
|
||||
If your <literal>StreamListener</literal> method is named as <literal>process</literal> for example, the stream builder bean is named as <literal>stream-builder-process</literal>.
|
||||
Since this is a factory bean, it should be accessed by prepending an ampersand (<literal>&</literal>) when accessing it programmatically.
|
||||
Following is an example and it assumes the <literal>StreamListener</literal> method is named as <literal>process</literal></simpara>
|
||||
<screen>StreamsBuilderFactoryBean streamsBuilderFactoryBean = context.getBean("&stream-builder-process", StreamsBuilderFactoryBean.class);
|
||||
KafkaStreams kafkaStreams = streamsBuilderFactoryBean.getKafkaStreams();</screen>
|
||||
</section>
|
||||
<section xml:id="_state_cleanup">
|
||||
<title>State Cleanup</title>
|
||||
<simpara>By default, the <literal>Kafkastreams.cleanup()</literal> method is called when the binding is stopped.
|
||||
See <link xl:href="https://docs.spring.io/spring-kafka/reference/html/_reference.html#_configuration">the Spring Kafka documentation</link>.
|
||||
To modify this behavior simply add a single <literal>CleanupConfig</literal> <literal>@Bean</literal> (configured to clean up on start, stop, or neither) to the application context; the bean will be detected and wired into the factory bean.</simpara>
|
||||
</section>
|
||||
</chapter>
|
||||
</part>
|
||||
<part xml:id="_appendices">
|
||||
|
||||
Reference in New Issue
Block a user