Kafka 原理

消息队列内部实现原理

客户端 A 发送消息给消息队列,客户端 B 从消息队列消费消息

两种消息传输方式

  • 点对点

    点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除),发送到队列的消息只能被一个消费者消费,即使有多个消费者。

    • 优点:拉取消息的速度是客户端来控制的。
    • 缺点:需要线程实时监控消息队列,一有消息就拉取
  • 发布订阅模式

    一对多,数据产生后,推送给所有订阅者。发布订阅模型可以有多种不同的订阅者,临时订阅者只有在主动监听主题时才能接受消息,持久订阅者则监听主题所有消息,即使当前订阅者不可用,处于离线状态。

    • 优点:不需要监控消息队列,消息队列会主动推送消息。
    • 缺点:客户端获取消息的速度由消息队列决定,可能造成资源浪费。

消息队列优点:

  1. 消息发送端和接收端不需要直接相连,可以通过一个中间件连接,可以解耦
  2. 消息队列可以对数据进行备份
  3. 扩展性 -- 集群
  4. 灵活性 & 峰值处理能力
  5. 可恢复性
  6. 顺序保证(队列的特性)
  7. 缓冲
  8. 异步通信:A 发送消息 B 挂了也没事儿

Kafka

kafka 简介

kafka 是一个分布式消息队列。kafka 对消息保存时根据 Topic 进行归类,发送消息者称为 Producer,消息接受者称为 Consumer,此外 kafka 集群由多个 kafka 实例组成,每个实例(server)称为 broker

无论是 kafka 集群还是 consumer 都依赖于 zookeeper 集群保存一些 meta 消息,来保证系统可用性。

kafka 架构

  • Producer:消息生产者,就是向 kafka broker (kafka 服务器)发消息的客户端
  • Consumer:消息消费者,向 kafka broker 取消息的客户端
  • Consumer Group:这是 kafka 用来实现一个 Topic 消息的广播(发送给所有的 consumer)和单播(发给任意一个 consumer)的手段。topic 的消息会发送到所有的 Consumer Group。但每个 partition 只会把消息发给该 Consumer Group 中的一个 consumer。如果需要实现广播,只要每个 consumer 有一个独立的 Consumer Group,即一个 consumer 一个 group。这样该 topic 下的所有 partition 都会将消息推送给所有 consumer 了。要实现单播只要所有的 consumer 在同一个 Consumer Group。用 Consumer Group 还可以将 consumer 进行自由的分组而不需要多次发送消息到不同的 topic。
  • Broker:一台 kafka 服务器就是一个 broker。一个集群由多个 broker 组成。一个 broker 可以容纳多个 topic。
  • Topic:可以理解为一个队列
    • 逻辑概念:同一个 Topic 的消息可分布在一个或多个节点(Broker)上。
    • 一个 Topic 包含一个或者多个 Partition
    • 每条消息都属于且仅属于一个 Topic
    • Producer 发布数据时,必须指定将消息发布到哪个 Topic
    • Consumer 订阅消息时,也必须指定订阅哪个 Topic 的消息。
  • Partition:每个 partition 是一个有序的队列。partition 中的每条消息都会被分配一个有序的 id(offset)。kafka 只保证按一个 partition 中的顺序将消息发给 consumer,不保证一个 topic 的整体(多个 partition 间)的顺序。
    • 一个 Patition 只分布于一个 Broker 上(不考虑备份)。
    • 一个 Partition 物理上对应一个文件夹
    • 一个 Partition 包含多个 Segment(Segment 对用户透明)。
    • 一个 Segment 对应一个文件
    • Segment 由一个个不可变记录组成
    • 记录只会被 append 到 Segmentt 中,不会被单独删除或者修改。
    • 清除过期日志时,直接删除一个或多个 Segment。

  • Offset:偏移量。当前消费到的消息的位置。

如图可以看出:kafka 集群 > broker > topic > partition。即一个 kafka 集群中有多台 kafka 服务器,每台服务器可以有多个 topic 存放不同主题的消息,当一个 topic 太大了,可以将其划分成多个 partition。上图中 消息生产者 A 发送 主题 A 的消息到服务器上,topic A 的消息被分在了两个 partition 上:partition 0 与 partition 1。partition 0 和 partition 1 分别在 broker 1 和 broker 2 上有着 leader 和 follower。其中 leader 可以处理读写请求,当生产者发送消息到 topic 上时是发送到对应的 partition 的 leader 上去,leader 再将消息同步到 follower 上。follower 消息只能处理消费者的读请求。然后 每个Consumer Group 可以有一个或者多个 consumer。同一个 consumer group 上的 consumer 不能消费同一个topic 下的同一个 partition,但是可以消费不同的 partition。也就是说 上图的 consumer A 和 consumer B 不能同时消费 Topic A 下的 partition 0 但是可以分别消费 partition 0 和 partition 1。不同的 consumer group 上的 consumer 之间的消费没有冲突,可以随便消费。这样可以非常方便的实现一个 Topic 的广播和单播

注意:上图中 同一个 partition 出现在了不同 broker 中是因为实现了 Replication(备份)

kafka 集群角色

  • Leader

    所有的通信都是跟 Leader 进行,当 Leader 写完数据后,Follower 会自动向 Leader 获取数据同步

  • Follwer

    当实现副本(Replication 后)会创建 Follwer 用来备份。当 Leader 获取数据后,会自动向 Leader 同步数据。如果没有实现副本。那么每个分区都只会有 Leader,不会有 Follwer。

Kafka 工作流程分析

Kafka 生产过程分析

写入方式

Producer 采用推 (push)模式将消息发布到 broker,每条消息都被 append 到分区中(其实是 append 到 partition 文件夹内的 Segment 文件中),属于顺序写磁盘(顺序写磁盘效率比随机写内存高,保障 kafka 吞吐率)。

分区

消息发送时都被发送到一个 topic,其本质就是一个目录,而 topic 是由一些 Partition Logs(分区日志)组成,其组织结构如下图所示。

我们可用看到,每个 Partition 中的消息都是有序的,生产的消息不断追加到 Partition log 上,其中的每个消息都被赋予了一个唯一的 offset 值。

  1. 分区的原因
    • 方便在集群中扩展,每个 Partition 可以通过调整以适应它所在的机器。而一个 topic 又可以由多个 partition 组成,因此整个集群就可以适应任意大小的数据了
    • 可以提高并发量,因为可以以 partition 为单位读写了。
  2. 分区的原则
    • 指定 Partition,直接使用
    • 未指定 partition 但指定 key,可以通过 key 进行 hash 出一个 partition
    • 采用轮询方法选出一个 partition

副本

同一个 partition 可能有 多个 replication。没有 replication 的情况下,一旦 broker 宕机,其上所有 partition 的数据都不可被消费,同时 producer 也不能再将消息存于其他 partition。引入 replication 之后,同一个 partition 可能会有多个 replication,而这时需要这些 replication 之间选择出一个 leader,producer 和 consumer 只与这个 leader 交互,其他 replication 作为 follower 从 leader 中复制数据。

写入数据

  1. Producer 先从 zookeeper 的 “brokers/.../state” 节点找到该 partition 的 leader
  2. producer 将消息发送给该 leader
  3. leader 将消息写入本地 log
  4. followers 从 leader pull 消息,写入本地 log 后向 leader 发送 ACK
  5. leader 收到所有 follower 的 ACK 后,向 prodicer 发送 ACK

Broker 保存消息

存储方式

物理上把 topic 分成一个或者多个 partition,每个 partition 物理上对应一个文件夹(该文件夹存储该 partition 的所有消息和索引文件)

存储策略

无论消息是否被消费, kafka 都会保留所有消息。有两种策略可以删除旧数据。

  • 基于事件
  • 基于大小

需要注意的是,因为 Kafka 读取特定消息的时间复杂度为 O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。

ZooKeeper 存储结构

其中由于新版本 kafka 消费者的 offset 存在本地,因此在 zookeeper 中看不到。brokers 节点下的 ids 存放着 brokerid,存放所有 broker 的信息。topics 节点内可以看到每个 topic 的 各个 partition 的具体信息。

注意:producer 不在 zk 中注册,消费者在 zk 中注册。

Kafka 消费过程分析

kafka 提供了两套 consimer API,高级和低级的。

高级 API

  • 优点
    • 写起来检点
    • 不需要自行去管理 offset,系统通过 zookeeper 自行管理。
    • 不需要管理分区,副本等情况,系统自动管理。
    • 消费者断线后会自动根据上一次记录在 zookeeper 中的 offset 去接着获取数据(默认设置 1 分钟更新一下 zookeeper 中存的 offset)
    • 可以使用 group 来区分对同一个 topic 的不同程序访问分离开来(不同的 group 记录不同的 offset,这样不同程序读取同一个 topic 才不会因为 offset 互相影响)
  • 缺点
    • 不能自行控制 offset
    • 不能细化控制如分区、副本、zk 等

低级 API

  • 优点
    • 能够让开发者自己控制 offset,想从那里读取就从哪里读取
    • 自行控制连接分区,对分区自定义进行负载均衡
    • 对 zk 的依赖性降低(offset 不一定非要存在 zk,自行存储 offset 即可,比如存在文件或者内存中)
  • 缺点
    • 太过复杂,需要自行控制 offset,连接哪个分区,找到分区 leader 等

消费者组

消费者是以 consumer group 消费者组的方式工作,由一个或者多个消费者组成一个组,共同消费一个 topic。每个分区在同一时间只能由 group 内的一个消费者读取,但是多个 group 可以同时消费这个 partition。在上图,有一个由三个消费者组成的 group,有一个消费者读取 topic 中的两个分区,另外两个分别读取一个分区。某个消费者读取某个分区,也可以叫做某个消费者是某个分区的拥有者。

在这种情况下,消费者可以通过水平扩展方式同时读取大量的消息。

消费方式

consumer 采用 pull(拉)模式从 broker 中读取数据。

push 模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度的传递消息,但是这样很容易造成 consumer 来不及处理消息,容易引起拒绝服务以及网络拥塞。而 pull 模式则是可以根据 consumer 的消费能力以适当的速率消费消息。

对于 kafka 而言,pull 模式更合适,它可以简化 broker 的设计,consumer 可自主控制消费消息的速率,同时 consumer 可以自己控制消费方式 ------ 批量消费或者逐条消费。

pull 模式不足之处是,如果 kafka 没有数据,消费者可能会陷入循环中,一直等待数据到达。为了避免这种情况,我们在我们的拉请求中有参数,允许消费者请求在等待数据到达的长轮询中进行阻塞(并且可选地等待到达给定的字节数,以确保大的传输大小)。

posted @ 2019-07-22 11:00 zazasen 阅读(...) 评论(...) 编辑 收藏