1. Kafka 的功能

Kafka 是一个分布式流式处理平台,流平台具有三个关键功能:

  1. 消息队列:发布和订阅消息流,这个功能类似于消息队列,这也是 Kafka 也被归类为消息队列的原因。
  2. 容错的持久方式存储记录消息流: Kafka 会把消息持久化到磁盘,有效避免了消息丢失的风险·。
  3. 流式处理平台:在消息发布的时候进行处理,Kafka 提供了一个完整的流式处理类库。

    2. Kafka 重要的五个概念

    Kafka 是一个 Pub-Sub 的消息系统,无论是发布还是订阅,都要指定 Topic。Kafka 将生产者发布的消息发送到Topic(主题)中,需要这些消息的消费者可以订阅这些 Topic(主题),如下图所示:
    Kafka - 图1
    Kafka - 图2
    上面这张图也为我们引出了,Kafka 比较重要的几个概念:

  4. Producer(生产者):产生消息的一方。

  5. Consumer(消费者):消费消息的一方。
  6. Broker(代理):可以看作是一个独立的 Kafka 实例。多个 Kafka Broker 组成一个 Kafka Cluster。
  7. Topic(主题):Producer 将消息发送到特定的主题,Consumer 通过订阅特定的 Topic(主题) 来消费消息。
  8. Partition(分区):Partition 属于 Topic 的一部分。一个 Topic 可以有多个 Partition ,并且同一 Topic 下的 Partition 可以分布在不同的 Broker 上,这也就表明一个 Topic 可以横跨多个 Broker 。

Kafka 天然是分布式的,往一个 Topic 丢数据,实际上就是往多个 Broker 的 Partition 存储数据。

3. Kafka 如何进行持久化?

  • Kafka 是将 Partition 的数据写在磁盘上的(消息日志),不过 Kafka 只允许追加写入(顺序访问),避免缓慢的随机 I/O 操作。
  • Kafka 也不是 Partition 一有数据就立马将数据写到磁盘上,它会先缓存一部分,等到足够多数据量或等待一定的时间再批量写入(flush)。

    4. Kafka 的 Consumer 如何进行消费?

    我们可以用消费者组,让每个 Consumer 去消费一个分区(这样可以提高吞吐量)。
    Kafka - 图3
    Consumer 在读的时候也很有讲究:正常的读磁盘数据是需要将内核态数据拷贝到用户态的,而 Kafka 通过调用sendfile()直接从内核空间(DMA的)到内核空间(Socket的)少做了一步拷贝的操作。

如果一个消费者组中的某个 Consumer 挂了,那挂了的 Consumer 所消费的分区就由存活的 Consumer 消费。那存活的 Consumer 怎么知道挂掉的 Consumer 消费到哪里呢?又或者是如果机器宕机需要重启,那么重启之后 Consumer 怎么知道自己消费到哪个位置呢? 这就要引出offset了,Kafka 就是用offset来表示 Consumer 的消费进度到哪了,每个 Consumer 会都有自己的offset,代表消息的序号。然后 Consumer 消费了数据之后,每隔一段时间(定时定期),会把自己消费过的消息的 offset 提交一下,表示『我已经消费过了,下次我要是重启啥的,你就让我继续从上次消费到的 offset 来继续消费吧』(可以选择自动提交还是手动提交)。 在以前版本的 Kafka,这个offset是由 Zookeeper 来管理的,后来 Kafka 开发者认为 Zookeeper 不合适大量的删改操作,于是把offset保存在 Kafka 集群的__consumer_offsets这个 Topic 中。

6. Kafka 和 Zookeeper 的关系

虽然 Zookeeper 在新版的 Kafka 中没有用于保存客户端的 offset,但是 Zookeeper 是 Kafka 的一个重要的依赖。它可以帮助 Kafka 做以下事情:

  • 探测 broker 和 consumer 的添加或移除。
  • 负责维护所有 partition 的领导者/从属者关系(主分区和备份分区),如果主分区挂了,需要选举出备份分区作为主分区。
  • 维护 topic、partition 等元配置信息。
  • ……

    7. Kafka 的多副本机制(保证了 Kafka 的高可用)

    Kafka 基本架构是多个 broker 组成,每个 broker 是一个节点。创建一个 topic 可以划分为多个 partition,每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据,这就是天然的分布式消息队列。就是说一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据。

Kafka 0.8 以后,Kafka 为分区(Partition)引入了多副本(Replica)机制。分区(Partition)中的多个副本之间会有一个叫做 leader 的家伙,其他副本称为 follower。我们发送的消息会被发送到 leader 副本,然后 follower 副本才能从 leader 副本中拉取消息进行同步。生产者和消费者只与 leader 副本交互。你可以理解为其他副本只是 leader 副本的拷贝,它们的存在只是为了保证消息存储的安全性。当 leader 副本发生故障时会从 follower 中选举出一个 leader,但是 follower 中如果有和 leader 同步程度达不到要求的参加不了 leader 的竞选。

Kafka 的多分区(Partition)以及多副本(Replica)机制有什么好处呢?

  1. Kafka 通过给特定 Topic 指定多个 Partition, 而各个 Partition 可以分布在不同的 Broker 上, 这样便能提供比较好的并发能力(负载均衡)。
  2. Partition 可以指定对应的 Replica 数, 这也极大地提高了消息存储的安全性,提高了容灾能力,不过也相应的增加了所需要的存储空间。

    8. Kafka 为什么快的四大原因

    Kafka - 图4

  3. 顺序写入数据,在 Partition 末尾追加,所以速度最优。

  4. 使用 MMAP 技术将磁盘文件与内存映射,Kafka 可以像操作磁盘一样操作内存。
  5. 通过 DMA 技术实现零拷贝,减少数据传输次数。
  6. 读取数据时配合 sendfile 直接暴力输出,批量压缩把所有消息变成一个批量文件,合理减少网络 IO 损耗。

    8.1 顺序读写磁盘

    生产者写入数据和消费者读取数据都是顺序读写的,先来一张图直观感受一下顺序读写和随机读写的速度:
    Kafka - 图5
    Kafka 写入数据是顺序的,下面每一个 Partition 都可以当做一个文件,每次接收到新数据后 Kafka 会把数据插入到文件末尾,虚框部分代表文件尾。
    Kafka - 图6
    这种方法有一个问题就是删除数据不方便,所以 Kafka 一般会把所有的数据都保留下来,每个消费者(Consumer)对每个 Topic 都有一个 offset 用来记录读取进度或者叫坐标。
    Kafka - 图7

    8.2 Memory Mapped Files(MMAP)

    MMAP 也就是内存映射文件,在 64 位操作系统中一般可以表示 20G 的数据文件,它的工作原理是直接利用操作系统的 Page 来实现文件到物理内存的直接映射,完成映射之后对物理内存的操作会被同步到硬盘上。

通过 MMAP 技术进程可以像读写硬盘一样读写内存(逻辑内存),不必关心内存的大小,因为有虚拟内存兜底。这种方式可以获取很大的 I/O 提升,省去了用户空间到内核空间复制的开销。

MMAP 也有一个很明显的缺陷,写到 MMAP 中的数据并没有被真正的写到硬盘,操作系统会在程序主动调用 flush 的时候才把数据真正的写到硬盘。

Kafka 提供了一个参数:producer.type 来控制是不是主动 flush,如果 Kafka 写入到 MMAP 之后就立即 flush 然后再返回 Producer 叫同步(sync),写入MMAP之后立即返回 Producer 不调用 flush 叫异步(async)。

8.3 Zero Copy(零拷贝)

Kafka - 图8
使用零拷贝之后,Kafka 没有通过 CPU 来进行数据搬运,所有的数据都是通过 DMA 来进行传输的。没有在内存层面复制(Copy)数据。无论传输数据量的大小,传输同样的数据使用了零拷贝能够缩短 65% 的时间,大幅度提升了机器传输数据的吞吐量,这也是Kafka能够支持百万 TPS 的一个重要原因。

  • Producer 生产的数据持久化到 broker,采用 mmap 文件映射,实现顺序的快速写入
  • Customer 从 broker 读取数据,采用 sendfile,将磁盘文件读到 OS 内核缓冲区后,转到 NIO buffer进行网络发送,减少 CPU 消耗

    8.4 Batch Data(数据批量处理)

    Kafka 把所有的消息都存放在一个一个的文件中,当消费者需要数据的时候 Kafka 直接把文件发送给消费者。比如说100万条消息放在一个文件中可能是10M的数据量,如果消费者和 Kafka 之间网络良好,10MB大概1秒就能发送完,既100万TPS,Kafka 每秒处理了10万条消息。

但是消费者只需要一条消息啊,kafka把整个文件都发送过来了,文件里面剩余的消息怎么办?不要忘了消费者可以通过 offset 记录消费进度。

发送文件还有一个好处就是可以对文件进行批量压缩,减少网络IO损耗

参考

  1. Kafka 为什么能那么快的 6 个原因
  2. 什么是消息队列?
  3. 全网最通俗易懂的Kafka入门
  4. Kafka消息中间件到底会不会丢消息
  5. 支持百万级TPS,Kafka是怎么做到的?答案藏在这10张图里
  6. 消息队列面试连环问:如何保证消息不丢失?处理重复消息?消息有序性?消息堆积处理?