Message Delivery Semantics

  • At most once —— Messages may be lost but are never redelivered(消息可能丢失但不会重复投递)
  • At least once —— Messages are never lost but may be redelivered(消息不会丢失但可能重复投递)
  • Exactly once —— this is what people actually want, each message is delivered once and only once(消息只投递一次)

许多系统都声称提供"exactly once"投递,但是仔细阅读很重要,大部分这种宣称都是误导(他们没有考虑生产者和消费者可能失败的情况,以及多个消费者进程同时处理的情况,还有写到磁盘上的数据可能丢失的情况)

Kafka的消息投递语义是直接的。发布消息的时候我们有一个概念叫消息被提交到日志。一旦被发布的消息被提交到日志,只要有一个这个消息所在分区的broker活着消息就不会丢失。从0.11.0.0版本以后,Kafka生产者支持幂等投递选项,以保证即使消息被重新发送,日志中也不会有重复的条目。为了实现这一点,broker给没用个生成者指定一个ID并且每条消息指定一个序列号。

并不是所有的情况都需要这样强的保证。

现在,让我们站在消费者的角度来看这个语义。消费者用日志控制它的位置。如果消费者没有崩溃它仅仅只是在内存中存储这个位置,但如果消费者失败了,我们想要另一个进程来接管这个分区,那么这个新的进程需要选择一个合适的位置开始处理。让我们来看一下消费者读取消息后处理消息和更新位置的几种选项。

  1. It can read the messages, then save its position in the log, and finally process the messages.(读取消息,然后在日志中保存位置,最后处理消息)。这种情况有一种可能就是消费者在保存位置之后就崩溃了。这情况下接管的进程会从保存的那个位置开始处理,即使在这个位置之前有一些消息没有被处理也不管了。这与"at-most-once"的语义是一致的,在这种情况下,消费者处理失败的那些消息就不会被处理了。
  2. It can read the messages, process the messages, and finally save its position.(读取消息,然后处理消息,最后保存位置)。这种情况下有一种可能是消费者在处理消息之后保存位置之前就崩溃了。这种情况下,新的进程接管以后会接收到一些之前已经被处理过的消息。这与"at-least-once"的语义是一致的。在许多情况下,消息有一个主键,以至于更新是幂等的(接收相同的消息两次,重写日志记录)。

 

The consumer's position is stored as a message in a topic, so we can write the offset to Kafka in the same transaction as the output topics receiving the processed data. If the transaction is aborted, the consumer's position will revert to its old value and the produced data on the output topics will not be visible to other consumers, depending on their "isolation level." In the default "read_uncommitted" isolation level, all messages are visible to consumers even if they were part of an aborted transaction, but in "read_committed," the consumer will only return messages from transactions which were committed (and any messages which were not part of a transaction).

 

当从主题消费并生产到另一个主题的时候,我们可以用事务生产者。消费者的位置作为消息被存储在topic中,以至于我们可以在接收处理数据的那个事务中将offset写到kafka。如果事务被中止,消费者的位置会恢复成旧值并且生产的数据对其它消费者不可见,这取决于隔离级别。默认的隔离级别是"read_uncommitted"表示消息对所有消费者可见,即使有些消息来自被中止的事务。

 

总结

1、消息投递语义

  最多一次:可能丢失消息但不会重复投递

  最少一次:不会丢失消息但可能重复投递

  精确一次:只会投递一次

2、kafka给每个生产者指定一个ID,每个发布的消息一个序列号,这样的话即使生产者重复发送消息,在提交日志中也不会有重复记录

3、站在消费者的角度,先保存位置后处理消息就是“最多一次”;先处理消息后保存位置就是“最少一次”;至于“精确一次”,可以使用事务生产者来实现,即在同一个事务中接收并处理消息,将位置(offset)保存到另一个topic中。只要事务成功了,皆大欢喜,若事务失败,则位置恢复。

 

参考 http://kafka.apache.org/documentation/#design