kafka的消费全流程

多线程安全问题

多线程安全:定义为:多线程去访问一个类,这个类始终表现出正确的行为,不管运行的环境、让线程交替执行、不需要任何的额外的同步、协同,都能表现出正确的行为。
比如:i++,这个肯定不是多线程安全的。
 
kafka中:
生产者:是多线程安全。(不可变的方式去解决)
KafkaProducer类中所有的成员变量,都是private、final的
 
消费者:不是多线程安全。消费的时候拿到一个分区,比如consumer1拿到分区0进行消费的时候,与此同时还需要进行ack确认,要确认消费的这一条或者几条是否被成功的消费了,这种情况下,consumer是很难确保多线程安全的。那么, 如果你想又要使用多线程,又想使用consumer,该怎么办?
思路:如果所有的成员变量都是不可变,final的,那一定是多线程安全的。还有一种确保多线程安全:线程封闭(每个线程实例化一个consumer对象)
 
多线程下使用生产者:

package com.msb.concurrent;

import com.msb.selfserial.User;
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.StringSerializer;

import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 多线程下使用生产者
 */
public class KafkaConProducer {

    //发送消息的个数
    private static final int MSG_SIZE = 1000;
    //负责发送消息的线程池
    private static ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    private static CountDownLatch countDownLatch = new CountDownLatch(MSG_SIZE);

    private static User makeUser(int id) {
        User user = new User(id);
        String userName = "msb_" + id;
        user.setName(userName);
        return user;
    }

    /**
     * 发送消息的任务
     */
    private static class ProduceWorker implements Runnable {

        private ProducerRecord<String, String> record;
        private KafkaProducer<String, String> producer;

        public ProduceWorker(ProducerRecord<String, String> record, KafkaProducer<String, String> producer) {
            this.record = record;
            this.producer = producer;
        }

        @Override
        public void run() {
            final String threadName = Thread.currentThread().getName();
            producer.send(record, new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    if (null != exception) {
                        exception.printStackTrace();
                    }
                    if (null != metadata) {
                        System.out.println(threadName + "|" + String.format("偏移量:%s,分区:%s", metadata.offset(), metadata.partition()));
                    }
                }
            });
            countDownLatch.countDown();
        }
    }

    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put("bootstrap.servers", "127.0.0.1:9092");
        properties.put("key.serializer", StringSerializer.class);
        properties.put("value.serializer", StringSerializer.class);

        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);

        try {
            for (int i = 0; i < MSG_SIZE; i++) {
                User user = makeUser(i);
                ProducerRecord<String, String> record = new ProducerRecord<>("concurrent-ConsumerOffsets", null, System.currentTimeMillis(), user.getId() + "", user.toString());
                executorService.submit(new ProduceWorker(record, producer));
            }
            countDownLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            producer.close();
            executorService.shutdown();
        }
    }
}
多线程下使用消费者:
 package com.msb.concurrent;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 多线程下正确的使用消费者,需要记住:一个线程一个消费者
 */
public class KafkaConConsumer {

    public static final int CONCURRENT_PARTITIONS_COUNT = 2;

    private static ExecutorService executorService = Executors.newFixedThreadPool(CONCURRENT_PARTITIONS_COUNT);

    private static class ConsumerWorker implements Runnable {
        private KafkaConsumer<String, String> consumer;

        public ConsumerWorker(Map<String, Object> config, String topic) {
            Properties properties = new Properties();
            properties.putAll(config);
            //一个线程一个消费者
            this.consumer = new KafkaConsumer<>(properties);
            this.consumer.subscribe(Collections.singletonList(topic));
        }

        @Override
        public void run() {
            final String threadName = Thread.currentThread().getName();
            while (true) {
                ConsumerRecords<String, String> records = consumer.poll(Duration.ofSeconds(1));
                for (ConsumerRecord<String, String> record : records) {
                    System.out.println(threadName + "|" + String.format("主题:%s,分区:%d,偏移量:%d,key:%s,value:%s",
                            record.topic(), record.partition(), record.offset(), record.key(), record.value()));
                }
            }
        }
    }

    public static void main(String[] args) {
        Map<String, Object> properties = new HashMap<>();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "c_test");
        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

        for (int i = 0; i < CONCURRENT_PARTITIONS_COUNT; i++) {
            //一个线程一个消费者
            executorService.submit(new ConsumerWorker(properties, "concurrent-ConsumerOffsets"));
        }
    }
}

 

群组协调

1、组协调器(broker)
1)选举leader消费者客户端
2)处理申请加入组的客户端
3)再平衡后,同步新的分配方案
4)心跳(与客户端维持),判断哪些客户端离线
5)管理消费者已经消费的偏移量(consumer_offset,默认50个)
 
2、消费者协调器(客户端)
1)向组协调器发起入组请求(第一个成为群主)
2)发起同步组(消费者分区划分情况)的请求(leader客户端,负责计算,分配策略作为入参传入)
3)心跳(与组协调器维持)
4)发起已经提交的消费者偏移量的请求(ACK确认)
5)主动的发起离组请求
 
当一个消费者加入分组(组协调器、消费者协调器,干的哪些事情)
1、消费客户端启动、重连(JoinGroup,请求->组协调器)
2、客户端已经完成JoinGroup,客户端(消费者协调器)再次向组协调器发起SyncGroup(同步组),获取新的分配方案
3、客户端关机、异常,触发离组。
4、客户端加入一组之后(一直保持心跳)

 

分区再均衡

 

kafka的消费者分区再均衡问题及案例

tips:
消费者是以群组名进行消费的,比如:
topic: 名为msb,里面有100条消息
groupID:test,名为test会将这100条消息消费;
groupID:test1,又定义了一个群组,名为test1,又会拿到100条消息,进行消费;
groupID:test2,再定义了一个消费者群组,名为test2,他也会重新消费这100条消息;
上面的三个群组,test、test1、test2之间提交什么东西,偏移量等等,相互没有任何关系的,只要groupID的名字是不一样的。因为消费者群组的概念主要用于什么?
当你的上游系统比如说是订单系统,产生了一批订单,比如100条订单信息,下游系统有:物流系统,他要拿到这100笔订单去发物流;下面还有通知系统,他也要拿到这100条消息进行通知发送;下面还有会员系统,购买了东西加一些积分什么的,他也要拿到这100条订单进行积分处理。所以群组的概念就在于你在进行这种生产消费的时候,生产的100条消息这是属于生产者,然后消费的时候,我有可能有不同的系统:物流系统是一个群组,积分系统是一个群组,会员系统是一个群组,我们之间是没有任何影响的,物流系统消费了80条消息,这和通知系统有什么关系呢?没关系。

 

 

 

 

 

 

 

--

posted on 2025-06-01 14:48  有点懒惰的大青年  阅读(34)  评论(0)    收藏  举报