(四)Kafka0.8.2官方文档中文版系列-消息传递语义
4.6 Message Delivery Semantic(消息传递语义)
现在我们了解了生产者和消费者的工作方式,让我们讨论Kafka在生产者和消费者之间提供的语义保证。显然,可以提供多种可能的消息传递保证:
- 最多一次 -消息可能会丢失,但永远不会被重新发送。
- 至少一次 -消息永远不会丢失,但可能会被重新发送。
- 恰好一次 - 这是人们真正想要的,每条消息只发送一次。
值得注意的是,这会分解为两个问题:发布消息的持久性保证以及消费消息时的保证。
许多系统声称提供“恰好一次”的交付语义,但阅读细则会发现,大多数这些说法具有误导性(即它们不会转化为消费者或生产者可能失败的情况,或者存在多个消费者进程情况,或者写入磁盘的数据可能丢失的情况)。
kafka的语义很直接。在发布消息时,我们有一个消息被“提交”到日志的概念。一旦已发布的消息被确认提交了;在该消息所有的副本中有一个副本所对应的分区代理中,只要有一个代理保持“活动”,它就不会丢失。alive的定义以及我们尝试处理哪些类型的故障的描述将在下一节中更详细地描述。现在让我们假设一个完美无损的broker,并尝试了解生产者和消费者对消息传递语义的保证。如果生产者尝试发布消息并遇到网络错误,并且无法确定在提交消息之前,还是之后是否发生了此错误。这类似于使用自动生成的key插入数据库表的情况。
这并不是生产者最想要表达的意思。虽然我们无法确定在网络错误的情况下发生了什么,但是可以允许生产者生成一种“主键”,使得重新生成请求是幂等的。对于复制系统而言,此功能并非易事,因为它必须在服务器发生故障的情况下能够工作。使用此功能,生产者可以重试,直到它收到成功提交的消息的确认,此时我们将保证消息仅仅只发布一次。我们希望在未来的Kafka版本中添加它。
并非所有用例都需要这样强有力的保证(每条消息只发布一次)。对于延迟敏感的用途,我们允许生产者指定它所需的耐久性等级。如果生产者指定它要等待提交的消息,则可以采用10毫秒的量级。然而,生产者也可以指定它想要完全异步地执行发送,或者它只想等到领导者(没有必要是followers)有消息。
现在让我们站在消费者的角度来描述语义。所有副本都具有完全相同的日志,具有相同的偏移量。消费者控制其在此日志中的位置(偏移量)。如果消费者从未崩溃,它可以将此位置(偏移量)存储在内存中,但如果消费者失败,此时我们希望该主题分区被另一个进程接管,则新进程将需要选择适当的位置以开始处理。假设消费者已经读取了一些消息 - 它有几种处理消息和更新其位置(偏移量)的选项(方式)。
- 它可以读取消息,先将其位置(日志偏移量)保存在日志中,后处理消息。在这种情况下,消费者进程可能在保存其位置之后,但在保存其消息处理的输出之前崩溃。在这种情况下,接管处理的过程将从保存的位置开始,即使该位置之前的一些消息尚未处理。这对应于“最多一次”语义,因为在消费者失败的情况下可能不会处理消息。
- 它可以读取消息,先处理消息,后保存其位置。在这种情况下,消费者进程可能在处理消息之后但在保存其位置之前崩溃。在这种情况下,当新进程接管它收到的前几条消息时,它们已经处理完毕。这对应于消费者失败情况下的“至少一次”语义。在许多情况下,消息具有主键,因此更新是幂等的(两次接收相同的消息只是用另一个自身的副本覆盖记录而已)。
- 那么恰好一次语义(即你真正想要的东西)呢?这里的限制实际上并不是消息传递系统的一个特征,而是需要协调消费者的位置与实际存储为输出的位置。实现这一目标的经典方法是在消费者位置的存储和消费者输出的存储之间引入两阶段提交(2PC)。但是,可以有一种更简单更普遍的方式来处理这个问题,通过让消费者将其偏移量存储在与其输出相同的位置。这是更好的一种选择,因为消费者可能想要写入的许多输出系统可能不支持两阶段提交。举一个例子,我们通过hadoop ETL产生的数据存储在HDFS上,HDFS中的数据偏移量与其读取的数据是存储在一起的,以确保数据和偏移量都得到更新或两者都不更新。我们遵循许多其他数据系统的类似模式,这些数据系统需要这些更强的语义,并且消息没有主键以用来进行重复数据删除。
因此,Kafka保证默认情况下至少一次交付,并允许用户通过禁用生产者的重试并在处理一批消息之前提交其偏移量,实施最多一次交付。完全一次交付需要与目标存储系统合作,但Kafka提供了偏移,这使得实现这一点变得更加简洁方便。