生产者发送消息的基本流程
- 从创建一个 ProducerRecord 对象开始, Producer Record 对象需要包含目标主题和要发送的内容。我们还可以指定键或分区。在发送 ProducerReco rd 对象时,生产者要先把键和值对象序列化成字节数组,这样它们才能够在网络上传输。
- 接下来,数据被传给分区器。如果之前在 Producer Record 对象里指定了分区,那么分区器就不会再做任何事情,直接把指定的分区返回。如果没有 指定分区,那么分区器会根据 Producer Record 对象的键来选择一个分区。选好分区以后,生产者就知道该往哪个主题和分区发送这条记录了。紧接着, 这条记录被添加到一个记录批次里(双端队列,尾部写入),这个批次里的所有消息会被发送到相同的主题和分区上。有一个独立的线程负责把这些记 录批次发送到相应的 broker 上。
- 服务器在收到这些消息时会返回一个响应。如果消息成功写入 Kafka ,就返回一个 RecordMetaData 对象,它包含了主题和分区信息,以及记录在分 区里的偏移量。如果写入失败, 则会返回一个错误。生产者在收到错误之后会尝试重新发送消息,几次之后如果还是失败,就返回错误信息。
- 生产者发送消息一般会发生两类错误: 一类是可重试错误,比如连接错误(可通过再次建立连接解决)、无主 no leader(可通过分区重新选举首领解决)。 另一类是无法通过重试解决,比如“消息太大”异常,具体见 message.max.bytes,这类消息不会进行任何重试,直接抛出异常
- message.max.bytes 表示一个服务器能够接收处理的消息的最大字节数,注意这个值 producer 和 consumer 必须设置一致,且不要大于 fetch.message.max.bytes 属性的值 (消费者能读取的最大消息,这个值应该大于或等于 message.max.bytes)。该值默认是 1000000 字节,大概 900KB~1MB。如果启动压缩,判断压缩后的值。 这个值的大小对性能影响很大,值越大,网络和 IO 的时间越长,还会增加磁盘写入的大小。 Kafka 设计的初衷是迅速处理短小的消息,一般 10K 大小的消息吞吐性能最好(LinkedIn 的 kafka 性能测试)
发送并忘记: 忽略 send 方法的返回值,不做任何处理。大多数情况下,消息会正常到达,而且生产者会自动重试,但有时会丢失消息。效率最高,但不保证消息的可靠性。
1.创建主题"./kafka-topics.sh --zookeeper localhost:2181 --create --topic hello-kafka --replication-factor 1 --partitions 4"
主题名hello-kafka,一个副本,4个分区
2.创建一个maven工程,引入依赖
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
3.创建一个常量类BusiConst,定义一个主题名称
package org.example.config;
public class BusiConst {
public static final String HELLO_TOPIC = "hello-kafka";
}
4.创建一个生产者HelloKafkaProducer
package org.example.helloKafka;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.serialization.StringSerializer;
import org.example.config.BusiConst;
import java.util.Properties;
public class HelloKafkaProducer {
public static void main(String[] args) {
//生产者必须指定3个属性(broker地址清单,key和value的序列化器)
Properties properties = new Properties();
properties.put("bootstrap.servers", "192.168.42.111:9092");
properties.put("key.serializer", StringSerializer.class);
properties.put("value.serializer", StringSerializer.class);
KafkaProducer<String, String> producer = new KafkaProducer<String, String>(properties);
try {
ProducerRecord<String, String> record;
for (int i = 0; i < 4; i++) {
record = new ProducerRecord<String, String>(BusiConst.HELLO_TOPIC,
String.valueOf(i), "Fisher");
//发送并忘记
producer.send(record);
System.out.println(i + ", message is sent.");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
producer.close();
}
}
}
5.创建消费者HelloKafkaConsumer
package org.example.helloKafka;
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 org.example.config.BusiConst;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class HelloKafkaConsumer {
public static void main(String[] args) {
//消费者必须指定3个属性(broker地址清单,key和value的反序列化器)
Properties properties = new Properties();
properties.put("bootstrap.servers", "192.168.42.111:9092");
properties.put("key.deserializer", StringDeserializer.class);
properties.put("value.deserializer", StringDeserializer.class);
//群组并非必须
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "test1");
KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(properties);
//订阅主题(可以多个)
consumer.subscribe(Collections.singletonList(BusiConst.HELLO_TOPIC));
try {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(500));
for (ConsumerRecord<String, String> record : records) {
System.out.println("主题:"+record.topic()+",分区:"+record.partition()+",偏移量:"+record.offset()+
",key:"+record.key()+",value:"+record.value());
}
}
} finally {
consumer.close();
}
}
}
6.先启动消费者,再启动生产者,生产了4条消息
7.查看消费者打印,只有分区0和分区3写入了消息