Kafka简介

我们先来看看Kafka的一些优秀特性。

● 高性能:以时间复杂度为O(1)的方式提供消息持久化能力,即使对TB级以上的数据也能保证常数时间复杂度的访问性能,同时支持离线数据处理和实时数据处理。

● 高吞吐率:即使在非常廉价的商用机器上也能做到单机支持每秒100千条以上消息的传输。

● 支持消息分区及分布式消费。

● Scale out:支持在线水平扩展。

Kafka能拥有这样优异的特性,与它的优良设计与编码是分不开的。为了在做到高性能的消息持久化及海量消息时仍能保持常数时间复杂度的访问性能,Kafka特地设计了一个精巧的消息存储系统。Kafka储存消息的文件被称为log,进入Kafka的消息被不断地追加到文件末尾,不论文件数据有多大,这个操作永远都是O(1)的,而且消息是不可变的,这种简单的log形式的文件结构很好地支撑了高吞吐量、多副本、消息快速持久化的设计目标。此外,消息的主体部分的格式在网络传输中和在log文件上是一致的,也就是说消息的主体部分可以从网络中读取后直接写入本地文件中,也可以直接从文件复制到网络中,而不需要在程序中二次加工,这有利于减少服务器端的开销。Kafka还采用了zero-copy(Java NIO File API)传输技术来提高I/O速度。如果log文件很大,那么查找位于某处的Message就需要遍历整个log文件,效率会很低。为了解决这个问题,Kafka将消息分片(Partition)、log分段(Segment)及增加索引(index)组合使用,如下图所示。

每个Topic上的消息都可以被分为N个独立的Partion(分区)存储,每个Partion里的消息的offset(可以理解为消息的ID)都从0开始不断递增,M个消息为一组并形成一个单独的Segment,每个Segment都由两个文件组成,其中start_offsetN.index为该Segment的索引文件,对应的start_offsetN.log则保存具体的Message内容。log文件的大小默认为1GB,每个Partion里的Segment文件名从0开始编号,后续每个Segment的文件名为上一个Segment文件最后一条消息的offset值,比如在上图的00000000000000368769.log文件中存储消息的offset是从368769+1开始的,直到368769+N。为了快速定位每个Segment中的某条消息,我们需要知道这个消息在此文件中的物理存储位置,即是从第几个字节开始的。上图中的00000000000000368769.index索引文件就完成了上述目标,文件中的每一行都记录了一个消息的编号与它在log文件中的存储起始位置,比如图中的3497这条记录表明Segment里的第3条消息在log文件中的存储起始位置为497。那么,要查询某个Partion里的任意消息,该如何知道去查哪个index文件呢?其实很简单,index文件名用二分法查找即可,可见编程基础多么重要。为了加速index文件的操作,又可以采用内存映射(MMAP)的方式将整个或者一部分index文件映射到内存中操作。

接下来我们说说Kafka分布式设计的核心亮点之一——消息分区。如下图所示,与之前的消息中间件不同,Kafka里Topic中的消息可以被分为多个Partion,不同的Topic也可以指定不同的分区数,这里的关键点是一个Topic的多个Partion可以分布在不同的Broker上,而位于不同节点上的Producer可以同时将消息写入多个Partion中,随后这些消息又被部署在不同的机器上对多个Consumer分别消费,因此大大增加了系统的横向扩展能力。

在消息分区的情况下,怎么确定一个消息应该进入哪个分区呢?正常情况下是通过消息的key的Hash值与分区数取模运算的结果来确定放入哪个分区的,如果key为Null,则采用依次轮询的简单方式确定目标分区。此外,我们可以自定义分区来将符合某种规则的消息发送到同一个分区。Kafka要求每个分区只能被一个Consumer消费,所以N个分区只能对应最多N个Consumer,同时,在消息分区后只能保证每个分区下消息消费的局部顺序性,不能保证一个Topic下多个分区消息的全局顺序性。在消费消息时,Consumer可以累积确认(Acknowledge)它所接收到的消息,当它确认了某个offset的消息时,就意味着之前的消息也都被成功接收到,此时Broker会更新ZooKeeper上此Broker所消费的offset记录信息,在Consumer意外宕机后,由于可能没有确认之前消费过的某些消息,因此在ZooKeeper上仍然记录着旧的offset信息,在Consumer恢复以后,最近消费的消息可能会被重复投递,这就是Kafka所承诺的“消息至少投递一次”(at-least-once delivery)的原因。

我们知道传统的消息系统有两种模型:点对点和发布-订阅模式,Kafka则提供了一种单一抽象模型,从而将这两种模型统一起来,即Consumer Group。Consumer Group可以被理解为Topic订阅者的角色,Topic中的每个分区只会被此Group中的一个Consumer消费,但一个Consumer可以同时消费Topic中多个分区的消息。比如某个Group订阅了一个具有100个分区的Topic,它下面有20个Consumer,则正常情况下Kafka平均会为每个Consumer都分配5个分区。此外,如果一个Topic被多个Consumer Group订阅,则类似于消息广播。如下图所示,4个分区的Topic中的每条消息都会被广播到Group A与Group B中。

当一个Consumer Group的成员发生变更时,例如新Consumer加入组、已有Consumer宕机或离开、分区数发生变化等,都会涉及分区与Group成员的重新配对过程,这个过程就叫作Rebalance。下图给出了新Consumer加入一个Group后的Rebalance结果。

由于消息都被保存在每个Broker的本地磁盘中,所以当某个Broker所在的机器磁盘损坏时,这些数据就永久性地丢失了。对于一个靠谱的消息系统来说,这显然是不可接受的。通过前面的学习,我们知道,分区结合副本已经成为新一代分布式系统中的经典设计套路,Kafka毫无悬念地也采用了这种设计。下图给出了一个分区结合副本模式的高可靠Kafka集群,从图中可以看到,Topic A分为两个分区(Partion 1与2),每个分区都有3个副本,其中一个副本为Leader,用于写入消息,其他副本为Follower,都从Leader同步消息,这些副本分散在4个Broker上,任何一个Broker失效,都不会影响集群的可用性,如果在这个Broker上恰好承担着某个分区的Leader角色,则通过Leader选举机制重新选择下一任Leader。

每个分区的Leader都会跟踪与其保持同步的Follower,该列表被称为ISR(即in-sync Replica),如果一个Follower宕机或者掉队太多(指Follower复制的消息落后于Leader的条数太多或者Follower的响应太慢),则Leader将它从ISR中移除。Producer在发布消息到某个Partition之前,都先通过ZooKeeper找到该Partition的Leader,然后写入消息,Leader会将该消息写入本地Log,而每个Follower都从Leader拉取消息数据,Follower存储的消息顺序与Leader保持一致。Follower在收到该消息并写入其Log后,向Leader发送ACK,一旦Leader收到了ISR中所有Follow的ACK应答,则该消息被认为已经提交,Leader随后将向Producer发送ACK,确认消息发布成功。

每个分区的Leader都会跟踪与其保持同步的Follower,该列表被称为ISR(即in-sync Replica),如果一个Follower宕机或者掉队太多(指Follower复制的消息落后于Leader的条数太多或者Follower的响应太慢),则Leader将它从ISR中移除。Producer在发布消息到某个Partition之前,都先通过ZooKeeper找到该Partition的Leader,然后写入消息,Leader会将该消息写入本地Log,而每个Follower都从Leader拉取消息数据,Follower存储的消息顺序与Leader保持一致。Follower在收到该消息并写入其Log后,向Leader发送ACK,一旦Leader收到了ISR中所有Follow的ACK应答,则该消息被认为已经提交,Leader随后将向Producer发送ACK,确认消息发布成功。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值