交付(传递)语义
交付语义 是在分布式消息系统(如 Kafka)中,用来描述消息从生产者到达消息系统并最终被消费者消费时的可靠性保证。它主要涉及到消息是否能正确地被投递,及在什么情况下可能会出现消息丢失或重复的问题。
根据Kafka broker和生产者的配置,支持“最多一次”、“至少一次”和“恰好一次”三种传递语义。
为了让这个概念更容易理解,可以把它比作邮寄信件时的几种可能性。
- 最多一次(At Most Once):
- 至少一次(At Least Once):
- 有且仅一次(Exactly Once):
最多一次
在最多一次传递语义中,生产者在发送消息后不会做太多的检查,如果消息因为网络问题或者系统崩溃没有送达,那这条消息就永远丢失了。换句话说,消息要么送达,要么不送达,但绝不会重复发送。
在这种交付语义下,系统只尝试发送一次消息,不需要处理复杂的重试逻辑,也不需要复杂的检查和确认机制。因此系统具有更高的吞吐量和更低的延迟。
因此这种交付语义适用于指标收集、日志收集等场景,这是因为:
- 指标收集:在收集大量的指标数据时,丢失少量数据不会对总体分析产生重大影响,但保证高吞吐量和低延迟非常重要。
- 日志收集:在日志收集中,丢失某些日志条目可能不会对系统的整体健康造成重大影响,重点是确保日志的实时性和收集的效率。
至少一次
在至少一次交付语义中,可以多次传递一条消息,但不应丢失任何消息。生产者确保所有消息都已传递,即使这可能导致消息重复。这是所有语义中最受欢迎的。采用至少一次语义的应用程序可能具有中等吞吐量和中等延迟。
有且仅一次
在“有且仅一次”交付语义中,一条消息只能传递一次,并且不能丢失任何消息。这是所有传递语义中最困难的。与其他两种语义相比,采用“有且仅一次”语义的系统需要额外的复杂性来确保消息的唯一性和正确处理,通常涉及去重机制和精确的确认机制。
因此这种交付语义适用于金融交易系统、订单处理系统等场景,这是因为:
- 金融交易系统:在金融交易中,确保每笔交易被处理恰好一次是至关重要的。重复处理可能会导致资金错误或不准确的记录,而丢失交易会导致财务损失或交易失败。
- 订单处理系统:在处理订单时,确保每个订单只处理一次是重要的。如果订单被处理多次,可能会导致多次发货或库存错误。相反,丢失订单会导致客户不满和销售损失。实现“有且仅一次”可以避免这些问题。
交付语义总结
下表总结了所有交付语义的行为。
Kafka生产者交付语义
在 Kafka 中可以使用生产者的 Acks 属性和broker的 min.insync.replica 属性实现不同的交付语义。
具体来说,acks属性决定了生产者在发送消息时等待多少确认,而min.insync.replicas 属性决定了一个分区必须有多少个同步副本才能接受写操作。
acks = 0
当 acks 属性设置为0时,生产者将获得最多一次交付语义。Kafka 生产者将消息发送给broker,不会等待任何确认响应。在此设置下,一旦发送的消息丢失也不会重试。
这种语义下,生产者采用“发送并忘记”的方式。这意味着生产者只负责将消息发送出去,而不关心消息是否成功存储或是否需要处理失败的情况。
数据丢失可能发生在此语义下,由于生产者无法确认消息是否已被代理收到,因此数据丢失的可能性很高。消息可能甚至还没有到达broker,或者消息传递后不久broker发生故障,从而导致数据丢失。
因此这种交付语义适合对丢失消息容忍度较高、需要低延迟的场景,如某些日志记录或监控系统。
acks = 1
当 acks 属性设置为 1 时,您可以实现至少一次交付语义。Kafka 生产者将记录发送给broker并等待领导者分区的响应。如果没有收到对所发送消息的确认,则生产者将根据重试配置(retries 属性)重试发送消息。重试属性默认为 0,请确保将其设置为所需数字或 Max.INT。
在此语义下,数据丢失的风险较低,但不是完全消除。例如,在生产者收到确认响应后,消息还需复制到追随者分区。如果在确认之后、复制之前broker发生故障,可能会导致数据丢失,因为生产者本身不会自动重新发送这些消息。
因此这种交付语义适合需要确保消息传递的场景,如金融交易系统其中丢失消息不被接受,但重复消息是可以的(消费者去重处理即可)。
Acks = all(-1)
当 acks 属性设置为 all 时,可以实现仅一次的交付语义。这个配置的工作流程如下:
-
acks设置为 all:在这种模式下,生产者发送消息后,会等待来自broker的确认。broker在确保ISR中 所有的副本都已成功写入后,才会发送确认响应。
-
重试机制:如果生产者在设定的时间内没有收到确认响应,它会根据重试配置(设置的重试次数)重新尝试发送消息。确保生产者不会因为偶发的网络问题或临时故障而丢失消息。
-
复制设置:min.insync.replicas 表示在处理请求时需要存在的最少同步副本数量。这样,即使某些副本发生故障,只要剩余副本能够正常工作,数据也不会丢失。
-
幂等性:为了实现**“恰好一次”**的交付语义,broker必须支持幂等性(配置enable.idempotence=true)。 幂等性确保即使生产者重试发送消息,消息也不会被重复写入,避免重复数据的问题。
//开启冥等性
//生产者代码中设置
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, "true"); // 启用幂等性
props.put(ProducerConfig.ACKS_CONFIG, "all"); // 设置acks为all以确保数据可靠性
在 acks 设置为 all 的模式下,数据丢失的可能性非常低。即使broker发生故障,生产者在确认之前不会收到确认响应,并会将消息重新发送到新选出的领导者分区。这种方式大大减少了消息丢失的风险。
注意:这里再次强调min.insync.replicas 这个配置,它是broker上的配置,它指定了当acks=all时,ISR 中必须存在的最少同步副本数量。如果 ISR 中的同步副本数量小于这个值,写入操作将会失败。
误区:
一个常见的误解是:认为min.insync.replicas表示有多少副本需要收到消息才能让leader响应ack给生产者。
事实并非如此,实际上,min.insync.replicas表示在处理请求时需要存在的最少同步副本数量。
以下图为例。即使配置了min.insync.replicas=2,leader也不会在仅有2个副本确认的情况下响应ack给生产者,而是等待所有3个副本确认才会响应ack给生产者。