Skip to content

Commit e72f1b3

Browse files
akenraolegz
authored andcommitted
GH-3193 Add ackCount and ackTime to KafkaConsumerProperties
Signed-off-by: akenra <37288280+akenra@users.noreply.github.com>
1 parent 7650f6a commit e72f1b3

4 files changed

Lines changed: 93 additions & 0 deletions

File tree

binders/kafka-binder/spring-cloud-stream-binder-kafka-core/src/main/java/org/springframework/cloud/stream/binder/kafka/properties/KafkaConsumerProperties.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.cloud.stream.binder.kafka.properties;
1818

19+
import java.time.Duration;
1920
import java.util.HashMap;
2021
import java.util.Map;
2122

@@ -99,6 +100,24 @@ public enum StandardHeaders {
99100
*/
100101
private ContainerProperties.AckMode ackMode;
101102

103+
/**
104+
* Consumer ack count. When set, the container will send an acknowledgment after the specified number
105+
* of messages have been consumed. Only applicable for COUNT or COUNT_TIME ack modes.
106+
* <p>
107+
* This property is binding-level and takes precedence over the global
108+
* {@code spring.kafka.listener.ackCount} when explicitly configured on the binding.
109+
*/
110+
private Integer ackCount;
111+
112+
/**
113+
* Consumer ack time. When set, the container will send an acknowledgment after the specified duration
114+
* has elapsed since the last message was consumed. Only applicable for TIME or COUNT_TIME ack modes.
115+
* <p>
116+
* This property is binding-level and takes precedence over the global
117+
* {@code spring.kafka.listener.ackTime} when explicitly configured on the binding.
118+
*/
119+
private Duration ackTime;
120+
102121
/**
103122
* Flag to enable auto commit on error in polled consumers.
104123
*/
@@ -223,6 +242,34 @@ public void setAckMode(ContainerProperties.AckMode ackMode) {
223242
this.ackMode = ackMode;
224243
}
225244

245+
/**
246+
* @return consumer ack count, or null if not set at the binding level.
247+
* <p>
248+
* Binding-level value takes precedence over the global
249+
* {@code spring.kafka.listener.ackCount} when explicitly configured.
250+
*/
251+
public Integer getAckCount() {
252+
return this.ackCount;
253+
}
254+
255+
public void setAckCount(Integer ackCount) {
256+
this.ackCount = ackCount;
257+
}
258+
259+
/**
260+
* @return consumer ack time, or null if not set at the binding level.
261+
* <p>
262+
* Binding-level value takes precedence over the global
263+
* {@code spring.kafka.listener.ackTime} when explicitly configured.
264+
*/
265+
public Duration getAckTime() {
266+
return this.ackTime;
267+
}
268+
269+
public void setAckTime(Duration ackTime) {
270+
this.ackTime = ackTime;
271+
}
272+
226273
/**
227274
* @return start offset
228275
*

binders/kafka-binder/spring-cloud-stream-binder-kafka/src/main/java/org/springframework/cloud/stream/binder/kafka/KafkaMessageChannelBinder.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.HashMap;
2828
import java.util.List;
2929
import java.util.Locale;
30+
import java.time.Duration;
3031
import java.util.Map;
3132
import java.util.Objects;
3233
import java.util.Set;
@@ -710,6 +711,18 @@ else if (applicationContext != null) {
710711
}
711712
}
712713

714+
// Apply binding-level ackCount and ackTime after global propagation
715+
// These take precedence over global spring.kafka.listener values when explicitly set
716+
KafkaConsumerProperties consumerExt = extendedConsumerProperties.getExtension();
717+
Integer bindingAckCount = consumerExt.getAckCount();
718+
if (bindingAckCount != null) {
719+
messageListenerContainer.getContainerProperties().setAckCount(bindingAckCount);
720+
}
721+
Duration bindingAckTime = consumerExt.getAckTime();
722+
if (bindingAckTime != null) {
723+
messageListenerContainer.getContainerProperties().setAckTime(bindingAckTime.toMillis());
724+
}
725+
713726
if (this.logger.isDebugEnabled()) {
714727
this.logger.debug("Listened partitions: "
715728
+ StringUtils.collectionToCommaDelimitedString(listenedPartitions));

binders/kafka-binder/spring-cloud-stream-binder-kafka/src/test/java/org/springframework/cloud/stream/binder/kafka/KafkaBinderTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2015,6 +2015,29 @@ void manualAckIsNotPossibleWhenAutoCommitOffsetIsEnabledOnTheBinder()
20152015
consumerBinding.unbind();
20162016
}
20172017

2018+
@Test
2019+
void bindingAckCountAndAckTimeAreAppliedToContainerProperties() {
2020+
Binder<MessageChannel, ExtendedConsumerProperties<KafkaConsumerProperties>, ExtendedProducerProperties<KafkaProducerProperties>> binder = getBinder();
2021+
var moduleInputChannel = new QueueChannel();
2022+
ExtendedConsumerProperties<KafkaConsumerProperties> consumerProperties = createConsumerProperties();
2023+
consumerProperties.getExtension().setAckCount(10);
2024+
consumerProperties.getExtension().setAckTime(Duration.ofSeconds(5));
2025+
2026+
Binding<MessageChannel> consumerBinding = binder.bindConsumer(
2027+
"testBindingAckCountAndAckTime" + UUID.randomUUID(), "test", moduleInputChannel,
2028+
consumerProperties);
2029+
try {
2030+
AbstractMessageListenerContainer<?, ?> container = TestUtils.getPropertyValue(
2031+
consumerBinding, "lifecycle.messageListenerContainer",
2032+
AbstractMessageListenerContainer.class);
2033+
assertThat(container.getContainerProperties().getAckCount()).isEqualTo(10);
2034+
assertThat(container.getContainerProperties().getAckTime()).isEqualTo(5000L);
2035+
}
2036+
finally {
2037+
consumerBinding.unbind();
2038+
}
2039+
}
2040+
20182041
@Test
20192042
@Override
20202043
@SuppressWarnings("unchecked")

docs/modules/ROOT/pages/kafka/kafka-binder/config-options.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,16 @@ ackMode::
189189
Specify the container ack mode.
190190
This is based on the AckMode enumeration defined in Spring Kafka.
191191
If `ackEachRecord` property is set to `true` and consumer is not in batch mode, then this will use the ack mode of `RECORD`, otherwise, use the provided ack mode using this property.
192+
ackCount::
193+
The number of records that must be processed before pending offsets are committed when the `ackMode` is `COUNT` or `COUNT_TIME`.
194+
When set, this binding-level value takes precedence over the global `spring.kafka.listener.ackCount`.
195+
+
196+
Default: not set.
197+
ackTime::
198+
The time after which pending offsets are committed when the `ackMode` is `TIME` or `COUNT_TIME`.
199+
When set, this binding-level value takes precedence over the global `spring.kafka.listener.ackTime`.
200+
+
201+
Default: not set.
192202

193203
autoCommitOnError::
194204
In pollable consumers, if set to `true`, it always auto commits on error.

0 commit comments

Comments
 (0)