消息交付可靠性保障
在介绍精确一次语义之前,我们先了解下什么是消息交付可靠性保障。
所谓消息交付可靠性保障,是消息引擎对 Producer 和 Consumer 要处理的消息提供什么样的承诺。常见的承诺有以下三种:
- 最多一次(at most once):消息可能会丢失,但绝不会重复消费。
- 至少一次(at lease once):消息不会丢失,但有可能被重复发送。
- 精确一次(exactly once):消息不会丢失,也不会被重复发送。
目前,Kafka 默认提供的交付可靠性保障是第二种,即至少一次。
在 Kafka 中,只有 Broker 成功“提交”消息且 Producer 接到 Broker 的应答才会认为该消息成功发送。不过倘若消息成功“提交”,但 Broker 的应答没有成功发送回 Producer 端(比如网络出现瞬时抖动),那么 Producer 就无法确定消息是否真的提交成功了。因此,它只能选择重试,也就是再次发送相同的消息。这就是 Kafka 默认提供至少一次可靠性保障的原因,不过这会导致消息重复发送。
Kafka 也能提供对最多一次交付保障,只需要让 Producer 禁止重试即可。这样一来,消息要么写入成功,要么写入失败,但绝不会重新发送。我们通常不会希望出现消息丢失的情况,但在一些场景里偶发的消息丢失其实是被允许的,相反,消息重复消费是绝对要避免的。此时,使用最多一次交付保障就是最恰当的。
无论至少一次还是最多一次,都不如精确一次来得有吸引力。大部分用户还是希望消息只被交付一次,这样,消息不会丢失,也不会被重复处理。或者说,即使 Producer 端重复发送了相同的消息,Broker 端也能做到自动去重。在下游 Consumer 看来,消息依然只有一条。
那么问题来了,Kafka 是怎么做到精确一次的呢?简单来说,这是通过两种机制:幂等性(Idempotence)和事务(Transaction)。
幂等性 Producer
在 Kafka 中,Producer 默认不是幂等性的,但我们可以创建幂等性 Producer,只需要设置属性 enable.idempotence 为 true 即可。
不过,幂等性 Producer是有多用范围的。
首先,它只能保证单分区上的幂等性,即一个幂等性 Producer 能够保证某个主题的一个分区不出现重复消息,它无法实现多个分区的幂等性。
其次,它只能实现单会话上的幂等性,不能实现跨会话的幂等性。这里的会话,我们可以理解为 Producer 进程的一次运行。当我们重启了 Producer 进程之后,这种幂等性保证就丧失了。
如果我们想实现对分区以及多会话上的消息无重复,则需要使用事务型 Producer。
事务型 Producer
事务型 Producer 能够保证将消息原子性地写入到多个分区中。这批消息要么全部写入成功,要么全部失败。另外,事务型 Producer 也不惧进程的重启。Producer 重启回来后,Kafka 依然保证它们发送消息的精确一次处理。
f’f
设置事务型 Producer 的方法也很简单,只需配置以下两个属性:
- 和幂等性 Producer 一样,开启 enable.idempotence = true。
- 设置 Producer 端参数 transactional.id。最好为其设置一个有意义的名字。
实际上即使消息写入失败,Kafka 也会把它们写入到底层的日志中,也就是说 Consumer 还是会看到这些消息。因此在 Consumer 端,读取事务型 Producer 发送的消息也需要做一些变更:设置 isolation.level 参数的值。目前该参数有两个取值:
- read_uncommitted
这是默认值,表明 Consumer 能够读取到 Kafka 写入的任何消息,不论事务型 Producer 提交事务还是终止事务,其写入的消息都可以读取。很显然,如果我们用了事务型 Producer,那么对应的 Consumer 就不要使用这个值。 - read_committed
表明 Consumer 只会读取事务型 Producer 成功提交事务写入的消息。当然,它也能看到非事务型 Producer 写入的所有消息。
尽管从交付语义上来看,事务型 Producer 能做的更多。但是事务型 Producer 的性能要更差,在实际使用过程中,我们需要仔细评估引入事务的开销,切不可无脑地启用事务。
Exactly Once
需要强调的时,Kafka 中的 Exactly Once,和我们通常理解的消息队列的服务⽔平中的 Exactly Once 是不⼀样的。
我们通常理解消息队列的服务⽔平中的 Exactly Once,它指的是,消息从⽣产者发送到 Broker,然后消费者再从 Broker 拉取消息,然后进⾏消费。这个过程中,确保每⼀条消息恰好传输⼀次,不重不丢。
而 Kafka 中的 Exactly Once,它解决的是,在流计算中,⽤ Kafka 作为数据源,并且将计算结果保存到 Kafka 这种场景下,数据从 Kafka 的某个主题中消费,在计算集群中计算,再把计算结果保存在 Kafka 的其他主题中。这样的过程中,保证每条消息都被恰好计算⼀次,确保计算结果正确。
也就是说,Kafka 中的 Exactly Once 机制,是为了解决在“读数据-计算-保存结果”这样的计算过程中数据不重不丢,⽽不是我们通常理解的使⽤消息队列进⾏消息⽣产消费过程中的 Exactly Once。