All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.darkphoenixs.kafka.pool.KafkaMessageReceiverRetry Maven / Gradle / Ivy

There is a newer version: 1.5.11
Show newest version
/*
 * Copyright (c) 2018. Dark Phoenixs (Open-Source Organization).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.darkphoenixs.kafka.pool;

import kafka.message.MessageAndMetadata;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.darkphoenixs.kafka.core.KafkaMessageAdapter;
import org.darkphoenixs.mq.exception.MQException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The type Kafka message receiver retry.
 *
 * @param  the type parameter
 */
public class KafkaMessageReceiverRetry {

    private static final Logger logger = LoggerFactory.getLogger(KafkaMessageReceiverRetry.class);

    private final int errorPoolSize = 1;

    private final int retryCount;

    /**
     * The Error message queue.
     */
    protected final BlockingQueue errorMessageQueue;

    /**
     * The Error message pool.
     */
    protected final ExecutorService errorMessagePool;

    /**
     * The Error retry threads.
     */
    protected final List errorRetryThreads;

    /**
     * The Error message count.
     */
    protected final ConcurrentMap errorMessageCount;

    /**
     * Instantiates a new Kafka message receiver retry.
     *
     * @param topic          the topic
     * @param retryCount     the retry count
     * @param messageAdapter the message adapter
     */
    public KafkaMessageReceiverRetry(String topic, int retryCount, KafkaMessageAdapter messageAdapter) {

        this.retryCount = retryCount;

        this.errorMessageQueue = new LinkedBlockingQueue();

        this.errorRetryThreads = new ArrayList(errorPoolSize);

        this.errorMessagePool = Executors.newFixedThreadPool(errorPoolSize, new KafkaPoolThreadFactory(KafkaMessageReceiverRetry.RetryThread.tagger + "-" + topic));

        this.errorMessageCount = new ConcurrentHashMap();

        RetryThread retryThread = new RetryThread(messageAdapter);

        this.errorRetryThreads.add(retryThread);

        this.errorMessagePool.submit(retryThread);
    }

    /**
     * Receive message retry.
     *
     * @param record the record
     */
    public void receiveMessageRetry(T record) {

        try {
            if (record instanceof MessageAndMetadata) {

                MessageAndMetadata messageAndMetadata = (MessageAndMetadata) record;

                if (0 < errorMessageCount(messageAndMetadata.topic(), messageAndMetadata.partition(), messageAndMetadata.offset())) {

                    errorMessageQueue.put(record);
                }
            } else if (record instanceof ConsumerRecord) {

                ConsumerRecord consumerRecord = (ConsumerRecord) record;

                if (0 < errorMessageCount(consumerRecord.topic(), consumerRecord.partition(), consumerRecord.offset())) {

                    errorMessageQueue.put(record);
                }
            }
        } catch (InterruptedException e) {

            logger.error("BlockingQueue put failed.", e);
        }
    }

    /**
     * Receive message clean.
     *
     * @param record the record
     */
    public void receiveMessageClean(T record) {

        if (record instanceof MessageAndMetadata) {

            MessageAndMetadata messageAndMetadata = (MessageAndMetadata) record;

            errorMessageCount.remove(errorMessageKey(messageAndMetadata.topic(), messageAndMetadata.partition(), messageAndMetadata.offset()));

        } else if (record instanceof ConsumerRecord) {

            ConsumerRecord consumerRecord = (ConsumerRecord) record;

            errorMessageCount.remove(errorMessageKey(consumerRecord.topic(), consumerRecord.partition(), consumerRecord.offset()));
        }
    }

    /**
     * Receive message count int.
     *
     * @param record the record
     * @return the int
     */
    public int receiveMessageCount(T record) {

        if (record instanceof MessageAndMetadata) {

            MessageAndMetadata messageAndMetadata = (MessageAndMetadata) record;

            return errorMessageCount.get(errorMessageKey(messageAndMetadata.topic(), messageAndMetadata.partition(), messageAndMetadata.offset())).get();

        } else if (record instanceof ConsumerRecord) {

            ConsumerRecord consumerRecord = (ConsumerRecord) record;

            return errorMessageCount.get(errorMessageKey(consumerRecord.topic(), consumerRecord.partition(), consumerRecord.offset())).get();
        }

        return 0;
    }

    /**
     * Destroy.
     */
    public void destroy() {

        if (errorMessageQueue != null)

            while (!errorMessageQueue.isEmpty()) ;

        for (RetryThread thread : errorRetryThreads)

            thread.shutdown();

        if (errorMessagePool != null) {

            errorMessagePool.shutdown();

            logger.info("Message Error pool closed.");
        }
    }

    private String errorMessageKey(final String _topic, final int _partition, final long _offset) {

        return _topic + "_" + _partition + "_" + _offset;
    }

    /**
     * Error message count int.
     *
     * @param topic     the topic
     * @param partition the partition
     * @param offset    the offset
     * @return the int
     */
    protected int errorMessageCount(String topic, int partition, long offset) {

        if (retryCount == 0)

            return 0;

        String errorKey = errorMessageKey(topic, partition, offset);

        AtomicInteger count = errorMessageCount.get(errorKey);

        if (null == count) {

            count = errorMessageCount.putIfAbsent(errorKey, new AtomicInteger(1));
        }

        if (null != count) {

            if (retryCount > count.get()) {

                return count.incrementAndGet();

            } else {
                errorMessageCount.remove(errorKey);

                return 0;
            }
        }

        return 1;
    }

    /**
     * The type Retry thread.
     */
    class RetryThread implements Runnable {

        /**
         * The constant tagger.
         */
        public static final String tagger = "RetryThread";

        private final AtomicBoolean closed = new AtomicBoolean(false);

        private final KafkaMessageAdapter adapter;

        /**
         * Instantiates a new Handler thread.
         *
         * @param adapter the adapter
         */
        public RetryThread(KafkaMessageAdapter adapter) {

            this.adapter = adapter;
        }

        @Override
        public void run() {

            logger.info(Thread.currentThread().getName() + " start.");

            while (!closed.get()) {

                T record = null;

                try {
                    record = errorMessageQueue.take();

                } catch (InterruptedException e) {

                    logger.error("BlockingQueue take failed.", e);
                }

                if (record instanceof ConsumerRecord) {

                    ConsumerRecord consumerRecord = (ConsumerRecord) record;

                    int retries = receiveMessageCount(record);

                    try {
                        logger.warn("Retry receive message. Number of retries: " + retries);

                        adapter.messageAdapter(consumerRecord);

                        receiveMessageClean(record);

                    } catch (MQException e) {

                        receiveMessageRetry(record);

                        logger.error("Receive message failed."
                                + " retries: " + retries
                                + " topic: " + consumerRecord.topic()
                                + " offset: " + consumerRecord.offset()
                                + " partition: " + consumerRecord.partition(), e);
                    }

                } else if (record instanceof MessageAndMetadata) {

                    MessageAndMetadata messageAndMetadata = (MessageAndMetadata) record;

                    int retries = receiveMessageCount(record);

                    try {
                        logger.warn("Retry receive message. Number of retries: " + retries);

                        adapter.messageAdapter(messageAndMetadata);

                        receiveMessageClean(record);

                    } catch (MQException e) {

                        receiveMessageRetry(record);

                        logger.error("Receive message failed."
                                + " retries: " + retries
                                + " topic: " + messageAndMetadata.topic()
                                + " offset: " + messageAndMetadata.offset()
                                + " partition: " + messageAndMetadata.partition(), e);
                    }
                }

            }
            logger.info(Thread.currentThread().getName() + " end.");
        }

        /**
         * Shutdown hook which can be called from a separate thread.
         */
        public void shutdown() {

            closed.set(true);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy