mirror of https://github.com/apache/nifi.git
NIFI-4656, NIFI-4680: This closes #2330. Fix error handling in consume/publish kafka processors. Address issue with HortonworksSchemaRegistry throwing RuntimeException when it should be IOException. Fixed bug in ConsumeerLease/ConsumKafkaRecord that caused it to report too many records received
Signed-off-by: joewitt <joewitt@apache.org>
This commit is contained in:
parent
1fc1d38fd8
commit
c138987bb4
|
@ -519,6 +519,12 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
|
|
||||||
try {
|
try {
|
||||||
reader = readerFactory.createRecordReader(attributes, in, logger);
|
reader = readerFactory.createRecordReader(attributes, in, logger);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
yield();
|
||||||
|
rollback(topicPartition);
|
||||||
|
handleParseFailure(consumerRecord, session, e, "Failed to parse message from Kafka due to comms failure. Will roll back session and try again momentarily.");
|
||||||
|
closeWriter(writer);
|
||||||
|
return;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
handleParseFailure(consumerRecord, session, e);
|
handleParseFailure(consumerRecord, session, e);
|
||||||
continue;
|
continue;
|
||||||
|
@ -543,13 +549,9 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
logger.error("Failed to obtain Schema for FlowFile. Will roll back the Kafka message offsets.", e);
|
logger.error("Failed to obtain Schema for FlowFile. Will roll back the Kafka message offsets.", e);
|
||||||
|
|
||||||
try {
|
|
||||||
rollback(topicPartition);
|
rollback(topicPartition);
|
||||||
} catch (final Exception rollbackException) {
|
|
||||||
logger.warn("Attempted to rollback Kafka message offset but was unable to do so", rollbackException);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
throw new ProcessException(e);
|
throw new ProcessException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,13 +574,21 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
}
|
}
|
||||||
|
|
||||||
tracker.incrementRecordCount(1L);
|
tracker.incrementRecordCount(1L);
|
||||||
session.adjustCounter("Records Received", records.size(), false);
|
session.adjustCounter("Records Received", 1L, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
logger.error("Failed to properly receive messages from Kafka. Will roll back session and any un-committed offsets from Kafka.", e);
|
logger.error("Failed to properly receive messages from Kafka. Will roll back session and any un-committed offsets from Kafka.", e);
|
||||||
|
|
||||||
|
closeWriter(writer);
|
||||||
|
rollback(topicPartition);
|
||||||
|
|
||||||
|
throw new ProcessException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeWriter(final RecordSetWriter writer) {
|
||||||
try {
|
try {
|
||||||
if (writer != null) {
|
if (writer != null) {
|
||||||
writer.close();
|
writer.close();
|
||||||
|
@ -586,26 +596,20 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
} catch (final Exception ioe) {
|
} catch (final Exception ioe) {
|
||||||
logger.warn("Failed to close Record Writer", ioe);
|
logger.warn("Failed to close Record Writer", ioe);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
rollback(topicPartition);
|
|
||||||
} catch (final Exception rollbackException) {
|
|
||||||
logger.warn("Attempted to rollback Kafka message offset but was unable to do so", rollbackException);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ProcessException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void rollback(final TopicPartition topicPartition) {
|
private void rollback(final TopicPartition topicPartition) {
|
||||||
|
try {
|
||||||
OffsetAndMetadata offsetAndMetadata = uncommittedOffsetsMap.get(topicPartition);
|
OffsetAndMetadata offsetAndMetadata = uncommittedOffsetsMap.get(topicPartition);
|
||||||
if (offsetAndMetadata == null) {
|
if (offsetAndMetadata == null) {
|
||||||
offsetAndMetadata = kafkaConsumer.committed(topicPartition);
|
offsetAndMetadata = kafkaConsumer.committed(topicPartition);
|
||||||
}
|
}
|
||||||
|
|
||||||
final long offset = offsetAndMetadata.offset();
|
final long offset = offsetAndMetadata == null ? 0L : offsetAndMetadata.offset();
|
||||||
kafkaConsumer.seek(topicPartition, offset);
|
kafkaConsumer.seek(topicPartition, offset);
|
||||||
|
} catch (final Exception rollbackException) {
|
||||||
|
logger.warn("Attempted to rollback Kafka message offset but was unable to do so", rollbackException);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -378,7 +379,10 @@ public class PublishKafkaRecord_0_11 extends AbstractProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send each FlowFile to Kafka asynchronously.
|
// Send each FlowFile to Kafka asynchronously.
|
||||||
for (final FlowFile flowFile : flowFiles) {
|
final Iterator<FlowFile> itr = flowFiles.iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
final FlowFile flowFile = itr.next();
|
||||||
|
|
||||||
if (!isScheduled()) {
|
if (!isScheduled()) {
|
||||||
// If stopped, re-queue FlowFile instead of sending it
|
// If stopped, re-queue FlowFile instead of sending it
|
||||||
if (useTransactions) {
|
if (useTransactions) {
|
||||||
|
@ -388,6 +392,7 @@ public class PublishKafkaRecord_0_11 extends AbstractProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.transfer(flowFile);
|
session.transfer(flowFile);
|
||||||
|
itr.remove();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -519,6 +519,12 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
|
|
||||||
try {
|
try {
|
||||||
reader = readerFactory.createRecordReader(attributes, in, logger);
|
reader = readerFactory.createRecordReader(attributes, in, logger);
|
||||||
|
} catch (final IOException e) {
|
||||||
|
yield();
|
||||||
|
rollback(topicPartition);
|
||||||
|
handleParseFailure(consumerRecord, session, e, "Failed to parse message from Kafka due to comms failure. Will roll back session and try again momentarily.");
|
||||||
|
closeWriter(writer);
|
||||||
|
return;
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
handleParseFailure(consumerRecord, session, e);
|
handleParseFailure(consumerRecord, session, e);
|
||||||
continue;
|
continue;
|
||||||
|
@ -543,13 +549,9 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
logger.error("Failed to obtain Schema for FlowFile. Will roll back the Kafka message offsets.", e);
|
logger.error("Failed to obtain Schema for FlowFile. Will roll back the Kafka message offsets.", e);
|
||||||
|
|
||||||
try {
|
|
||||||
rollback(topicPartition);
|
rollback(topicPartition);
|
||||||
} catch (final Exception rollbackException) {
|
|
||||||
logger.warn("Attempted to rollback Kafka message offset but was unable to do so", rollbackException);
|
|
||||||
}
|
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
|
|
||||||
throw new ProcessException(e);
|
throw new ProcessException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,13 +574,21 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
}
|
}
|
||||||
|
|
||||||
tracker.incrementRecordCount(1L);
|
tracker.incrementRecordCount(1L);
|
||||||
session.adjustCounter("Records Received", records.size(), false);
|
session.adjustCounter("Records Received", 1L, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
logger.error("Failed to properly receive messages from Kafka. Will roll back session and any un-committed offsets from Kafka.", e);
|
logger.error("Failed to properly receive messages from Kafka. Will roll back session and any un-committed offsets from Kafka.", e);
|
||||||
|
|
||||||
|
closeWriter(writer);
|
||||||
|
rollback(topicPartition);
|
||||||
|
|
||||||
|
throw new ProcessException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeWriter(final RecordSetWriter writer) {
|
||||||
try {
|
try {
|
||||||
if (writer != null) {
|
if (writer != null) {
|
||||||
writer.close();
|
writer.close();
|
||||||
|
@ -586,26 +596,20 @@ public abstract class ConsumerLease implements Closeable, ConsumerRebalanceListe
|
||||||
} catch (final Exception ioe) {
|
} catch (final Exception ioe) {
|
||||||
logger.warn("Failed to close Record Writer", ioe);
|
logger.warn("Failed to close Record Writer", ioe);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
rollback(topicPartition);
|
|
||||||
} catch (final Exception rollbackException) {
|
|
||||||
logger.warn("Attempted to rollback Kafka message offset but was unable to do so", rollbackException);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ProcessException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void rollback(final TopicPartition topicPartition) {
|
private void rollback(final TopicPartition topicPartition) {
|
||||||
|
try {
|
||||||
OffsetAndMetadata offsetAndMetadata = uncommittedOffsetsMap.get(topicPartition);
|
OffsetAndMetadata offsetAndMetadata = uncommittedOffsetsMap.get(topicPartition);
|
||||||
if (offsetAndMetadata == null) {
|
if (offsetAndMetadata == null) {
|
||||||
offsetAndMetadata = kafkaConsumer.committed(topicPartition);
|
offsetAndMetadata = kafkaConsumer.committed(topicPartition);
|
||||||
}
|
}
|
||||||
|
|
||||||
final long offset = offsetAndMetadata.offset();
|
final long offset = offsetAndMetadata == null ? 0L : offsetAndMetadata.offset();
|
||||||
kafkaConsumer.seek(topicPartition, offset);
|
kafkaConsumer.seek(topicPartition, offset);
|
||||||
|
} catch (final Exception rollbackException) {
|
||||||
|
logger.warn("Attempted to rollback Kafka message offset but was unable to do so", rollbackException);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -378,7 +379,10 @@ public class PublishKafkaRecord_1_0 extends AbstractProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send each FlowFile to Kafka asynchronously.
|
// Send each FlowFile to Kafka asynchronously.
|
||||||
for (final FlowFile flowFile : flowFiles) {
|
final Iterator<FlowFile> itr = flowFiles.iterator();
|
||||||
|
while (itr.hasNext()) {
|
||||||
|
final FlowFile flowFile = itr.next();
|
||||||
|
|
||||||
if (!isScheduled()) {
|
if (!isScheduled()) {
|
||||||
// If stopped, re-queue FlowFile instead of sending it
|
// If stopped, re-queue FlowFile instead of sending it
|
||||||
if (useTransactions) {
|
if (useTransactions) {
|
||||||
|
@ -388,6 +392,7 @@ public class PublishKafkaRecord_1_0 extends AbstractProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
session.transfer(flowFile);
|
session.transfer(flowFile);
|
||||||
|
itr.remove();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.apache.nifi.schemaregistry.hortonworks;
|
package org.apache.nifi.schemaregistry.hortonworks;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -224,23 +225,33 @@ public class HortonworksSchemaRegistry extends AbstractControllerService impleme
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecordSchema retrieveSchema(final String schemaName) throws org.apache.nifi.schema.access.SchemaNotFoundException {
|
public RecordSchema retrieveSchema(final String schemaName) throws org.apache.nifi.schema.access.SchemaNotFoundException, IOException {
|
||||||
final SchemaRegistryClient client = getClient();
|
final SchemaRegistryClient client = getClient();
|
||||||
|
|
||||||
|
final SchemaVersionInfo versionInfo;
|
||||||
|
final Long schemaId;
|
||||||
|
final Integer version;
|
||||||
|
|
||||||
|
try {
|
||||||
final SchemaMetadataInfo metadataInfo = client.getSchemaMetadataInfo(schemaName);
|
final SchemaMetadataInfo metadataInfo = client.getSchemaMetadataInfo(schemaName);
|
||||||
if (metadataInfo == null) {
|
if (metadataInfo == null) {
|
||||||
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with name '" + schemaName + "'");
|
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with name '" + schemaName + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
final Long schemaId = metadataInfo.getId();
|
schemaId = metadataInfo.getId();
|
||||||
if (schemaId == null) {
|
if (schemaId == null) {
|
||||||
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with name '" + schemaName + "'");
|
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with name '" + schemaName + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
final SchemaVersionInfo versionInfo = getLatestSchemaVersionInfo(client, schemaName);
|
versionInfo = getLatestSchemaVersionInfo(client, schemaName);
|
||||||
final Integer version = versionInfo.getVersion();
|
version = versionInfo.getVersion();
|
||||||
if (version == null) {
|
if (version == null) {
|
||||||
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with name '" + schemaName + "'");
|
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with name '" + schemaName + "'");
|
||||||
}
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
handleException("Failed to retrieve schema with name '" + schemaName + "'", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final String schemaText = versionInfo.getSchemaText();
|
final String schemaText = versionInfo.getSchemaText();
|
||||||
final SchemaIdentifier schemaIdentifier = (schemaId == null || version == null) ? SchemaIdentifier.ofName(schemaName) : SchemaIdentifier.of(schemaName, schemaId, version);
|
final SchemaIdentifier schemaIdentifier = (schemaId == null || version == null) ? SchemaIdentifier.ofName(schemaName) : SchemaIdentifier.of(schemaName, schemaId, version);
|
||||||
|
@ -254,8 +265,10 @@ public class HortonworksSchemaRegistry extends AbstractControllerService impleme
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String retrieveSchemaText(final long schemaId, final int version) throws org.apache.nifi.schema.access.SchemaNotFoundException {
|
public String retrieveSchemaText(final long schemaId, final int version) throws org.apache.nifi.schema.access.SchemaNotFoundException, IOException {
|
||||||
final SchemaRegistryClient client = getClient();
|
final SchemaRegistryClient client = getClient();
|
||||||
|
|
||||||
|
try {
|
||||||
final SchemaMetadataInfo info = client.getSchemaMetadataInfo(schemaId);
|
final SchemaMetadataInfo info = client.getSchemaMetadataInfo(schemaId);
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with ID '" + schemaId + "' and version '" + version + "'");
|
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with ID '" + schemaId + "' and version '" + version + "'");
|
||||||
|
@ -271,24 +284,36 @@ public class HortonworksSchemaRegistry extends AbstractControllerService impleme
|
||||||
}
|
}
|
||||||
|
|
||||||
return versionInfo.getSchemaText();
|
return versionInfo.getSchemaText();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
handleException("Failed to retrieve schema with ID '" + schemaId + "' and version '" + version + "'", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RecordSchema retrieveSchema(final long schemaId, final int version) throws org.apache.nifi.schema.access.SchemaNotFoundException {
|
public RecordSchema retrieveSchema(final long schemaId, final int version) throws org.apache.nifi.schema.access.SchemaNotFoundException, IOException {
|
||||||
final SchemaRegistryClient client = getClient();
|
final SchemaRegistryClient client = getClient();
|
||||||
|
|
||||||
|
final String schemaName;
|
||||||
|
final SchemaVersionInfo versionInfo;
|
||||||
|
try {
|
||||||
final SchemaMetadataInfo info = client.getSchemaMetadataInfo(schemaId);
|
final SchemaMetadataInfo info = client.getSchemaMetadataInfo(schemaId);
|
||||||
if (info == null) {
|
if (info == null) {
|
||||||
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with ID '" + schemaId + "' and version '" + version + "'");
|
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with ID '" + schemaId + "' and version '" + version + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
final SchemaMetadata metadata = info.getSchemaMetadata();
|
final SchemaMetadata metadata = info.getSchemaMetadata();
|
||||||
final String schemaName = metadata.getName();
|
schemaName = metadata.getName();
|
||||||
|
|
||||||
final SchemaVersionKey schemaVersionKey = new SchemaVersionKey(schemaName, version);
|
final SchemaVersionKey schemaVersionKey = new SchemaVersionKey(schemaName, version);
|
||||||
final SchemaVersionInfo versionInfo = getSchemaVersionInfo(client, schemaVersionKey);
|
versionInfo = getSchemaVersionInfo(client, schemaVersionKey);
|
||||||
if (versionInfo == null) {
|
if (versionInfo == null) {
|
||||||
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with ID '" + schemaId + "' and version '" + version + "'");
|
throw new org.apache.nifi.schema.access.SchemaNotFoundException("Could not find schema with ID '" + schemaId + "' and version '" + version + "'");
|
||||||
}
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
|
handleException("Failed to retrieve schema with ID '" + schemaId + "' and version '" + version + "'", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final String schemaText = versionInfo.getSchemaText();
|
final String schemaText = versionInfo.getSchemaText();
|
||||||
|
|
||||||
|
@ -300,6 +325,32 @@ public class HortonworksSchemaRegistry extends AbstractControllerService impleme
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The schema registry client wraps all IOExceptions in RuntimeException. So if an IOException occurs, we don't know
|
||||||
|
// that it was an IO problem. So we will look through the Exception's cause chain to see if there is an IOException present.
|
||||||
|
private void handleException(final String message, final Exception e) throws IOException, org.apache.nifi.schema.access.SchemaNotFoundException {
|
||||||
|
if (containsIOException(e)) {
|
||||||
|
throw new IOException(message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new org.apache.nifi.schema.access.SchemaNotFoundException(message, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean containsIOException(final Throwable t) {
|
||||||
|
if (t == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t instanceof IOException) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Throwable cause = t.getCause();
|
||||||
|
if (cause == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return containsIOException(cause);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<SchemaField> getSuppliedSchemaFields() {
|
public Set<SchemaField> getSuppliedSchemaFields() {
|
||||||
|
|
Loading…
Reference in New Issue