KAFKA-10: Fix for ConsumerConfiguration.executor

Fixes GH10 https://github.com/spring-projects/spring-integration-kafka/issues/10

Previously the `ConsumerConfiguration` used an internal `ExecutorService`.
In the management environment (App Server) it causes an issue with unmanaged Threads.

* Add `setExecutor` option and expose it to the Namespace to inject a managed `Executor`
* Stop an internal `ExecutorService` manually from the `ConsumerConfiguration.shutdown()`

Polishing
This commit is contained in:
Artem Bilan
2015-01-08 12:05:26 +02:00
committed by Artem Bilan
parent c3d696187c
commit 46d06fd404
4 changed files with 92 additions and 17 deletions

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -81,6 +81,8 @@ public class KafkaConsumerContextParser extends AbstractSingleBeanDefinitionPars
"key-decoder"); "key-decoder");
IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration, IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration,
"max-messages"); "max-messages");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(consumerConfigurationBuilder, consumerConfiguration,
"executor");
IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, parentElem, IntegrationNamespaceUtils.setValueIfAttributeDefined(consumerMetadataBuilder, parentElem,
"consumer-timeout"); "consumer-timeout");

View File

@@ -1,42 +1,66 @@
/* /*
* Copyright 2002-2013 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License"); * Copyright 2002-2015 the original author or authors. Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy of the License at * you may not use this file except in compliance with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and limitations under the * either express or implied. See the License for the specific language governing permissions and limitations under the
* License. * License.
*/ */
package org.springframework.integration.kafka.support; package org.springframework.integration.kafka.support;
import java.util.*; import java.util.ArrayList;
import java.util.concurrent.*; import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.MessagingException;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.Assert;
import kafka.consumer.ConsumerTimeoutException; import kafka.consumer.ConsumerTimeoutException;
import kafka.consumer.KafkaStream; import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector; import kafka.javaapi.consumer.ConsumerConnector;
import kafka.message.MessageAndMetadata; import kafka.message.MessageAndMetadata;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.messaging.MessagingException;
/** /**
* @author Soby Chacko * @author Soby Chacko
* @author Rajasekar Elango * @author Rajasekar Elango
* @author Artem Bilan
* @since 0.5 * @since 0.5
*/ */
public class ConsumerConfiguration<K, V> { public class ConsumerConfiguration<K, V> {
private static final Log LOGGER = LogFactory.getLog(ConsumerConfiguration.class); private static final Log LOGGER = LogFactory.getLog(ConsumerConfiguration.class);
private final ConsumerMetadata<K, V> consumerMetadata; private final ConsumerMetadata<K, V> consumerMetadata;
private final ConsumerConnectionProvider consumerConnectionProvider; private final ConsumerConnectionProvider consumerConnectionProvider;
private final MessageLeftOverTracker<K, V> messageLeftOverTracker; private final MessageLeftOverTracker<K, V> messageLeftOverTracker;
private ConsumerConnector consumerConnector; private ConsumerConnector consumerConnector;
private volatile int count = 0; private volatile int count = 0;
private int maxMessages = 1; private int maxMessages = 1;
private Collection<List<KafkaStream<K, V>>> consumerMessageStreams; private Collection<List<KafkaStream<K, V>>> consumerMessageStreams;
private final ExecutorService executorService = Executors.newCachedThreadPool(); private ExecutorService executorService = Executors.newCachedThreadPool();
private boolean executorExplicitlySet;
private volatile boolean stopped;
public ConsumerConfiguration(final ConsumerMetadata<K, V> consumerMetadata, public ConsumerConfiguration(final ConsumerMetadata<K, V> consumerMetadata,
final ConsumerConnectionProvider consumerConnectionProvider, final ConsumerConnectionProvider consumerConnectionProvider,
@@ -46,6 +70,19 @@ public class ConsumerConfiguration<K, V> {
this.messageLeftOverTracker = messageLeftOverTracker; this.messageLeftOverTracker = messageLeftOverTracker;
} }
public void setExecutor(Executor executor) {
boolean isExecutorService = executor instanceof ExecutorService;
boolean isThreadPoolTaskExecutor = executor instanceof ThreadPoolTaskExecutor;
Assert.isTrue(isExecutorService || isThreadPoolTaskExecutor);
if (isExecutorService) {
this.executorService = (ExecutorService) executor;
}
else {
this.executorService = ((ThreadPoolTaskExecutor) executor).getThreadPoolExecutor();
}
this.executorExplicitlySet = true;
}
public ConsumerMetadata<K, V> getConsumerMetadata() { public ConsumerMetadata<K, V> getConsumerMetadata() {
return consumerMetadata; return consumerMetadata;
} }
@@ -75,7 +112,8 @@ public class ConsumerConfiguration<K, V> {
} }
} }
} }
} catch (ConsumerTimeoutException cte) { }
catch (ConsumerTimeoutException cte) {
LOGGER.debug("Consumer timed out"); LOGGER.debug("Consumer timed out");
} }
return rawMessages; return rawMessages;
@@ -93,7 +131,7 @@ public class ConsumerConfiguration<K, V> {
messages.putAll(getLeftOverMessageMap()); messages.putAll(getLeftOverMessageMap());
try { try {
for (final Future<List<MessageAndMetadata<K, V>>> result : executorService.invokeAll(tasks)) { for (final Future<List<MessageAndMetadata<K, V>>> result : this.executorService.invokeAll(tasks)) {
if (!result.get().isEmpty()) { if (!result.get().isEmpty()) {
final String topic = result.get().get(0).topic(); final String topic = result.get().get(0).topic();
if (!messages.containsKey(topic)) { if (!messages.containsKey(topic)) {
@@ -106,8 +144,14 @@ public class ConsumerConfiguration<K, V> {
} }
} }
} }
} catch (Exception e) { }
throw new MessagingException("Consuming from Kafka failed", e); catch (Exception e) {
if (!this.stopped) {
throw new MessagingException("Consuming from Kafka failed", e);
}
else {
LOGGER.warn("Consuming from Kafka failed", e);
}
} }
if (messages.isEmpty()) { if (messages.isEmpty()) {
@@ -221,4 +265,13 @@ public class ConsumerConfiguration<K, V> {
} }
return consumerConnector; return consumerConnector;
} }
public void shutdown() {
this.stopped = true;
if (!this.executorExplicitlySet) {
this.executorService.shutdownNow();
}
getConsumerConnector().shutdown();
}
} }

View File

@@ -82,7 +82,7 @@ public class KafkaConsumerContext<K, V> implements DisposableBean {
@Override @Override
public void destroy() throws Exception { public void destroy() throws Exception {
for (ConsumerConfiguration<K, V> config : this.consumerConfigurations.values()) { for (ConsumerConfiguration<K, V> config : this.consumerConfigurations.values()) {
config.getConsumerConnector().shutdown(); config.shutdown();
} }
} }

View File

@@ -311,6 +311,21 @@
</xsd:appinfo> </xsd:appinfo>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="executor" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
A java.util.concurrent.Executor bean reference.
Typically
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor.
Used to execute iterator task on KafkaStreams.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.concurrent.Executor"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType> </xsd:complexType>
</xsd:element> </xsd:element>
</xsd:choice> </xsd:choice>
@@ -328,9 +343,14 @@
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="zookeeper-connect" use="required" type="xsd:string"> <xsd:attribute name="zookeeper-connect" use="required" type="xsd:string">
<xsd:annotation> <xsd:annotation>
<xsd:documentation> <xsd:appinfo>
Kafka Server Bean Name <xsd:documentation>
</xsd:documentation> Kafka Server Bean Name
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.kafka.support.ZookeeperConnect"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation> </xsd:annotation>
</xsd:attribute> </xsd:attribute>
<xsd:attribute name="consumer-properties" use="optional" type="xsd:string"> <xsd:attribute name="consumer-properties" use="optional" type="xsd:string">