kafka消费者总结(四)

之前介绍了消费者提交offset和消费消息保持一致性,消费者group的rebalance操作以及消费者pull消息的流程,本次总结我们介绍如何通过多线程从kafka消费消息。

我们知道创建kafka topic命令是可以指定分区的个数以及复制因子(也就是分区副本的个数),命令如下:

#创建topic

kafka-topics.sh --create --zookeeper 127.0.0.1:2181 --replication-factor 2 --partitions 2 --topic test

上面的命令是创建了一个名称为test的topic,有2个分区,每个分区有2个副本。通过多线程消费kafka分区的消息,可以提升效率,代码如下:

/**
 * 提交消费请求的线程
 * @author steven.xu
 */
public class ProcessThread extends CommonThread {
//业务处理器(线程池)
private ExecutorService serviceExecutor = null;
LogContext logContext = new LogContext("[ProcessThread]");

Logger log = logContext.logger(getClass());

        private int threadcount ;


public ProcessThread( int threadNum) {
super(queue);

serviceExecutor = Executors.newFixedThreadPool(threadNum);

               this.threadcount = threadNum;

}

public void setStop(){
stop = true;
}

public void run() {
while (!stop) {
try {
/**************************************************************/

                               //提交任务处理

                                 for(int i = 0; i

serviceExecutor.execute(new Consumer(KafkaProperties.TOPIC));

                                }

/*************************************************************/
} catch (Exception ex) {
ex.printStackTrace();
log.error("出现未知异常:" + ex.toString());
}
}
log.debug("请求处理线程关闭.......");
}

}

从ProcessThread 这个类中可以看到,根据构造函数初始化了一个固定线程数的线程池,然后新建threadNum 个消费者线程提交到线程池中执行

public class Consumer extends ShutdownableThread {
    private final KafkaConsumer consumer;
    private final String topic;

    public Consumer(String topic) {
        super("KafkaConsumerExample", false);
        Properties props = new Properties();
       props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, KafkaProperties.KAFKA_SERVER_URL + ":" +                        KafkaProperties.KAFKA_SERVER_PORT);
        props.put(ConsumerConfig.GROUP_ID_CONFIG, "DemoConsumer");
        props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
        props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
        props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, "30000");
        props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.IntegerDeserializer");
        props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        consumer = new KafkaConsumer<>(props);
        this.topic = topic;
    }
    @Override
    public void doWork() {
        consumer.subscribe(Collections.singletonList(this.topic));
        ConsumerRecords records = consumer.poll(1000);
        for (ConsumerRecord record : records) {
            System.out.println("Received message: (" + record.key() + ", " + record.value() + ") at offset " +                                   record.offset());
        }
    }
    @Override
    public String name() {
        return null;
    }
    @Override
    public boolean isInterruptible() {
        return false;
    }

}


Consumer 是继承自ShutdownableThread,后者是通过scala实现的一个线程类,会在run方法中一致调用doWork方法消费消息,关键代码如下:

 override def run(): Unit = {
    info("Starting")
    try {
      while (isRunning)
        doWork()
    } catch {
      case e: FatalExitError =>
        shutdownInitiated.countDown()
        shutdownComplete.countDown()
        info("Stopped")
        Exit.exit(e.statusCode())
      case e: Throwable =>
        if (isRunning)
          error("Error due to", e)
    } finally {
       shutdownComplete.countDown()
    }
    info("Stopped")
  }

通过这种方式我们可以实例化多个消费者,并将其封装成线程类提交到线程池执行,这里有个问题需要注意就是消费者线程的个数最好不要超过分区的个数,因为每个消费分区只能分配给同一consumer group内的一个消费者消费,要是消费者格个数大于分区数,就有可能部分消费者没有分区消费,造成资源浪费

至此我们的kafka消费者总结到此已经全部完成,后面的部分我们将介绍producer的内容


你可能感兴趣的:(kafka)