十二生肖排列顺序topic在逻辑上可以被理解为一个队列,消息必须指明它的topic,可以理解为消息必须指定放到哪一个队列中。为了提高kafka的吞吐率,物理上把topic分成一个或多个partion,每个partion物理上对应一个文件夹。该文件夹下存储该partition下的消息及索引文件。
若创建两个topic,topic1和topic2,每个topic对应有13个和19个分区,其中集群有8个结点,则集群中会创建32个文件夹。如下图所示:
每个日志文件都是一个log entry序列,每个log entry序列包含一个四字节整形值(消息长度,1+4+n),一字节magic value,四字节的crc校验码,n字节的消息体长度组成。每条消息都有在当前partition下的唯一的64字节的offset。它指明了消息的的存储,磁盘上消息的存储格式如下:
对于传统的消息系统,通常会删除已经消费过的消息,kafka会保存已经消费的消息。并且根据实际情况对已经消费的消息提供两种删除策略,分别是基于消息的消费时间以及partition文件的大小。
kafka采用push机制来推送消息到broker,pull机制来消费消息,push与pull机制各由优缺点。kafka采取pull机制消费消息可以简化broker的设计,push机制采取尽快的投递消息,
这样很可能导致consumer来不及处理消息从而导致网络拥塞或者服务,通过consumer自己来控制何时消费消息。即可批量消费又可逐条消费,能够选择不同的提交方式,从而实现不同传输语义。
当Producer向broker发送消息时,一旦这条消息被commit,因数replication的存在,它就不会丢。但是如果Producer发送数据给broker后,遇到网络问题而造成通信中断,那Producer就无法判断该条消息是否已经commit。虽然Kafka无法确定网络故障期间发生了什么,但是Producer可以生成一种类似于主键的东西,发生故障时幂等性的重试多次,这样就做到了Exactly once。截止到目前(Kafka 0.8.2版本,2015-03-04),这一Feature还并未实现,有希望在Kafka未来的版本中实现。(所以目前默认情况下一条消息从Producer到broker是确保了At least once,可通过设置Producer异步发送实现At most once)。
当Producer向broker发送消息时,一旦这条消息被commit,因数replication的存在,它就不会丢。但是如果Producer发送数据给broker后,遇到网络问题而造成通信中断,那Producer就无法判断该条消息是否已经commit。虽然Kafka无法确定网络故障期间发生了什么,但是Producer可以生成一种类似于主键的东西,发生故障时幂等性的重试多次,这样就做到了Exactly once。截止到目前(Kafka 0.8.2版本,2015-03-04),这一Feature还并未实现,有希望在Kafka未来的版本中实现。(所以目前默认情况下一条消息从Producer到broker是确保了At least once,可通过设置Producer异步发送实现At most once)。
读完消息先commit再处理消息。这种模式下,如果Consumer在commit后还没来得及处理消息就crash了,下次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于At most once
读完消息先处理再commit。这种模式下,如果在处理完消息之后commit之前Consumer crash了,下次重新开始工作时还会处理刚刚未commit的消息,实际上该消息已经被处理过了。这就对应于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可以非常直接非常容易得使用这种方式。