package com.zendesk.maxwell.producer; import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import com.google.common.collect.Iterables; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.zendesk.maxwell.MaxwellContext; import com.zendesk.maxwell.producer.partitioners.MaxwellKinesisPartitioner; import com.zendesk.maxwell.replication.BinlogPosition; import com.zendesk.maxwell.replication.Position; import com.zendesk.maxwell.row.RowMap; import com.amazonaws.services.kinesis.producer.Attempt; import com.amazonaws.services.kinesis.producer.KinesisProducer; import com.amazonaws.services.kinesis.producer.KinesisProducerConfiguration; import com.amazonaws.services.kinesis.producer.UserRecordFailedException; import com.amazonaws.services.kinesis.producer.UserRecordResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; class KinesisCallback implements FutureCallback<UserRecordResult> { public static final Logger logger = LoggerFactory.getLogger(KinesisCallback.class); private final AbstractAsyncProducer.CallbackCompleter cc; private final Position position; private final String json; private MaxwellContext context; private final String key; public KinesisCallback(AbstractAsyncProducer.CallbackCompleter cc, Position position, String key, String json, MaxwellContext context) { this.cc = cc; this.position = position; this.key = key; this.json = json; this.context = context; } @Override public void onFailure(Throwable t) { logger.error(t.getClass().getSimpleName() + " @ " + position + " -- " + key); logger.error(t.getLocalizedMessage()); if(t instanceof UserRecordFailedException) { Attempt last = Iterables.getLast(((UserRecordFailedException) t).getResult().getAttempts()); logger.error(String.format("Record failed to put - %s : %s", last.getErrorCode(), last.getErrorMessage())); } logger.error("Exception during put", t); if (!context.getConfig().ignoreProducerError) { context.terminate(new RuntimeException(t)); } else { cc.markCompleted(); } }; @Override public void onSuccess(UserRecordResult result) { if(logger.isDebugEnabled()) { logger.debug("-> key:" + key + ", shard id:" + result.getShardId() + ", sequence number:" + result.getSequenceNumber()); logger.debug(" " + json); logger.debug(" " + position); logger.debug(""); } cc.markCompleted(); }; } public class MaxwellKinesisProducer extends AbstractAsyncProducer { private static final Logger logger = LoggerFactory.getLogger(MaxwellKinesisProducer.class); private final MaxwellKinesisPartitioner partitioner; private final KinesisProducer kinesisProducer; private final String kinesisStream; public MaxwellKinesisProducer(MaxwellContext context, String kinesisStream) { super(context); String partitionKey = context.getConfig().producerPartitionKey; String partitionColumns = context.getConfig().producerPartitionColumns; String partitionFallback = context.getConfig().producerPartitionFallback; boolean kinesisMd5Keys = context.getConfig().kinesisMd5Keys; this.partitioner = new MaxwellKinesisPartitioner(partitionKey, partitionColumns, partitionFallback, kinesisMd5Keys); this.kinesisStream = kinesisStream; Path path = Paths.get("kinesis-producer-library.properties"); if(Files.exists(path) && Files.isRegularFile(path)) { KinesisProducerConfiguration config = KinesisProducerConfiguration.fromPropertiesFile(path.toString()); this.kinesisProducer = new KinesisProducer(config); } else { this.kinesisProducer = new KinesisProducer(); } } @Override public void sendAsync(RowMap r, AbstractAsyncProducer.CallbackCompleter cc) throws Exception { String key = this.partitioner.getKinesisKey(r); String value = r.toJSON(outputConfig); ByteBuffer encodedValue = ByteBuffer.wrap(value.getBytes("UTF-8")); ListenableFuture<UserRecordResult> future = kinesisProducer.addUserRecord(kinesisStream, key, encodedValue); // release the reference to ease memory pressure if(!KinesisCallback.logger.isDebugEnabled()) { value = null; } FutureCallback<UserRecordResult> callback = new KinesisCallback(cc, r.getPosition(), key, value, this.context); Futures.addCallback(future, callback); } }