选择哪种发送模式取决于你的应用场景和需求。如果你需要极低的延迟和简单的代码实现,可以选择Fire-and-forget;如果你需要确保每条消息都被可靠地写入Kafka,并且能够处理错误,可以选择Synchronous send;如果你需要兼顾低延迟、高吞吐量和可靠性,可以选择Asynchronous send。
kafka发送端3种不同的发送模式,如下:
1、Fire-and-forget
只发送消息,不关心消息是否发送成功,本质上也是一种异步发送的方式,消息先存储在缓冲区中,达到设定条件后批量发送。
当然这是kafka吞吐量最高的一种方式,并配合参数acks=0,这样生产者不需要等待服务器的响应,以网络能支持的最大速度发送消息,但是也是消息最不可靠的一种方式,因为对于发送失败的消息没有做任何处理。
Fire-and-forget是最简单的发送模式,在这种模式下,生产者将消息发送到Kafka服务器后,不会等待任何来自服务器的响应,一旦消息被发送出去,生产者就会立即继续处理其他任务,不会关心消息是否成功写入Kafka。如下代码案例:
ProducerRecord<byte[],byte[]> record = new ProducerRecord<byte[],byte[]>("the-topic", key, value);
try {
producer.send(record);
} catch (Exception e) {
e.printStackTrace();
}
在发送消息之前有可能会发生异常,例如是序列化消息失败的SerializationException、缓冲区满的BufferExhaustedException、发送超时的TimeoutException或者发送的线程被中断的InterruptException,发送消息之后并没有异常处理。
这种方式有如下特点:
优点:
- 低延迟:由于生产者不需要等待服务器的响应,因此可以实现非常低的延迟。
- 高吞吐量:生产者可以快速地连续发送多条消息,不受服务器响应时间的限制。
缺点:
- 不可靠:如果消息在传输过程中丢失,或者Kafka服务器未能成功接收消息,生产者将无法得知这一情况。
- 无法处理失败:由于没有反馈机制,生产者无法对发送失败的消息进行重试或记录日志。
2、Synchronous send
同步发送,send()方法会返回Futrue对象,通过调用Futrue对象的get()方法,等待直到结果返回,根据返回的结果可以判断是否发送成功,如果业务要求消息必须是按顺序发送的,那么可以使用同步的方式,并且只能在一个partation上,结合参数设置retries的值让发送失败时重试,设置max_in_flight_requests_per_connection=1,可以控制生产者在收到服务器晌应之前只能发送1个消息,在消息发送成功后立刻flush,从而控制消息顺序发送。
在Synchronous send模式下,生产者发送消息后会阻塞并等待Kafka服务器的响应,只有当消息被成功写入Kafka,或者发生错误时,生产者才会继续执行后续操作。如下代码案例:
ProducerRecord<byte[],byte[]> record = new ProducerRecord<byte[],byte[]>("the-topic", key, value);
try {
RecordMetadata metadata = producer.send(record).get();
} catch (Exception e) {
e.printStackTrace();
}
producer.flush();
producer.close();
在调用send()方法后再调用get()方法等待结果返回。如果发送失败会抛出异常,如果发送成功会返回一个RecordMetadata对象,然后可以调用offset()方法获取该消息在当前分区的偏移量。
KafkaProducer有两种类型的异常,第一种是可以重试的Retriable,该类异常可以通过重新发送消息解决。例如是连接异常后重新连接、“no leader”异常后重新选取新的leader。KafkaProducer可以配置为遇到该类异常后自动重新发送消息直到超过重试次数。第二类是不可重试的,例如是“message size too large”(消息太大),该类异常会马上返回错误。
这种方式有如下特点:
优点:
- 可靠性:生产者可以确保每条消息都被成功写入Kafka,或者得知发送失败的消息以便进行重试或记录日志。
- 错误处理:生产者可以根据Kafka服务器的响应来处理错误(例如,网络问题或Kafka服务器内部错误)。
缺点:
- 高延迟:由于需要等待Kafka服务器的响应,因此延迟较高。
- 可能的吞吐量下降:生产者的吞吐量受限于Kafka服务器的响应时间。如果服务器响应缓慢,生产者的吞吐量也会降低。
3、Asynchronous send
异步发送,在调用send()方法的时候指定一个callback函数,当broker接收到返回的时候,该callback函数会被触发执行。
如果业务需要知道消息发送是否成功,并且对消息的顺序不关心,那么可以用异步+回调的方式来发送消息,配合参数retries=0,并将发送失败的消息记录到日志文件中;要使用callback函数,先要实现org.apache.kafka.clients.producer.Callback接口,该接口只有一个onCompletion方法,如果发送异常,onCompletion的参数Exception e会为非空。
Asynchronous send模式结合了Fire-and-forget和Synchronous send的特点。生产者发送消息后不会立即阻塞,而是继续执行其他任务。同时,生产者会异步地等待Kafka服务器的响应,并在收到响应后处理结果。
ProducerRecord<byte[],byte[]> record = new ProducerRecord<byte[],byte[]>("the-topic", key, value);
producer.send(myRecord,
new Callback() {
public void onCompletion(RecordMetadata metadata, Exception e) {
if(e != null) {
e.printStackTrace();
} else {
System.out.println("The offset of the record we just sent is: " + metadata.offset());
}
}
});
这种方式有如下特点:
优点:
- 低延迟和高吞吐量:生产者可以在等待服务器响应的同时继续发送其他消息,从而实现低延迟和高吞吐量。
- 可靠性:与Fire-and-forget不同,生产者可以异步地得知消息的发送结果,并对失败的消息进行处理。
- 灵活的错误处理:生产者可以自定义回调函数来处理Kafka服务器的响应,实现灵活的错误处理机制。
缺点:
- 复杂性增加:与Fire-and-forget和Synchronous send相比,Asynchronous send需要更多的代码来处理异步操作和回调函数。
- 潜在的并发问题:如果回调函数执行时间较长,可能会影响到生产者的性能和吞吐量。