在消息队列系统中,有如下几种可能的消息传递机制(delivery guarantee):
1)At most once:消息可能会丢,但绝不会重复传输。
2)At least one:消息绝不会丢,但可能会重复传输。
3)Exactly once:每条消息肯定会被传输且仅传输一次,很多时候这是用户所想要的。
当Producer向Broker发送消息时,一旦这条消息被Commit,因Replication机制的存在,消息就不会丢。但是如果Producer发送数据给Broker,遇到网络问题而造成通信中断,那么Producer就无法判断该条消息是否已经Commit。虽然Kafka无法确定网络故障期间发生了什么,但是Producer可以生成一种类似于主键的东西,在发生故障时,幂等性地重试,这样就做到了Exactly once。到目前为止(Kafka 0.8.2.1版本),这一Feature还并未实现,有希望在Kafka未来的版本中实现。目前,默认情况下一条消息从Producer到Broker确保了At least once,可通过设置Producer异步发送实现At most once。
接下来讨论的是消息从Broker到Consumer High Level的delivery guarantee语义。Con⁃sumer在从Broker读取消息后,可以选择Commit,该操作会在Zookeeper中保存该Consumer在该Partition中读取的消息的offset。该Consumer下一次再读该Partition时会从下一条开始读取。如未Commit,下一次读取的开始位置会跟上一次Commit之后的开始位置相同。当然可以将Consumer设置为Autocommit,即Consumer一旦读到数据,立即自动Commit。如果只讨论这一读取消息的过程,那么Kafka确实确保了Exactly once。但实际使用中应用程序并非在Consumer读取完数据就结束了,而是要进行进一步处理,而数据处理与Commit的顺序在很大程度上决定了消息从Broker和Consumer的delivery guarantee语义。读完消息先Commit再处理。(www.daowen.com)
在这种模式下,如果Consumer在Commit后还没来得及处理消息就宕机了,下次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于At most once。读完消息先处理再Commit,在这种模式下,如果在处理完消息之后Commit,之前Consumer宕机了,下次重新开始工作时还会处理已提交未处理的消息,实际上该消息已经被处理过了,这就对应于At least once。
在很多使用场景下,消息都有一个主键,所以消息的处理往往具有幂等性,即多次处理这一条消息,与仅处理一次是等效的,那就可以认为是Exactly once。毕竟它不是Kafka本身提供的机制,主键本身也并不能完全保证操作的幂等性。而且实际上说delivery guarantee语义是讨论被处理多少次,而非处理结果怎样。因为处理方式多种多样,大家不应该把处理过程的一些特性———如是否具备幂等性,当成Kafka本身的Feature。如果一定要做到Exactly once,就需要协调offset和实际操作的输出。
精典的做法是引入两阶段提交,如果能让offset和操作输入存在同一个地方,会更简洁和通用。这种方式可能更好,因为许多输出系统可能不支持两阶段提交。比如,Consumer拿到数据后可能把数据放到HDFS,如果把最新的offset和数据本身一同写到HDFS,那么就可以保证数据的输出和offset的更新或者都完成,或者都不完成,间接实现Exactly once。目前就high level API而言,offset是存于Zookeeper中的,无法存于HDFS中,而low level API的offset是由自己去维护的,可以将之存于HDFS中。
总之,Kafka默认保证At least once,并且允许通过设置Producer异步提交来实现At most once。而Exactly once要求与外部存储系统协作,幸运的是,Kafka提供的offset可以非常直接、非常容易地使用这种方式。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。