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

推荐订阅源

博客园 - 聂微东
罗磊的独立博客
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
L
LINUX DO - 最新话题
Webroot Blog
Webroot Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
T
Tenable Blog
Help Net Security
Help Net Security
WordPress大学
WordPress大学
H
Heimdal Security Blog
SecWiki News
SecWiki News
V2EX - 技术
V2EX - 技术
C
CERT Recently Published Vulnerability Notes
P
Privacy International News Feed
T
Tailwind CSS Blog
L
LINUX DO - 热门话题
Scott Helme
Scott Helme
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
O
OpenAI News
Latest news
Latest news
月光博客
月光博客
L
LangChain Blog
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
I
InfoQ
T
Threat Research - Cisco Blogs
人人都是产品经理
人人都是产品经理
Forbes - Security
Forbes - Security
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园_首页
雷峰网
雷峰网
T
Tor Project blog
Security Archives - TechRepublic
Security Archives - TechRepublic
G
GRAHAM CLULEY
Project Zero
Project Zero
MyScale Blog
MyScale Blog
V
V2EX
Vercel News
Vercel News
F
Fortinet All Blogs
The Register - Security
The Register - Security
N
News and Events Feed by Topic
P
Palo Alto Networks Blog
H
Hacker News: Front Page
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - 司徒正美

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

用一个实际业务场景演示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条消息,这和通知系统有什么关系呢?没关系。

--