package com.neverwinterdp.kafka.producer;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.ByteArraySerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Tuan Nguyen
* @email tuan08@gmail.com
*/
public class AckKafkaWriter extends AbstractKafkaWriter {
static private Logger LOGGER = LoggerFactory.getLogger(AckKafkaWriter.class);
private Properties kafkaProperties;
private KafkaProducer<byte[], byte[]> producer;
private AtomicLong idTracker = new AtomicLong();
private WaittingAckProducerRecordHolder<byte[], byte[]> waittingAckBuffer = new WaittingAckProducerRecordHolder<byte[], byte[]>();
private ResendThread resendThread ;
public AckKafkaWriter(String name, String kafkaBrokerUrls) {
this(name, null, kafkaBrokerUrls);
}
public AckKafkaWriter(String name, Map<String, String> props, String kafkaBrokerUrls) {
super(name);
Properties kafkaProps = new Properties();
kafkaProps.put("bootstrap.servers", kafkaBrokerUrls);
kafkaProps.put("value.serializer", ByteArraySerializer.class.getName());
kafkaProps.put("key.serializer", ByteArraySerializer.class.getName());
if (props != null) {
kafkaProps.putAll(props);
}
this.kafkaProperties = kafkaProps;
reconnect();
}
public void reconnect() {
if (producer != null) producer.close();
producer = new KafkaProducer<byte[], byte[]>(kafkaProperties);
}
public void send(String topic, int partition, String key, String message, Callback callback, long timeout) throws Exception {
byte[] keyBytes = key.getBytes();
byte[] messageBytes = message.getBytes();
send(topic, partition, keyBytes, messageBytes, callback, timeout);
}
public void send(String topic, byte[] key, byte[] message, Callback callback, long timeout) throws Exception {
ProducerRecord<byte[], byte[]> record = new ProducerRecord<byte[], byte[]>(topic, key, message);
long id = idTracker.incrementAndGet();
WaittingAckProducerRecord<byte[], byte[]> ackRecord = new WaittingAckProducerRecord<byte[], byte[]>(id, record, callback);
waittingAckBuffer.add(ackRecord, timeout);
AckCallback ackCallback = new AckCallback(id);
producer.send(record, ackCallback);
}
public void send(String topic, int partition, byte[] key, byte[] message, Callback callback, long timeout) throws Exception {
ProducerRecord<byte[], byte[]> record = new ProducerRecord<byte[], byte[]>(topic, partition, key, message);
long id = idTracker.incrementAndGet();
WaittingAckProducerRecord<byte[], byte[]> ackRecord = new WaittingAckProducerRecord<byte[], byte[]>(id, record, callback);
waittingAckBuffer.add(ackRecord, timeout);
AckCallback ackCallback = new AckCallback(id);
producer.send(record, ackCallback);
}
void triggerResendThread() {
if(resendThread == null || !resendThread.isAlive()) {
resendThread = new ResendThread();
resendThread.start();
}
}
public void waitAndClose(long timeout) {
try {
int waitTime = 0;
while(waitTime < timeout && waittingAckBuffer.size() > 0) {
Thread.sleep(100);
waitTime += 100;
}
} catch (InterruptedException e) {
}
producer.close();
}
public void close() throws InterruptedException {
if(resendThread != null && resendThread.isAlive()) {
resendThread.waitForTermination(60000);
}
producer.close();
}
public class ResendThread extends Thread {
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
return;
}
List<WaittingAckProducerRecord<byte[],byte[]>> needToResendRecords = waittingAckBuffer.getNeedToResendRecords();
while(needToResendRecords.size() > 0) {
for(WaittingAckProducerRecord<byte[], byte[]> sel : needToResendRecords) {
AckCallback ackCallback = new AckCallback(sel.getId());
producer.send(sel.getProducerRecord(), ackCallback);
sel.setNeedToResend(false);
}
System.err.println("Resend " + needToResendRecords.size());
needToResendRecords = waittingAckBuffer.getNeedToResendRecords();
}
}
synchronized void notifyTermination() {
notifyAll() ;
}
synchronized void waitForTermination(long timeout) throws InterruptedException {
wait(timeout);
}
}
public class AckCallback implements Callback {
private long recordId ;
AckCallback(long recordId) {
this.recordId = recordId ;
}
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
try {
if(exception != null) {
WaittingAckProducerRecord<byte[],byte[]> ackRecord = waittingAckBuffer.onFail(recordId);
if(ackRecord.getCallback() != null) {
ackRecord.getCallback().onCompletion(metadata, exception);
}
triggerResendThread();
} else {
//remove the the successfully sent record
WaittingAckProducerRecord<byte[],byte[]> ackRecord = waittingAckBuffer.onSuccess(recordId);
if(ackRecord.getCallback() != null) {
ackRecord.getCallback().onCompletion(metadata, exception);
}
}
} catch(Exception ex) {
LOGGER.error("Error handling ack buffer", ex);
}
}
}
}