得物面试:Kafka消息0丢失,如何实现?
尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
Kafka消息0丢失,如何实现?
kafka如何保证消息不丢失?
最近有小伙伴在面试得物,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V140版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取

消息的发送流程
一条消息从生产到被消费,将会经历三个阶段:
- 生产阶段,Producer 新建消息,而后经过网络将消息投递给 MQ Broker
- 存储阶段,消息将会存储在 Broker 端磁盘中
- 消息阶段, Consumer 将会从 Broker 拉取消息
以上任一阶段, 都可能会丢失消息,只要这三个阶段0丢失,就能够完全解决消息丢失的问题。
生产阶段如何实现0丢失方式
从架构视角来说, kafka 生产者之所以会丢消息,和Producer 的高吞吐架构有关。
Producer 的高吞吐架构
Producer 的高吞吐架构: 异步发生+ 批量发送。
Kafka的Producer发送消息采用的是异步发送的方式。
在消息发送的过程中,涉及到了两个线程和一个队列:
- 业务线程 和 Sender线程
- 以及一个消息累积器 : RecordAccumulator。

Kafka Producer SDK会创建了一个消息累积器 RecordAccumulator,里边使用 双端队列 缓存消息, 业务线程 将消息加入到 RecordAccumulator ,业务线程就返回了。
这就是业务发送的妙处, 注意, 业务线程就返回了,但是底层的发送工作,还没开始。
谁来负责底层发送呢? Sender线程。
Sender线程不断从 RecordAccumulator 中拉取消息,负责发送到Kafka broker。

这回我们明白了, 原来机关在这里:
-
kafka在发送消息时,是由底层的SEND线程进行消息的批量发送,不是由业务代码线程执行发送的。
-
业务代码线程执行完send方法后,就返回了。
很多小伙伴 没有写个kafka发消息的代码,下面有一个demo,大家一看就明白了:
package org.example;
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class KafkaProducerExample {
public static void main(String[] args) {
Producer<String, String> producer = getProducer();
// Kafka 主题名称
String topic = "mytopic";
// 发送消息
String message = "Hello, Kafka!";
ProducerRecord<String, String> record = new ProducerRecord<>(topic, message);
producer.send(record);
// 关闭 Kafka 生产者
producer.close();
}
private static Producer<String, String> getProducer() {
Properties properties = getProperties();
// 创建 Kafka 生产者
Producer<String, String> producer = new KafkaProducer<>(properties);
return producer;
}
private static Properties getProperties() {
// Kafka 服务器地址和端口
String bootstrapServers = "localhost:9092";
// Kafka 生产者配置
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
return properties;
}
}
消息到底发送给broker侧没有了?通过send方法其实是无法知道的。
上面的代码,创建消息之后,就开始发送消息了
ProducerRecord<String, String> record = new ProducerRecord<>(topic, message);
producer.send(record);
其实,如果要想知道发现的结果,可以通过send方法的 Future实例去实现:
Future<RecordMetadata> future = producer.send(record);
Producer#send方法是一个异步方法,即它会立即返回一个Future对象,而不会等待消息发送完成。
可以使用Future对象来异步处理发送结果,例如等待发送完成或注册回调函数来处理结果。具体的办法是,给Future去注册回调函数处理结果,这样可以实现非阻塞的方式处理发送完成的回调。
Future去注册回调函数处理结果,下面是一个示例代码:
future.addCallback(new ListenableFutureCallback<RecordMetadata>() {
@Override
public void onSuccess(RecordMetadata metadata) {
System.out.println("消息发送成功,分区:" + metadata.partition() + ",偏移量:" + metadata.offset());
}
@Override
public void onFailure(Throwable ex) {
System.err.println("消息发送失败:" + ex.getMessage());
}
});
还没有其他的方法, 获取发送的处理结果呢?
其实,Producer#send方法有两个重载版本, 具体如下:
Future<RecordMetadata> send(ProducerRecord<K, V> record)
Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback)
除了上面尼恩给大家介绍的是一个参数的版本, 实际上还有一个两个参数的版本:
Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback)
通过这个版本, 大家可以注册回调函数的方式,完成发送结果的处理。通过回调函数版本,更好的实现非阻塞的方式处理发送完成的回调。
参考的代码如下:
producer.send(record, new Callback() {
@Override
public void onCompletion(RecordMetadata metadata

最低0.47元/天 解锁文章
755

被折叠的 条评论
为什么被折叠?



