package com.amazonaws.services.kinesis.producer.demo; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.stream.Collectors; import java.util.stream.IntStream; import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.kinesis.model.PutRecordsRequest; import com.amazonaws.services.kinesis.model.PutRecordsRequestEntry; import com.amazonaws.services.kinesis.model.PutRecordsResult; import com.amazonaws.services.kinesis.model.PutRecordsResultEntry; import com.google.common.collect.ImmutableSet; public class RetryingBatchedClickEventsToKinesis extends BatchedClickEventsToKinesis { private final int MAX_BACKOFF = 30000; private final int MAX_ATTEMPTS = 5; private final Set<String> RETRYABLE_ERR_CODES = ImmutableSet.of( "ProvisionedThroughputExceededException", "InternalFailure", "ServiceUnavailable"); private int backoff; private int attempt; private Map<PutRecordsRequestEntry, Integer> recordAttempts; public RetryingBatchedClickEventsToKinesis( BlockingQueue<ClickEvent> inputQueue) { super(inputQueue); reset(); recordAttempts = new HashMap<>(); } @Override protected void flush() { PutRecordsRequest req = new PutRecordsRequest() .withStreamName(STREAM_NAME) .withRecords(entries); PutRecordsResult res = null; try { res = kinesis.putRecords(req); reset(); } catch (AmazonClientException e) { if ((e instanceof AmazonServiceException && ((AmazonServiceException) e).getStatusCode() / 100 == 4) || attempt == MAX_ATTEMPTS) { reset(); throw e; } try { Thread.sleep(backoff); } catch (InterruptedException ie) { ie.printStackTrace(); } Math.min(MAX_BACKOFF, backoff *= 2); attempt++; flush(); } final List<PutRecordsResultEntry> results = res.getRecords(); List<PutRecordsRequestEntry> retries = IntStream .range(0, results.size()) .mapToObj(i -> { PutRecordsRequestEntry e = entries.get(i); String errorCode = results.get(i).getErrorCode(); int n = recordAttempts.getOrDefault(e, 1) + 1; // Determine whether the record should be retried if (errorCode != null && RETRYABLE_ERR_CODES.contains(errorCode) && n < MAX_ATTEMPTS) { recordAttempts.put(e, n); return Optional.of(e); } else { recordAttempts.remove(e); return Optional.<PutRecordsRequestEntry>empty(); } }) .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); entries.clear(); retries.forEach(e -> addEntry(e)); } private void reset() { attempt = 1; backoff = 100; } }