在分布式环境中,故障是一种非常常见的情况,随时可能发生。在 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 个属性
- enable.idempotence=true(地址 a、b 和 c)
- MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=5(生产者每次连接总是有一个进行中的请求)
- 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 自动提供以下参数,您无需明确设置
- isolation.level=read_committed
- enable.idempotence=true
- MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION=5