Spring Cloud Stream Kafka batch - Manual Commit Entire batch - spring-kafka

We are using spring cloud streams Hoxton.SR4 to consume messages from Kafka topic. We've enabled spring.cloud.stream.bindings..consumer.batch-mode=true, fetching 2000 records per poll. I would like to know if there is a way we can manually acknowledge/commit entire batch.

SR4 is quite old; the current Hoxton release is SR9 and the current spring cloud stream version is 3.0.10.RELEASE (Hoxton.SR9 pulls in 3.0.9).
You need to consume a Message and get the acknowledgment from a header.
#SpringBootApplication
public class So652289261Application {
public static void main(String[] args) {
SpringApplication.run(So652289261Application.class, args);
}
#Bean
Consumer<Message<List<Foo>>> consume() {
return msg -> {
System.out.println(msg.getPayload());
msg.getHeaders().get(KafkaHeaders.ACKNOWLEDGMENT, Acknowledgment.class).acknowledge();
};
}
#Bean
public ListenerContainerCustomizer<AbstractMessageListenerContainer<?, ?>> customizer() {
return (container, dest, group) -> container.getContainerProperties()
.setCommitLogLevel(LogIfLevelEnabled.Level.INFO);
}
#Bean
public ApplicationRunner runner(KafkaTemplate<byte[], byte[]> template) {
return args -> {
template.send("consume-in-0", "{\"bar\":\"baz\"}".getBytes());
template.send("consume-in-0", "{\"bar\":\"qux\"}".getBytes());
};
}
public static class Foo {
private String bar;
public Foo() {
}
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
#Override
public String toString() {
return "Foo [bar=" + this.bar + "]";
}
}
}
Properties for Boot 2.3.6 and Cloud Hoxton.SR9
spring.cloud.stream.bindings.consume-in-0.group=so65228926
spring.cloud.stream.bindings.consume-in-0.consumer.batch-mode=true
spring.cloud.stream.kafka.bindings.consume-in-0.consumer.auto-commit-offset=false
spring.kafka.producer.properties.linger.ms=50
Properties for Boot 2.4.0 and Cloud 2020.0.0-M6
spring.cloud.stream.bindings.consume-in-0.group=so65228926
spring.cloud.stream.bindings.consume-in-0.consumer.batch-mode=true
spring.cloud.stream.kafka.bindings.consume-in-0.consumer.ack-mode=MANUAL
spring.kafka.producer.properties.linger.ms=50
[Foo [bar=baz], Foo [bar=qux]]
... Committing: {consume-in-0-0=OffsetAndMetadata{offset=14, leaderEpoch=null, metadata=''}}

Related

Issue with Record Filter Strategy(Spring boot : 2.3.8). Filtered messages are coming again and again to the filter

I am working on the spring kafka batch listener filter strategy. I am facing an issue that, the filtered events are coming again and again. could any one help me on this issue ? spring boot with kafka version(2.3.8)
Here is my configuration:
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new
ConcurrentKafkaListenerContainerFactory<>();
configurer.configure(factory, kafkaConsumerFactory);
factory.setBatchListener(true);
factory.setAckDiscarded(true);
factory.getContainerProperties().setIdleBetweenPolls(30000);
factory.setRecordFilterStrategy(
(consumerRecord) -> {
MyObject myObject = new ObjectMapper().readValue(consumerRecord.value(), MyObj.class);
if (myObject.frequency > 10) {
return false;
} else {
return true;
}});
factory.setBatchErrorHandler(new SeekToCurrentBatchErrorHandler());
When using batch mode with MANUAL acks, if you filter all the records (discard them all), the listener will get an empty list so you can still acknowledge the batch to commit the offsets.
I just tested it and it works as expected.
#SpringBootApplication
public class So67259790Application {
public static void main(String[] args) {
SpringApplication.run(So67259790Application.class, args);
}
#KafkaListener(id = "so67259790", topics = "so67259790")
public void listen(List<String> in, Acknowledgment ack) {
System.out.println(in);
ack.acknowledge();
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so67259790").partitions(1).replicas(1).build();
}
#Bean
public ApplicationRunner runner(KafkaTemplate<String, String> template) {
return args -> {
template.send("so67259790", "foo");
template.send("so67259790", "bar");
};
}
#Bean
public RecordFilterStrategy<Object, Object> rfs() {
return rec -> true;
}
}

Firebase ApiFuture to CompletableFuture in a reactive stack

I am using the Firebase Admin Java SDK in a server side Spring WebFlux environment. The Firebase SDK provides two methods for each operation. A synchronous ".doOperation()" method which returns a result and a ".doOperationAsync()" method which returns an instance of ApiFuture.
The Javadoc for ApiFuture can be found here.
ApiFuture extends from java.util.concurrent.Future, which is no longer supported by Mono.fromFuture().
Is there a way to convert Googles ApiFuture into a Mono?
It's possible to convert ApiFuture to CompletableFuture, which can be used with Mono.fromFuture:
internal class ApiCompletableFuture<V>(
private val future: ApiFuture<V>,
executor: Executor = directExecutor(),
) : CompletableFuture<V>(), ApiFutureCallback<V> {
init {
ApiFutures.addCallback(future, this, executor)
}
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
future.cancel(mayInterruptIfRunning)
return super.cancel(mayInterruptIfRunning)
}
override fun onSuccess(result: V) {
complete(result)
}
override fun onFailure(t: Throwable) {
completeExceptionally(t)
}
}
Some utility methods for you
public static <T> CompletableFuture<T> toCompletableFuture(ApiFuture<T> apiFuture) {
final CompletableFuture<T> cf = new CompletableFuture<>();
ApiFutures.addCallback(apiFuture,
new ApiFutureCallback<T>() {
#Override
public void onFailure(Throwable t) {
cf.completeExceptionally(t);
}
#Override
public void onSuccess(T result) {
cf.complete(result);
}
},
MoreExecutors.directExecutor());
return cf;
}
public static <T> Mono<T> toMono(ApiFuture<T> apiFuture) {
return Mono.create(sink -> ApiFutures.addCallback(apiFuture,
new ApiFutureCallback<T>() {
#Override
public void onFailure(Throwable t) {
sink.error(t);
}
#Override
public void onSuccess(T result) {
sink.success(result);
}
},
MoreExecutors.directExecutor()));
}

How to bind a Store using Spring Cloud Stream and Kafka?

I'd like to use a Kafka state store of type KeyValueStore in a sample application using the Kafka Binder of Spring Cloud Stream.
According to the documentation, it should be pretty simple.
This is my main class:
#SpringBootApplication
public class KafkaStreamTestApplication {
public static void main(String[] args) {
SpringApplication.run(KafkaStreamTestApplication.class, args);
}
#Bean
public BiFunction<KStream<String, String>, KeyValueStore<String,String>, KStream<String, String>> process(){
return (input,store) -> input.mapValues(v -> v.toUpperCase());
}
#Bean
public StoreBuilder myStore() {
return Stores.keyValueStoreBuilder(
Stores.persistentKeyValueStore("my-store"), Serdes.String(),
Serdes.String());
}
}
I suppose that the KeyValueStore should be passed as the second parameter of the "process" method, but the application fails to start with the message below:
Caused by: java.lang.IllegalStateException: No factory found for binding target type: org.apache.kafka.streams.state.KeyValueStore among registered factories: channelFactory,messageSourceFactory,kStreamBoundElementFactory,kTableBoundElementFactory,globalKTableBoundElementFactory
at org.springframework.cloud.stream.binding.AbstractBindableProxyFactory.getBindingTargetFactory(AbstractBindableProxyFactory.java:82) ~[spring-cloud-stream-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.cloud.stream.binder.kafka.streams.function.KafkaStreamsBindableProxyFactory.bindInput(KafkaStreamsBindableProxyFactory.java:191) ~[spring-cloud-stream-binder-kafka-streams-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.cloud.stream.binder.kafka.streams.function.KafkaStreamsBindableProxyFactory.afterPropertiesSet(KafkaStreamsBindableProxyFactory.java:103) ~[spring-cloud-stream-binder-kafka-streams-3.0.3.RELEASE.jar:3.0.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1792) ~[spring-beans-5.2.5.RELEASE.jar:5.2.5.RELEASE]
I found the solution about how to use a store reading an unit test in Spring Cloud Stream.
The code below is how I applied that solution to my code.
The transformer uses the Store provided by Spring bean method "myStore"
#SpringBootApplication
public class KafkaStreamTestApplication {
public static final String MY_STORE_NAME = "my-store";
public static void main(String[] args) {
SpringApplication.run(KafkaStreamTestApplication.class, args);
}
#Bean
public Function<KStream<String, String>, KStream<String, String>> process2(){
return (input) -> input.
transformValues(() -> new MyValueTransformer(), MY_STORE_NAME);
}
#Bean
public StoreBuilder<?> myStore() {
return Stores.keyValueStoreBuilder(
Stores.persistentKeyValueStore(MY_STORE_NAME), Serdes.String(),
Serdes.String());
}
}
public class MyValueTransformer implements ValueTransformer<String, String> {
private KeyValueStore<String,String> store;
private ProcessorContext context;
#Override
public void init(ProcessorContext context) {
this.context = context;
store = (KeyValueStore<String, String>) this.context.getStateStore(KafkaStreamTestApplication.MY_STORE_NAME);
}
#Override
public String transform(String value) {
String tValue = store.get(value);
if(tValue==null) {
store.put(value, value.toUpperCase());
}
return tValue;
}
#Override
public void close() {
if(store!=null) {
store.close();
}
}
}

SeekToCurrentErrorHandler: DeadLetterPublishingRecoverer is not handling deserialize errors

I am trying to write kafka consumer using spring-kafka version 2.3.0.M2 library.
To handle run time errors I am using SeekToCurrentErrorHandler.class with DeadLetterPublishingRecoverer as my recoverer. This works fine only when my consumer code throws exception, but fails when unable to deserialize the message.
I tried implementing ErrorHandler myself and I was successful but with this approach I myself end up writing DLT code to handle error messages which I do not want to do.
Below are my kafka properties
spring:
kafka:
consumer:
bootstrap-servers: localhost:9092
group-id: group_id
auto-offset-reset: latest
key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
properties:
spring.json.trusted.packages: com.mypackage
spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
spring.deserializer.value.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory(
ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
ConsumerFactory<Object, Object> kafkaConsumerFactory,
KafkaTemplate<Object, Object> template) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
configurer.configure(factory, kafkaConsumerFactory);
factory.setErrorHandler(new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template), maxFailures));}
It works fine for me (note that Boot will auto-configure the error handler)...
#SpringBootApplication
public class So56728833Application {
public static void main(String[] args) {
SpringApplication.run(So56728833Application.class, args);
}
#Bean
public SeekToCurrentErrorHandler errorHandler(KafkaTemplate<String, String> template) {
SeekToCurrentErrorHandler eh = new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template), 3);
eh.setClassifier( // retry for all except deserialization exceptions
new BinaryExceptionClassifier(Collections.singletonList(DeserializationException.class), false));
return eh;
}
#KafkaListener(id = "so56728833"
+ "", topics = "so56728833")
public void listen(Foo in) {
System.out.println(in);
if (in.getBar().equals("baz")) {
throw new IllegalStateException("Test retries");
}
}
#KafkaListener(id = "so56728833dlt", topics = "so56728833.DLT")
public void listenDLT(Object in) {
System.out.println("Received from DLT: " + (in instanceof byte[] ? new String((byte[]) in) : in));
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so56728833").partitions(1).replicas(1).build();
}
#Bean
public NewTopic dlt() {
return TopicBuilder.name("so56728833.DLT").partitions(1).replicas(1).build();
}
public static class Foo {
private String bar;
public Foo() {
super();
}
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
#Override
public String toString() {
return "Foo [bar=" + this.bar + "]";
}
}
}
spring:
kafka:
consumer:
auto-offset-reset: earliest
enable-auto-commit: false
key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
properties:
spring.json.trusted.packages: com.example
spring.deserializer.key.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
spring.json.value.default.type: com.example.So56728833Application$Foo
producer:
key-serializer: org.springframework.kafka.support.serializer.JsonSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
logging:
level:
org.springframework.kafka: trace
I have 3 records in the topic:
"badJSON"
"{\"bar\":\"baz\"}"
"{\"bar\":\"qux\"}"
I see the first one going directly to the DLT, and the second one goes there after 3 attempts.

Logging MDC with #Async and TaskDecorator

Using Spring MVC, I have the following setup:
An AbstractRequestLoggingFilter derived filter for logging requests.
A TaskDecorator to marshal the MDC context mapping from the web request thread to the #Async thread.
I'm attempting to collect context info using MDC (or a ThreadLocal object) for all components involved in handling the request.
I can correctly retrieve the MDC context info from the #Async thread. However, if the #Async thread were to add context info to the MDC, how can I now marshal the MDC context info to the thread that handles the response?
TaskDecorator
public class MdcTaskDecorator implements TaskDecorator {
#Override
public Runnable decorate(Runnable runnable) {
// Web thread context
// Get the logging MDC context
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
// #Async thread context
// Restore the web thread MDC context
if(contextMap != null) {
MDC.setContextMap(contextMap);
}
else {
MDC.clear();
}
// Run the new thread
runnable.run();
}
finally {
MDC.clear();
}
};
}
}
Async method
#Async
public CompletableFuture<String> doSomething_Async() {
MDC.put("doSomething", "started");
return doit();
}
Logging Filter
public class ServletLoggingFilter extends AbstractRequestLoggingFilter {
#Override
protected void beforeRequest(HttpServletRequest request, String message) {
MDC.put("webthread", Thread.currentThread().getName()); // Will be webthread-1
}
#Override
protected void afterRequest(HttpServletRequest request, String message) {
MDC.put("responsethread", Thread.currentThread().getName()); // Will be webthread-2
String s = MDC.get("doSomething"); // Will be null
// logthis();
}
}
I hope you have solved the problem, but if you did not, here comes a solution.
All you have to do can be summarized as following 2 simple steps:
Keep your class MdcTaskDecorator.
Extends AsyncConfigurerSupport for your main class and override getAsyncExecutor() to set decorator with your customized one as follows:
public class AsyncTaskDecoratorApplication extends AsyncConfigurerSupport {
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MdcTaskDecorator());
executor.initialize();
return executor;
}
public static void main(String[] args) {
SpringApplication.run(AsyncTaskdecoratorApplication.class, args);
}
}
Create a bean that will pass the MDC properties from parent thread to the successor thread.
#Configuration
#Slf4j
public class AsyncMDCConfiguration {
#Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MDCTaskDecorator());//MDCTaskDecorator i s a custom created class thet implements TaskDecorator that is reponsible for passing on the MDC properties
executor.initialize();
return executor;
}
}
#Slf4j
public class MDCTaskDecorator implements TaskDecorator {
#Override
public Runnable decorate(Runnable runnable) {
Map<String, String> contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
}
};
}
}
All Good now. Happy Coding
I have some solutions that roughly divided into Callable(for #Async), AsyncExecutionInterceptor(for #Async), CallableProcessingInterceptor(for controller).
1.The Callable solution for putting context infos into #Async thread:
The key is using the ContextAwarePoolExecutor to replace the default executor of #Async:
#Configuration
public class DemoExecutorConfig {
#Bean("demoExecutor")
public Executor contextAwarePoolExecutor() {
return new ContextAwarePoolExecutor();
}
}
And the ContextAwarePoolExecutor overwriting submit and submitListenable methods with ContextAwareCallable inside:
public class ContextAwarePoolExecutor extends ThreadPoolTaskExecutor {
private static final long serialVersionUID = 667815067287186086L;
#Override
public <T> Future<T> submit(Callable<T> task) {
return super.submit(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}
#Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
return super.submitListenable(new ContextAwareCallable<T>(task, newThreadContextContainer()));
}
/**
* set infos what we need
*/
private ThreadContextContainer newThreadContextContainer() {
ThreadContextContainer container = new ThreadContextContainer();
container.setRequestAttributes(RequestContextHolder.currentRequestAttributes());
container.setContextMapOfMDC(MDC.getCopyOfContextMap());
return container;
}
}
The ThreadContextContainer is just a pojo to store infos for convenience:
public class ThreadContextContainer implements Serializable {
private static final long serialVersionUID = -6809291915300091330L;
private RequestAttributes requestAttributes;
private Map<String, String> contextMapOfMDC;
public RequestAttributes getRequestAttributes() {
return requestAttributes;
}
public Map<String, String> getContextMapOfMDC() {
return contextMapOfMDC;
}
public void setRequestAttributes(RequestAttributes requestAttributes) {
this.requestAttributes = requestAttributes;
}
public void setContextMapOfMDC(Map<String, String> contextMapOfMDC) {
this.contextMapOfMDC = contextMapOfMDC;
}
}
The ContextAwareCallable(a Callable proxy for original task) overwriting the call method to storage MDC or other context infos before the original task executing its call method:
public class ContextAwareCallable<T> implements Callable<T> {
/**
* the original task
*/
private Callable<T> task;
/**
* for storing infos what we need
*/
private ThreadContextContainer threadContextContainer;
public ContextAwareCallable(Callable<T> task, ThreadContextContainer threadContextContainer) {
this.task = task;
this.threadContextContainer = threadContextContainer;
}
#Override
public T call() throws Exception {
// set infos
if (threadContextContainer != null) {
RequestAttributes requestAttributes = threadContextContainer.getRequestAttributes();
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes);
}
Map<String, String> contextMapOfMDC = threadContextContainer.getContextMapOfMDC();
if (contextMapOfMDC != null) {
MDC.setContextMap(contextMapOfMDC);
}
}
try {
// execute the original task
return task.call();
} finally {
// clear infos after task completed
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}
}
}
In the end, using the #Async with the configured bean "demoExecutor" like this: #Async("demoExecutor")
void yourTaskMethod();
2.In regard to your question of handling the response:
Regret to tell that I don't really have a verified solution. Maybe the org.springframework.aop.interceptor.AsyncExecutionInterceptor#invoke is possible to solve that.
And I do not think it has a solution to handle the response with your ServletLoggingFilter. Because the Async method will be returned instantly. The afterRequest method executes immediately and returns before Async method doing things. You won't get what you want unless you synchronously wait for the Async method to finish executing.
But if you just want to log something, you can add those codes into my example ContextAwareCallable after the original task executing its call method:
try {
// execute the original task
return task.call();
} finally {
String something = MDC.get("doSomething"); // will not be null
// logthis(something);
// clear infos after task completed
RequestContextHolder.resetRequestAttributes();
try {
MDC.clear();
} finally {
}
}

Resources