package com.amazonaws.services.kinesis; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.amazonaws.services.kinesis.clientlibrary.exceptions.InvalidStateException; import com.amazonaws.services.kinesis.clientlibrary.exceptions.ShutdownException; import com.amazonaws.services.kinesis.clientlibrary.exceptions.ThrottlingException; import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessor; import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorCheckpointer; import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorFactory; import com.amazonaws.services.kinesis.clientlibrary.types.ShutdownReason; import com.amazonaws.services.kinesis.model.Record; public abstract class ManagedClientProcessor implements IRecordProcessor { private static final Log LOG = LogFactory .getLog(ManagedClientProcessor.class); private final int NUM_RETRIES = 10; private final long BACKOFF_TIME_IN_MILLIS = 100L; private String kinesisShardId; /** * {@inheritDoc} */ @Override public void initialize(String shardId) { LOG.info("Initializing Managed Processor for Shard: " + shardId); this.kinesisShardId = shardId; } /** * {@inheritDoc} */ @Override public abstract void processRecords(List<Record> records, IRecordProcessorCheckpointer checkpointer); /** * The Copy method allows a subclass to implement a method which returns a * new version of the template processor, which takes care of making * variables threadsafe, etc * * @return * @throws Exception */ public abstract ManagedClientProcessor copy() throws Exception; /** * {@inheritDoc} */ @Override public void shutdown(IRecordProcessorCheckpointer checkpointer, ShutdownReason reason) { LOG.info("Shutting down record processor for shard: " + kinesisShardId); // Important to checkpoint after reaching end of shard, so we can start // processing data from child shards. if (reason == ShutdownReason.TERMINATE) { try { checkpoint(checkpointer); } catch (Exception e) { e.printStackTrace(); } } } /** * Checkpoint with retries. * * @param checkpointer */ protected void checkpoint(IRecordProcessorCheckpointer checkpointer) { LOG.info("Checkpointing shard " + kinesisShardId); for (int i = 0; i < NUM_RETRIES; i++) { try { checkpointer.checkpoint(); break; } catch (ShutdownException se) { // Ignore checkpoint if the processor instance has been shutdown // (fail over). LOG.info("Caught shutdown exception, skipping checkpoint.", se); break; } catch (ThrottlingException e) { // Backoff and re-attempt checkpoint upon transient failures if (i >= (NUM_RETRIES - 1)) { LOG.error("Checkpoint failed after " + (i + 1) + "attempts.", e); break; } else { LOG.info("Transient issue when checkpointing - attempt " + (i + 1) + " of " + NUM_RETRIES, e); } } catch (InvalidStateException e) { // This indicates an issue with the DynamoDB table (check for // table, provisioned IOPS). LOG.error( "Cannot save checkpoint to the DynamoDB table used by the KinesisClientLibrary.", e); break; } try { Thread.sleep(BACKOFF_TIME_IN_MILLIS); } catch (InterruptedException e) { LOG.debug("Interrupted sleep", e); } } } }