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

浙公网安备 33010602011771号