架构

  • Producer:消息⽣产者,向 Kafka Broker 发消息的客户端。
  • Consumer:消息消费者,从 Kafka Broker 取消息的客户端。Kafka支持持久化,生产者退出后,未消费的消息仍可被消费。
  • Consumer Group:消费者组(CG),消费者组内每个消费者负责消费不同分区的数据,提⾼消费能⼒。⼀个分区只能由组内⼀个消费者消费,消费者组之间互不影响。所有的消费者都属于某个消费者组,即消费者组是逻辑上的⼀个订阅者。
  • Broker:⼀台 Kafka 机器就是⼀个 Broker。⼀个集群(kafka cluster)由多个 Broker 组成。⼀个 Broker 可以容纳多个 Topic。
  • Controller:由zookeeper选举其中一个Broker产生。它的主要作用是在 Apache ZooKeeper 的帮助下管理和协调整个 Kafka 集群。
  • Topic:可以理解为⼀个队列,Topic 将消息分类,⽣产者和消费者⾯向的是同⼀个 Topic。
  • Partition:为了实现扩展性,提⾼并发能⼒,⼀个⾮常⼤的 Topic 可以分布到多个 Broker上,⼀个 Topic 可以分为多个 Partition,同⼀个topic在不同的分区的数据是不重复的,每个 Partition 是⼀个有序的队列,其表现形式就是⼀个⼀个的⽂件夹。不同Partition可以部署在同一台机器上,但不建议这么做。
  • Replication:每⼀个分区都有多个副本,副本的作⽤是做备胎。当主分区(Leader)故障的时候会选择⼀个备胎(Follower)上位,成为Leader。在kafka中默认副本的最⼤数量是10个,且副本的数量不能⼤于Broker的数量,follower和leader绝对是在不同的机器,同⼀机器对同⼀个分区也只可能存放⼀个副本(包括⾃⼰)。
  • Message:每⼀条发送的消息主体。
  • Leader:每个分区多个副本的“主”副本,⽣产者发送数据的对象,以及消费者消费数据的对象,都是 Leader。
  • Follower:每个分区多个副本的“从”副本,使用发布订阅模式主动拉取Leader的数据(与redis不同),实时从 Leader 中同步数据,保持和 Leader 数据的同步。Leader 发⽣故障时,某个 Follower 还会成为新的 Leader。
  • Offset:消费者消费的位置信息,监控数据消费到什么位置,当消费者挂掉再重新恢复的时候,可以从消费位置继续消费。
  • ZooKeeper:Kafka 集群能够正常⼯作,需要依赖于 ZooKeeper,ZooKeeper 帮助 Kafka存储和管理集群信息。
  • High Level API 和Low Level API :高水平API,kafka本身定义的行为,屏蔽细节管理,使用方便;低水平API细节需要自己处理,较为灵活但是复杂。

工作流程

Kafka集群将 Record 流存储在称为 Topic 的类中,每个记录由⼀个键、⼀个值和⼀个时间戳组成。
Kafka 中消息是以 Topic 进⾏分类的,⽣产者⽣产消息,消费者消费消息,⾯向的都是同⼀个Topic。Topic 是逻辑上的概念,⽽ Partition 是物理上的概念,每个 Partition 对应于⼀个 log ⽂件,该log ⽂件中存储的就是 Producer ⽣产的数据。Producer ⽣产的数据会不断追加到该 log ⽂件末端,且每条数据都有⾃⼰的 Offset。消费者组中的每个消费者,都会实时记录⾃⼰消费到了哪个 Offset,以便出错恢复时,从上次的位置继续消费。

存储机制

由于⽣产者⽣产的消息会不断追加到 log ⽂件末尾,为防⽌ log ⽂件过⼤导致数据定位效率低下,Kafka 采取了分⽚和索引机制。它将每个 Partition 分为多个 Segment,每个 Segment 对应两个⽂件:.index索引⽂件和 .log 数据⽂件。这种索引思想值得我们学习应用到平时的开发中。

怎么避免重复消费

出现重复消费的情况:

  • Broker存储的消息都有Offset标记,消费者通过Offset标记维护当前已经消费的数据,每消费一批数据,就更新一次。消费端默认5秒后向Broker获取消息时,自动提交Offset。当消费者消费时,应用程序宕机,可能导致Offset没有提交,产生重复消费。
  • Partition Balance机制,把多个Partition均衡的分给多个消费者。如果Consumer在默认的5分钟内没有处理完这一批消息,就会触发Kafka的Rebalance机制,导致Offset提交失败。Rebalance后,Consumer端继续从未提交的Offset位置进行消费。

解决方法:

  • 提高消费端处理性能,避免处罚Balance
    1. 采用异步的方式处理消息,缩短单个消息消费的时长。
    2. 调整消息处理的超时时间。
    3. 减少一次性从Broker上拉取的数据条数。
  • 针对消息生成md5然后保存到mysql或者redis里,在处理消息之前先去mysql或者redis藜麦牛奶判断是否已经消费过。

如何保证信息不丢失

  1. Producer端确保消息能够到达Broker,并且实现消息的存储。这过程如果发生网络问题,有可能消息丢失。

    解决方法:

    • 异步发送改为同步发送,这样Producer就能实时知道消息发送的结果。
    • 添加异步回调函数来监听消息发送的结果,如果发送失败,可以在回调中重试。
    • 重试参数retriesProducer自动重试。
  2. Broker端将消息持久化到磁盘。Kafka为提升性能,采用异步批量的实现机制,按照一定的消息量和时间间隔去刷盘。刷盘的动作由操作系统调度,若刷盘前系统崩溃,则数据丢失。

    解决方法:

    • Partition副本机制以及acks机制
      • producer端设置request.required.acks = 0;只要请求已发送出去,就算是发送完了,不关心有没有写成功。性能很好,如果是对一些日志进行分析,可以承受丢数据的情况,用这个参数,性能会很好。
      • request.required.acks = 1;发送一条消息,当leader partition写入成功以后,才算写入成功。不过这种方式也有丢数据的可能。
      • request.required.acks = -1;需要ISR列表里面,所有副本都写完以后,这条消息才算写入成功。

Kafka如何保证消息消费的顺序性

Producer发送消息的时候,根据消息的key进行取模,来决定把当前消息存储到哪一个Partition中。因Consumer是完全独立的网络节点,就可能会出现读取不是按照发送顺序来实现的。

解决方法:

  • 自定义消息分区的路由算法,把指定的key都发送到同一个Partition中,指定一个Consumer去消费。
  • Consumer端采用阻塞队列,将获取的消息先保存到阻塞队列,再用异步线程从阻塞队列中获取消息。