惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
H
Help Net Security
博客园_首页
P
Privacy International News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tenable Blog
Latest news
Latest news
D
Darknet – Hacking Tools, Hacker News & Cyber Security
爱范儿
爱范儿
Cyberwarzone
Cyberwarzone
P
Palo Alto Networks Blog
月光博客
月光博客
有赞技术团队
有赞技术团队
Know Your Adversary
Know Your Adversary
博客园 - 叶小钗
P
Proofpoint News Feed
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
S
Schneier on Security
A
About on SuperTechFans
F
Full Disclosure
The Cloudflare Blog
T
The Exploit Database - CXSecurity.com
S
SegmentFault 最新的问题
Apple Machine Learning Research
Apple Machine Learning Research
Microsoft Security Blog
Microsoft Security Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
N
News and Events Feed by Topic
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Webroot Blog
Webroot Blog
量子位
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
WordPress大学
WordPress大学
Last Week in AI
Last Week in AI
美团技术团队
Help Net Security
Help Net Security
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
云风的 BLOG
云风的 BLOG
Y
Y Combinator Blog
博客园 - 司徒正美
N
Netflix TechBlog - Medium
S
Security @ Cisco Blogs
B
Blog
P
Privacy & Cybersecurity Law Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News - Newest:
Hacker News - Newest: "LLM"
V
Visual Studio Blog
NISL@THU
NISL@THU

博客园 - 有点懒惰的大青年

用一个实际业务场景演示ON和WHERE的正确使用方式 idea中查看源码 异或运算 使用Hutool对时间的花式操作 合并K个升序列表 比较器 枚举类的设计模式 spring boot中使用RedissonClient实现分布式锁 idea同时启动application,启用不同端口 @PostConstruct用法 ApplicationContext 事件发布与监听机制详解 java自带命令jps介绍 关于maven中标签的说明 mysql的跨库查询 linux命令ll显示结果的含义 Files类的使用 java环境变量设置 java值传递和引用传递 kafka的集群与可靠性 关于kafka 达梦数据库执行计划介绍 关于MQ 用位运算实现加减乘除(3)
kafka的消费全流程
有点懒惰的大青年 · 2025-06-01 · via 博客园 - 有点懒惰的大青年

多线程安全问题

多线程安全:定义为:多线程去访问一个类,这个类始终表现出正确的行为,不管运行的环境、让线程交替执行、不需要任何的额外的同步、协同,都能表现出正确的行为。

比如: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条消息,这和通知系统有什么关系呢?没关系。

--