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

com.github.hackerwin7.jlib.utils.drivers.kafka.consumer.KafkaSimpleConsumer Maven / Gradle / Ivy

There is a newer version: 0.1.1
Show newest version
package com.github.hackerwin7.jlib.utils.drivers.kafka.consumer;

import com.github.hackerwin7.jlib.utils.drivers.kafka.conf.KafkaConf;
import com.github.hackerwin7.jlib.utils.drivers.kafka.data.KafkaMsg;
import kafka.api.FetchRequest;
import kafka.api.FetchRequestBuilder;
import kafka.api.PartitionOffsetRequestInfo;
import kafka.common.ErrorMapping;
import kafka.common.TopicAndPartition;
import kafka.javaapi.*;
import kafka.javaapi.consumer.SimpleConsumer;
import kafka.message.MessageAndOffset;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Created by IntelliJ IDEA.
 * User: hackerwin7
 * Date: 2015/12/10
 * Time: 10:57 AM
 * Desc: kafka api for simple consumer to control offset
 *       add execute pool service for single topic and multiple partition and multiple consumer thread
 *       add consumer threads monitor with these consumer running
 */
public class KafkaSimpleConsumer {
    /*logger*/
    private static Logger logger = Logger.getLogger(KafkaSimpleConsumer.class);

    /*constants*/
    public static final int QUEUE_SIZE = 10000;
    public static final String CONF_SPLIT = ",";
    public static final String PORT_SPLIT = ":";
    public static final int CONSUME_TIME_OUT = 100000;
    public static final int CONSUME_BUFFER_SIZE = 64 * 1024;
    public static final int FETCH_SIZE = 1024 * 1024;
    public static final int ERR_COUNT_RECONN = 5;
    public static final long SLEEPING_TIME = 3000;
    public static final int THREAD_POOL_SIZE = 100;

    /*data*/
    private String topic = null;
    private Map brokers = new HashMap<>();//broker host -> broker port
    private Map offsets = new HashMap<>();//partition -> offset
    private Map endOffsets = new HashMap<>();
    private BlockingQueue queue = new LinkedBlockingQueue<>(QUEUE_SIZE);

    /*driver*/
    private Map consumers = new HashMap<>();// consumer pool, topic partition -> simple consumer

    /*thread pool*/
    private ExecutorService executors = Executors.newFixedThreadPool(THREAD_POOL_SIZE);

    /**
     * constructor by kafka config
     * @param conf
     */
    public KafkaSimpleConsumer(KafkaConf conf) {
        String brokersStr = conf.getProp(KafkaConf.SIMPLE_BROKER_LIST);
        String partitionsStr = conf.getProp(KafkaConf.SIMPLE_PARTITIONS);
        String offsetsStr = conf.getProp(KafkaConf.SIMPLE_OFFSETS);
        String endOffsetsStr = conf.getProp(KafkaConf.SIMPLE_END_OFFSETS);
        String topicStr = conf.getProp(KafkaConf.SIMPLE_TOPIC);
        //topic
        topic = topicStr;
        //brokers
        String[] brokersArr = StringUtils.split(brokersStr, CONF_SPLIT);
        for(String broker : brokersArr) {
            String[] brokerArr = StringUtils.split(broker, PORT_SPLIT);
            String brokerSeed = brokerArr[0];
            Integer port = Integer.valueOf(brokerArr[1]);
            brokers.put(brokerSeed, port);
        }
        //partitions
        String[] partitionsArr = StringUtils.split(partitionsStr, CONF_SPLIT);
        //offsets
        String[] offsetsArr = StringUtils.split(offsetsStr, CONF_SPLIT);
        for(int i = 0; i <= partitionsArr.length - 1; i++) {
            Integer partition = Integer.valueOf(partitionsArr[i]);
            Long offset = Long.valueOf(offsetsArr[i]);
            offsets.put(partition, offset);
        }
        //endOffsets
        String[] endOffsetsArr = StringUtils.split(endOffsetsStr, CONF_SPLIT);
        for(int i = 0; i <= partitionsArr.length - 1; i++) {
            Integer partition = Integer.valueOf(partitionsArr[i]);
            Long endOffset = Long.MAX_VALUE;
            if(i <= endOffsetsArr.length - 1) {
                endOffset = Long.valueOf(endOffsetsArr[i]);
            }
            endOffsets.put(partition, endOffset);
        }
        //construct and start consumer
        start();
    }

    /**
     * start the all consumer to running put to the queue
     */
    private void start() {
        for(Map.Entry entry : offsets.entrySet()) {
            //getOrigin offset partition info for specific consume thread
            int partition = entry.getKey();
            long offset = entry.getValue();
            long endOffset = endOffsets.get(partition);
            if(!endOffsets.containsKey(partition) || endOffset <= 0) {
                logger.info("topic = " + topic + ", partition = " + partition + ", offset = " + offset + ", end offset = " + endOffset + " is invalid, reset end offset to Long.max");
                endOffset = Long.MAX_VALUE;
            }
            //build consume thread
            ConsumeThread cth = new ConsumeThread(brokers, partition, offset, endOffset);
            executors.submit(cth);
            consumers.put(partition, cth);
            logger.info("started consumer topic = " + topic + ", partition = " + partition + ", offset = " + offset + ", end offset = " + endOffset);
        }
    }

    /**
     * find max offset
     * @return max offset
     */
    public long findMaxOffset(int partition) {
        ConsumeThread consumeThread = consumers.get(partition);
        return consumeThread.getMaxOffset();
    }

    /**
     * find min offset
     * @param partition
     * @return min offset
     */
    public long findMinOffset(int partition) {
        ConsumeThread consumeThread = consumers.get(partition);
        return consumeThread.getMinOffset();
    }

    /**
     * getOrigin topic in this consumer
     * @return topic name
     */
    public String getTopic() {
        return topic;
    }

    /**
     * close thread pool
     */
    public void close() {
        if(executors != null) {
            executors.shutdownNow();
        }
    }

    /**
     * start the thread to run the consumer
     */
    public class ConsumeThread implements Runnable {
        /*logger*/
        private Logger logger = Logger.getLogger(ConsumeThread.class);
        /*data*/
        private int partition = 0;
        private long offset = 0;
        private long endOffset = 0;
        private Map brokers = null;//concurrent map getOrigin no put
        private AtomicBoolean running = new AtomicBoolean(true);
        private List replicaBrokers = new ArrayList<>();
        String clientName = "simple consumer";
        /*driver*/
        private SimpleConsumer consumer = null;

        /**
         * constructor with broker and partition, offset
         * @param brokers
         * @param partition
         * @param beginOffset
         * @param endOffset
         */
        public ConsumeThread(Map brokers, int partition, long beginOffset, long endOffset) {
            this.brokers = brokers;
            this.partition = partition;
            this.offset = beginOffset;
            this.endOffset = endOffset;
        }

        /**
         * run consumer to consume the kafka message
         */
        public void run() {
            PartitionMetadata metadata = findLeader();
            if(metadata == null) {
                logger.error("can not find metadata for topic = " + topic);
                return;
            }
            if(metadata.leader() == null) {
                logger.error("can not find leader for topic = " + topic);
                return;
            }
            String leader = metadata.leader().host();
            int port  = metadata.leader().port();
            clientName = "simple_" + System.currentTimeMillis();
            consumer = new SimpleConsumer(leader, port, CONSUME_TIME_OUT, CONSUME_BUFFER_SIZE, clientName);
            //while status var
            long readOffset = offset;
            int numErr = 0;
            while (running.get()) {
                FetchRequest request = new FetchRequestBuilder()
                        .clientId(clientName)
                        .addFetch(topic, partition, readOffset, FETCH_SIZE)
                        .build();
                FetchResponse response = consumer.fetch(request);
                if(response.hasError()) {//error deal
                    numErr++;
                    short code = response.errorCode(topic, partition);
                    logger.error("receive error response from broker = " + leader + ", port = " + port + ", topic = " + topic + ", partition = " + partition + ", err code = " + code);
                    if(numErr >= ERR_COUNT_RECONN) { // exit consumer
                        logger.error("continuous receive " + numErr + " responses, exiting consumer thread......");
                        return;
                    } else if(code == ErrorMapping.OffsetOutOfRangeCode()) { // reset read offset
                        long minOffset = getMinOffset();
                        long maxOffset = getMaxOffset();
                        logger.error("encounter out off range fetch error.");
                        logger.error("min offset = " + minOffset + ", max offset = " + maxOffset + ", request offset = " + readOffset);
                        if(readOffset < minOffset) {
                            readOffset = minOffset;
                        } else if(readOffset > maxOffset) {
                            readOffset = maxOffset;
                        } else {
                            /*no op*/
                        }
                        logger.error("reset the request offset to " + readOffset + ", in partition = " + partition + ", of topic = " + topic);
                    } else { // find new leader and rebuild consumer
                        logger.error("leader broker maybe switched , finding ......");
                        logger.error("old leader = " + leader + ", port = " + port);
                        consumer.close();
                        metadata = findNewLeader(metadata);
                        if(metadata == null) {
                            logger.error("can not find new metadata for topic = " + topic);
                            logger.error("exiting consumer thread...");
                            return;
                        }
                        if(metadata.leader() == null) {
                            logger.error("can not find new leader for topic = " + topic);
                            logger.error("exiting consumer thread...");
                            return;
                        }
                        leader = metadata.leader().host();
                        port = metadata.leader().port();
                        consumer = new SimpleConsumer(leader, port, CONSUME_TIME_OUT, CONSUME_BUFFER_SIZE, clientName);
                        logger.error("new leader = " + leader + ", port = " + port);
                    }
                } else {// no error
                    numErr = 0;//clear the continuous err number
                    for(MessageAndOffset messageAndOffset : response.messageSet(topic, partition)) {
                        long curOffset = messageAndOffset.offset();
                        if(curOffset < readOffset) {
                            logger.error("found an old offset = " + curOffset + ", expect read offset = " + readOffset);
                        } else {//put message to offset
                            //header
                            long nextOffset = messageAndOffset.nextOffset();
                            long currentOffset = curOffset;
                            byte[] val  = null;
                            String key = null;
                            String topicMsg = null;
                            int partitionMsg = 0;
                            //deal value
                            ByteBuffer valBuffer = messageAndOffset.message().payload();
                            byte[] valBytes = new byte[valBuffer.limit()];
                            valBuffer.get(valBytes);
                            val = valBytes;
                            //deal key
                            if(messageAndOffset.message().hasKey()) {
                                ByteBuffer keyBuffer = messageAndOffset.message().key();
                                byte[] keyBytes = new byte[keyBuffer.limit()];
                                keyBuffer.get(keyBytes);
                                key = new String(keyBytes);
                            }
                            //deal topic
                            topicMsg = topic;
                            //deal partition
                            partitionMsg = partition;
                            //make kafka message
                            KafkaMsg msg = KafkaMsg.createBuilder()
                                    .offset(currentOffset)
                                    .nextOffset(nextOffset)
                                    .val(val)
                                    .key(key)
                                    .topic(topicMsg)
                                    .partition(partitionMsg)
                                    .build();
                            //put into queue
                            while (true) {
                                try {
                                    queue.put(msg);
                                    break;
                                } catch (InterruptedException e) {
                                    logger.error(e.getMessage(), e);
                                    try {
                                        Thread.sleep(SLEEPING_TIME);
                                    } catch (InterruptedException ee) {
                                        logger.error(ee.getMessage(), ee);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        /**
         * find specified topic's leader broker
         * @return leader
         */
        private PartitionMetadata findLeader() {
            PartitionMetadata metadata = null;
            loop:
            for(Map.Entry brokerPort : brokers.entrySet()) {
                String seed = brokerPort.getKey();
                int port = brokerPort.getValue();
                String clientName = "find_leader" + System.currentTimeMillis();
                SimpleConsumer consumer = null;
                try {
                    consumer = new SimpleConsumer(seed, port, CONSUME_TIME_OUT, CONSUME_BUFFER_SIZE, clientName);
                    List topics = Collections.singletonList(topic);
                    TopicMetadataRequest request = new TopicMetadataRequest(topics);
                    TopicMetadataResponse response = consumer.send(request);
                    List metadatas = response.topicsMetadata();
                    for(TopicMetadata item : metadatas) {
                        for(PartitionMetadata part : item.partitionsMetadata()) {
                            if(part.partitionId() == partition) {
                                metadata = part;
                                break loop;
                            }
                        }
                    }
                } catch (Throwable e) {
                    logger.error("error communicating with broker = [" + seed + "] to find leader for topic = [" + topic + "], partition = [" + partition + "]");
                } finally {
                    if(consumer != null)
                        consumer.close();
                }
            }
            if(replicaBrokers != null) {
                replicaBrokers.clear();
                for(kafka.cluster.Broker replica : metadata.replicas()) {
                    replicaBrokers.add(replica.host() + ":" + replica.port());//add replica broker to save
                }
            }
            return metadata;
        }

        /**
         * find new leader
         * @param old
         * @return new leader metadata
         * @throws Exception
         */
        private PartitionMetadata findNewLeader(PartitionMetadata old) {
            PartitionMetadata leader = null;
            for(int i = 0; i <= 2; i++) {
                boolean goToSleep = false;
                PartitionMetadata metadata = findLeader();
                if(metadata == null) {
                    goToSleep = true;
                } else if(metadata.leader() == null) {
                    goToSleep = true;
                } else if(StringUtils.equalsIgnoreCase(old.leader().host(), metadata.leader().host()) && i == 0) {
                    goToSleep = true;
                } else {
                    return metadata;//new leader
                }
                if(goToSleep) {
                    try {
                        Thread.sleep(SLEEPING_TIME);
                    } catch (InterruptedException e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
            logger.error("unable to find new leader when simple consumer has error code......");
            return null;
        }

        /**
         * getOrigin offset from topic:partition
         * @return offset
         */
        private long getOffset(long minOrmax) {
            TopicAndPartition topicAndPartition = new TopicAndPartition(topic, partition);
            Map requestInfoMap = new HashMap<>();
            requestInfoMap.put(topicAndPartition, new PartitionOffsetRequestInfo(minOrmax, 1));
            kafka.javaapi.OffsetRequest request = new kafka.javaapi.OffsetRequest(requestInfoMap, kafka.api.OffsetRequest.CurrentVersion(), clientName);
            OffsetResponse response = consumer.getOffsetsBefore(request);
            if(response.hasError()) {
                logger.error("error fetching offset data with topic = " + topic + ", partition = " + partition + ", reason = " + response.errorCode(topic, partition));
                return -1;
            }
            long[] offsets = response.offsets(topic, partition);
            return offsets[0];
        }

        /**
         * getOrigin min offset by specific partition
         * @return min offset
         */
        private long getMinOffset() {
            return getOffset(kafka.api.OffsetRequest.EarliestTime());
        }

        /**
         * getOrigin max offset
         * @return maxoffset
         */
        private long getMaxOffset() {
            return getOffset(kafka.api.OffsetRequest.LatestTime());
        }
    }

    /**
     * consume message
     * @return kafka msg
     * @throws Exception
     */
    public KafkaMsg consume() throws Exception {
        return queue.take();
    }

    /**
     * getOrigin queue size
     * @return size of queue
     */
    public int getQueueSize() {
        return queue.size();
    }

    /**
     * is queue empty
     * @return bool
     */
    public boolean isConsumeEmpty() {
        return queue.isEmpty();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy