赞
踩
Apache Kafka无疑是一个伟大的发明,可以进行消息削峰、异步处理、大规模流式处理等,如果应用得当,还可以进行高可用的可靠消息投递,进行电商秒杀、金融级别的异步扣库存、异步生成订单、履约等等。Kafka exactly once语义是一个很大的话题,在生产环境如何保证exactly once语义的消息投递?或者是面试官问你这个问题,如果你只是说把生产者设置成acks=all,开启幂等enable.idempotence=true,然后客户端进行去重,那大概率凉凉,下面我们来详细剖析一下Kafka exacly once语义是如何保证的。
首先生产者得保证向broker仅投递一次消息,消息不能丢失,也不能重复。消息不丢失如何保证?Kafka有TCP一样的重传机制,如果producer在规定时间内未收到brocker的ack确认号,则会进行重传,这里涉及producer的三个关键参数:
acks指定了生产者在多少个分区副本收到消息的情况下才会认为消息写入成功。在默认情况下,Kafka会在首领副本收到消息后向客户端回应消息写入成功。这个参数对写入消息的持久性有重大影响,对于不同的场景,使用默认值可能不是最好的选择。
你会发现,为acks设置的值越小,生产者发送消息的速度就越快。也就是说,我们通过牺牲可靠性来换取较低的生产者延迟。不过,端到端延迟是指从消息生成到可供消费者读取的时间,这对3种配置来说都是一样的。为什么?
开启acs=all之后,可以保证消息不丢失,也就是at least once语义,如果brocker把消息写入日志,但是由于网络原因,producer未收到ack,此时发生重传,broducer就会持久化两条消息,concumer对应的也会消费到两次,如何避免这种情况?
设置enable.idempotence=true
如果启用了幂等生产者,那么每条消息都将包含生产者ID(PID)和序列号。我们将它们与目标主题和分区组合在一起,用于唯一标识一条消息。broker会用这些唯一标识符跟踪写入每个分区的最后5条消息。为了减少每个分区需要跟踪的序列号数量,生产者需要将max.inflight.requests设置成5或更小的值(默认值是5),这个参数指定了生产者在收到服务器响应之前可以发送多少个消息批次。如果broker收到之前已经收到过的消息,那么它将拒绝这条消息,并返回错误。
对于使用同一条消息调用两次producer.send()就会导致消息重复的情况,即使使用幂等生产者也无法避免。这是因为生产者无法知道这两条消息实际上是一样的。对于第二个情况,我们可以通过事务解决,除此之外,事务还能确保事务生产者发送多条消息的原子性(要么都成功,要么都失败)。
为保证Streams应用程序的正确性,Kafka中加入了事务机制。为了让流式处理应用程序生成正确的结果,要保证每个输入的消息都被精确处理一次,即使是在发生故障的情况下。一些流式处理应用程序对准确性要求较高,特别是如果处理过程包含了聚合或连接操作,那么事务对它们来说就会非常有用。
如果broker期望消息2后面跟着消息3,但收到了消息27,那么这个时候该怎么办?在这种情况下,broker将返回“乱序”错误。如果使用了不带事务的幂等生产者,则这个错误可能会被忽略。
事务也支持为Consumer设置不同的隔离级别。
假设有一个简单的流式处理应用程序:它从源主题读取消息,然后可能会对消息做一些处理,再将结果写入另一个主题。我们想要确保处理的每一条消息的结果只被写入一次。那么,哪些地方有可能出错呢?
事务是如何保证精确一次性的?
事务引入了事务协调器和两阶段提交来完成原子多分区写入。当生产者要提交一个事务时,它会发送“提交”消息给事务协调器,事务协调器会将提交标记写入所有涉及这个事务的分区。如果生产者在向部分分区写入提交消息后发生崩溃,该怎么办?Kafka事务使用两阶段提交和事务日志来解决这个问题。
在开始第一个事务之前,生产者需要通过调用initTransaction()来注册自己。这个请求会被发送给一个broker,它将成为这个事务性生产者的事务协调器。就像每一个broker都是部分消费者群组的消费者群组协调器一样,每一个broker都是部分生产者的事务协调器。Kafka需要一个事务日志,这里使用了一个叫作 __transaction_state的内部主题,来记录事务的状态,这个算法会执行如下步骤。
initTransaction()API注册了一个带有新事务ID的协调器或者增加现有事务ID的epoch,用以隔离变成“僵尸”的旧生产者。当epoch增加时,挂起的事务将被中止。
我们通过设置isolation.level参数来控制消费者如何读取以事务方式写入的消息。如果设置为read_committed,那么调用consumer.poll()将返回属于已成功提交的事务或以非事务方式写入的消息,它不会返回属于已中止或执行中的事务的消息。默认的隔离级别是read_uncommitted,它将返回所有记录,包括属于执行中或已中止的事务的记录。配置成read_committed并不能保证应用程序可以读取到特定事务的所有消息。也可以只订阅属于某个事务的部分主题,这样就可以只读取部分消息。此外,应用程序无法知道事务何时开始或结束,或者哪些消息是哪个事务的一部分。
因此Kafka事务也是支持设置隔离级别,下次用到聊到别再两眼一抹黑了!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。