【问题标题】:Difference between idempotence and exactly-once in Kafka StreamKafka Stream中幂等性和exactly-once的区别
【发布时间】:2020-03-12 15:00:41
【问题描述】:

我正在阅读我理解的文档,我们可以通过启用idempotence=true 实现一次性交易

idempotence:幂等生产者只启用一次 生产者针对单个主题。基本上每条消息都会发送 有更强的保证,并且不会重复,以防万一 错误

因此,如果我们已经具有幂等性,那么为什么我们需要在 Kafka Stream 中精确一次的另一个属性?幂等性与完全一次有什么区别

为什么在普通的 Kafka Producer 中没有exactly-once 属性?

【问题讨论】:

标签: apache-kafka apache-kafka-streams


【解决方案1】:

在分布式环境中,故障是一种非常常见的情况,随时可能发生。在 Kafka 环境中,broker 可能会崩溃、网络故障、处理失败、发布消息失败或消费消息失败等。 这些不同的场景引入了不同类型的数据丢失和重复。

失败场景

A(Ack Failed): Producer 发布消息成功,retry>1,但由于失败无法收到确认。在这种情况下,生产者将重试可能引入重复的相同消息。

B(生产者进程在批量消息中失败):生产者发送一批消息失败,几乎没有发布成功。在这种情况下,一旦生产者重新启动,它将再次重新发布批处理中的所有消息,这将在 Kafka 中引入重复。

C(Fire & Forget Failed) 生产者发布消息,retry=0(fire and forget)。如果发布失败,将不知道并发送下一条消息,这将导致消息丢失。

D(Consumer failed in batch message) 消费者从 Kafka 接收一批消息并手动提交它们的偏移量 (enable.auto.commit=false)。如果消费者在提交到 Kafka 之前失败了,下次消费者将再次消费相同的记录,在消费者端复制重复。

Exactly-Once 语义

在这种情况下,即使生产者尝试重新发送消息,它也会导致 消息将被消费者发布和消费一次。

为了在 Kafka 中实现 Exactly-Once 语义,它使用以下 3 个属性

  1. enable.idempotence=true(地址 a、b 和 c)
  2. MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=5(生产者每次连接总是有一个进行中的请求)
  3. isolation.level=read_committed(地址 d)

启用幂等(enable.idempotence=true)

幂等传递使生产者能够准确地将消息写入 Kafka 在主题的生命周期内访问一次主题的特定分区 单个生产者没有数据丢失和每个分区的顺序。

"请注意,启用幂等性要求 MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION 小于或等于 5,RETRIES_CONFIG 大于 0 并且 ACKS_CONFIG 为 'all'。如果用户没有明确设置这些值,则合适的值将是选择。如果设置了不兼容的值,则会抛出 ConfigException"

为了实现幂等性,Kafka 在生成消息时使用一个唯一的 ID,称为产品 ID 或 PID 和序列号。生产者不断增加发布的每条消息的序列号,该消息映射具有唯一的 PID。代理总是将当前序列号与前一个序列号进行比较,如果新序列号不比前一个序列号大 +1,它会拒绝,这可以避免重复,如果超过更大的显示在消息中丢失,则在同一时间

在失败场景中,broker 会将序列号与前一个序列号进行比较,如果序列号没有增加,+1 将拒绝该消息。

事务(isolation.level)

事务使我们能够以原子方式更新多个主题分区中的数据。交易中包含的所有记录都将成功保存,否则将不保存。它允许您在同一个事务中提交您的消费者偏移量以及您已处理的数据,从而允许端到端的精确一次语义。

生产者不等待向 Kafka 写入消息,而生产者使用 beginTransaction、commitTransaction 和 abortTransaction(以防失败) 消费者使用隔离级别 read_committed 或 read_uncommitted

  • read_committed:消费者将始终只读取已提交的数据。
  • read_uncommitted:按偏移顺序读取所有消息,无需等待 用于提交的事务

如果一个具有isolation.level=read_committed 的消费者到达一个尚未完成的事务的控制消息,它不会再从这个分区传递任何消息,直到生产者提交或中止事务或发生事务超时。事务超时由生产者使用配置 transaction.timeout.ms(默认 1 分钟)确定。

Exactly-Once 在生产者和消费者中

在正常情况下,我们有单独的生产者和消费者。生产者必须幂等并同时管理事务,因此消费者可以使用isolation.level 只读read_committed 以使整个过程成为原子操作。 这保证了生产者将始终与源系统同步。即使生产者崩溃或事务中止,它始终是一致的,并且将一条消息或一批消息作为一个单元发布一次。

同一消费者将收到一条消息或一批消息作为一个单元一次。

在 Exactly-Once 语义 Producer 和 Consumer 中将显示为 将作为一个单元运行的原子操作。要么发布和 完全消耗一次或中止。

恰好在 Kafka Stream 中一次

Kafka Stream 使用来自主题 A 的消息,处理并将消息发布到主题 B,一旦发布,使用提交(提交主要是秘密运行)将所有状态存储数据刷新到磁盘。

Kafka Stream 中的 Exactly-once 是一种读-处理-写模式,可保证此操作将被视为原子操作。由于 Kafka Stream 同时满足了生产者、消费者和事务的需求,Kafka Stream 提供了特殊的参数 processing.guarantee,它可以为 exact_once 或 at_least_once,这使得生活变得容易,无需单独处理所有参数。

Kafka Streams 以原子方式更新消费者偏移量、本地状态存储、 状态存储更改日志主题和生产以输出所有主题 一起。如果这些步骤中的任何一个失败,所有的更改都是 回滚。

processing.guarantee:exact_once 自动提供以下参数,您无需明确设置

  1. isolation.level=read_committed
  2. enable.idempotence=true
  3. MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=5

【讨论】:

    【解决方案2】:

    Kafka 流从 端到端 的角度提供了一次性语义(从一个主题消费,处理该消息,然后生成到另一个主题)。但是,您只提到了 生产者的 幂等属性。这只是整个画面的一小部分。

    让我重新表述这个问题:

    为什么我们需要消费者端的一次性交付语义 虽然我们已经在 制作方?

    答案: 因为一次性交付语义不仅在生产步骤,而且在整个处理流程中。要在语义上实现exactly-once交付,生产和消费必须满足一些条件。

    这是一般场景:进程 A 向主题 T 生成消息。同时,进程 B 尝试使用来自主题 T 的消息。我们希望确保进程 B 不会两次处理一条消息。

    生产者部分:我们必须确保生产者永远不会产生两次消息。我们可以使用Kafka Idempotent Producer

    消费者部分: 以下是消费者的基本工作流程:

    • 第一步:消费者从Kafka的topic中成功拉取消息M。
    • 第 2 步:消费者尝试执行作业,作业成功返回。
    • 第 3 步:消费者将消息的偏移量提交给 Kafka 代理。

    以上步骤只是一条快乐的道路。现实中会出现很多问题。

    • 场景 1:步骤 2 中的作业成功执行,但随后消费者崩溃。由于这种意外情况,消费者尚未提交消息的偏移量。当消费者重启时,消息会被消费两次。
    • 场景 2:当消费者在第 3 步提交偏移量时,由于硬件故障(例如:CPU、内存冲突等)而崩溃(例如:CPU、内存冲突……)重新启动时,消费者无法知道它已成功提交偏移量或不是。

    因为可能会发生很多问题,所以作业的执行和提交偏移必须是原子的,以保证消费者端的一次性交付语义。这并不意味着我们不能,但要确保一次性交付语义需要付出很多努力。 Kafka Stream 支持工程师的工作。

    注意: Kafka Stream 提供“exactly-once 流处理”。它指的是从一个主题消费,在一个Kafka主题中实现中间状态并产生一个。如果我们的应用程序依赖于其他一些外部服务(数据库、服务...),我们必须确保我们的外部依赖关系能够保证在这些情况下完全一次。

    TL,DR:exactly-once for full flow需要生产者和消费者之间的合作。

    参考资料:

    【讨论】:

    • 我不会称它为delivery,因为delivery 通常意味着消息被读取/发送的频率,而在Kafka 中,Exactly-once 显然会因失败而在内部退出。仅一次 deliver(即通过网络实际发送消息的频率)显然是不可能的(参见 en.wikipedia.org/wiki/Byzantine_fault 和 en.wikipedia.org/wiki/Two_Generals%27_Problem)
    • 是的。正如答案中提到的,Kafka Stream 确实不提供一般意义上的一次性交付。关于两个一般问题,在分布式系统中,我们不能有完全一次性的一般,但是当我们失去一些条件或向系统添加更多条件时,它是可行的。例如:超时。然而,情况就不同了。
    • 好吧,我不会使用术语delivery,而是坚持使用semantics。
    猜你喜欢
    • 2019-07-23
    • 1970-01-01
    • 2019-08-25
    • 2019-12-10
    • 2019-11-15
    • 1970-01-01
    • 2019-06-23
    • 2020-08-20
    • 1970-01-01
    相关资源
    最近更新 更多