Kafka 简介

100 阅读11分钟

Kafka 简介

本文翻译自 Kafka in a Nutshell, 原作者 Kevin Sookocheff

Kafka 只是一个消息系统,为什么会这么火呢?事实上,对于相互间需要传输数据的系统来说,消息系统是非常重要的基础设施之一。 为什么这么说呢,我们先来看一下没有消息系统的数据管道会是什么样子。

这个系统使用 Hadoop 存储、处理数据。 Hadoop 里没有数据的话就无从谈起,所以第一步就是加载数据。

img

至此还没有什么太大的问题。但不幸的是,实际情况是同时存在着好几个系统,他们都需要和 Hadoop 以及彼此交互。情况一下子变得复杂多了,每个系统都要通过不同的传输通道和另一个系统交互。每个通道又有各自的协议和通讯方式。开发团队不得不投入大量精力用于维护系统间的数据传输。

img

再看到这张图片,展示的是使用 Kafka 作为中央事件总线的样子。所有数据的输入都先进入 Kafka,所有数据的输出也都从 Kafka 读取。Kafka 集中管理生产者与消费者之间的数据通讯。

img

Kafka 是什么

Kafka 是一个分布式的消息系统,通过分发订阅模式提供高速、高可扩展性、冗余的消息传递。Kafka 分布式的设计有很多好处。首先,Kafka 支持大规模的永久或临时的消费者。其次,Kafka 是高可用的,节点异常可以快速恢复而且是自动恢复。在业界,这些特性使得 Kafka 成为了一个理想的工具用于大规模数据系统各组件间的通讯与交互。

Kafka 术语

Kafka 的基础架构由这几个概念构成: topicproducerconsumerbroker

Kafka 中所有的消息都按 topic 进行划分。如果你想要发送一条消息,实际上是把它发给了一个特定的 topic。如果你想要读一条消息,其实也是从一个特定的 topic 读取到它。consumer 从 topic 消息;producer 将消息到 topic。

最后,Kafka 作为一个分布式系统,是以集群的方式运行的。集群中的每个节点,称之为一个 broker。

Kafka Topic 详解

Kafka 的 topic 会划分为一定数量的 partition。partition 可以把一个 topic 并行化:将一个 topic 划分到多个 broker,每个 partition 存放于一个独立的机器上,多个 consumer 可以并行地读取同一个 topic 的数据。 多个 consumer 可以并行地从一个 topic 的多个 partition 读取。这使得 Kafka 的消息处理可以达到一个很高的吞吐量。

partition 中的每条消息都有一个标识符(offset)。offset 是一条消息在这个不可变队列中的顺序下标。Kafka 会维护这个消息的顺序。consumer 可以从一个指定的 offset 或者他们选择的任意一个 offset 开始读消息。 consumer 可以在他们认为合适的任何时间点加入集群。

基于这些约束,kafka 集群中的每一条消息,都可以通过topic、partition 和 offset 来确定唯一。

img

从另一个角度,可以把一个 partition 看做一份日志。数据源把消息写到日志,一个或多个 consumer 在他们选定的时间从日志读取消息。

下面这图里,一个数据源正在往日志写数据,consumer A 和 B 正在从不同的 offset 读日志。

img

Kafka 会将消息保存一段时间,具体时长可以配置。consumer 可以据此相应地调整它们的行为。举个例子,如果 Kafka 配置只能存储一天的消息,而一个 consumer 挂掉的时间超过了一天,那么这个 consumer 就会丢失消息。而如果这个 consumer 只是挂了一个小时,它恢复后可以从上一次的 offset 继续读起。

从 kafka 的角度来看,consumer 从 topic 读了什么数据是无状态的。

Partition 和 Broker

每个 broker 都维护了一定数量的 partition。每个 partition 都可能是一个 leader 或者就是一个普通的副本。所有对 topic 的读和写都是通过 leader,leader 会协调其他副本更新数据。如果一个 leader 挂了,会有一个副本取代它成为新的 leader。

img

Producer

producer 只会写入单独某一个 partition 的 leader,这就像负载均衡,每次写操作都可能由不同的 broker 和机器处理。

在第一张图片中,producer 正在往 0 号 partition 写入,而 0 号 partition 又把数据复制到可用的备份上。

img

在第一张图片中,producer 正在往 1 号 partition 写入,而 1 号 partition 又把数据复制到可用的备份上。

img

每个机器各自负责分配到自己的数据,于是整个系统的吞吐量就提升了。

consumer 和 consumer 组

consumer 可以从任何一个 partition 读取,可以像消息的生产一样来调整消息消费的吞吐量。对于一个指定的topic,consumer 可以组织成一个 consumer 组。组里的每个 consumer 都读一个不同的 partition,整个组就消费了这整个topic 的所有消息。

如果 consumer 的数量超过了 partition,那么就会有些 consumer 被闲置,因为没有 partition 可供他们读取。如果 partition 的数量超过了 consumer,那么有些 consumer 就会从多个 partition 读取消息。

如果 consumer 的数量和 partition 一样,那么每个 consumer 都会刚好对应一个 partion.

下面这张来自官方文档的图片,描述了一个 topic、多个 partition 的场景。

服务 1 维护 partition 0 号和 3 号; 服务 2 维护 1 号和 2 号.

这里有两个 consumer 组,A 和 B。A 由两个 consumer 构成, B 由 4 个 consumer 构成。

consumer 组 A 有 2 个 consumer 来消费 4 个partition,每个 consumer 要从 2 个 partition 读取数据。另一边,consumer 组 B,有着和 partition 数一样的 consumer 数,每个 consumer 都从一个 partition 读取。

img

一致性与可用性

在讨论一致性与可用性前,注意下面这些承诺只适用于消息生产到单个 partition 并从单个 partition 消费。如果两个 consumer 从同一个 partition 消费,或者两个 producer 往同一个 partition 生产,那么这些保证就不再适用了。

Kafka 对于数据的一致性和可用性,有如下承诺:

  1. 消息发送到一个 topic partition 后,会按照发送的顺序追加到提交日志.

  2. 单个 consumer 看到的消息的顺序,就是他们在日志中的顺序.

  3. 当一条消息同步到所有备份后,消息才会被视为已提交.

  4. 只要任意一个备份存活,任何已提交的数据都不会丢失.

第 1、2 条承诺保证了,在每个 partition 看来,消息的顺序被保留了下来。 需要注意的是,对于整个 topic 来说,消息的顺序是不保证的。第 3、4 条承诺保证了已提交了的消息一定可以被获取。在 Kafka 中,被选举为 leader 的 partition 会负责同步收到的消息给其他备份。一旦有一份备份确认收到了消息,这个备份就会视作处于同步状态。

为了更深入地理解这点,让我们更仔细地看下,写消息的时候发生了什么。

处理写操作

当在和 Kafka 集群通讯的时候,所有的消息都会被发给 partition 中的的 leader。leader 会负责把消息写到自己的副本中,一旦消息被提交了,还会把消息扩散给在其他 broker 的副本。

所有副本都确认已经收到消息后,就可以说同步完成了.

img

当集群中的每个 broker 都是可用的的时候,consumer 和 producer 可以顺利地从 leader partition 读写数据而不出问题。但是任意一个 leader 或者其他副本都有可能出故障,每类情况都需要处理。

异常处理

当一个副本出问题时会发生什么呢? 异常副本将不再可写,也无法接收消息,不再与 leader 同步数据,落后越来越大。

在下图中,副本 3 就不再从 leader 接收消息。

img

当第二个副本也挂了,会发生什么呢?第二个副本也不再接收消息,不再与 leader 同步。

img

这时就只剩下 leader 自己处于同步状态。此时仍存在一个处于同步状态的副本,只不过这个副本刚好就是这个 partition 的 leader。

如果 leader 也挂了呢? 那我们就只剩下了三个都挂了的副本。

img

副本 1 确实还处于同步状态,它只是无法收到任何数据,如果真的收到了数据还是会同步的。

副本 2 缺失了一些数据,而第一个挂掉的副本 3 则缺失得更多。这种状态下,有两种可行的解决方案。

第一种方案,也是最简单的,等到 leader 恢复了再继续。 一旦 leader 恢复了,它就会开始接收并写入数据,而其他副本则等恢复后再从 leader 同步数据。

第二种方案就是再选举一个 broker 作为新的 leader。 这个新的 broker 之前没有和 leader 同步数据,从旧broker 挂掉到新 broker 产生的这段时间内的数据都会丢失。当新的 broker 起来后, 就会发现之前提交的数据都不见了。选举新 leader 再快也可能会导致丢失数据。但我们可以尽量缩短宕机时间。任何新机器都有可能成为 leader.

再回过头来看,当 leader 挂掉而其他副本仍然存活的场景。

img

此时,Kafka 的控制器会感知到 leader 失联并从其他副本中选举出一个新的 leader。这会导致客户端出现几秒钟的LeaderNotAvailable 报错。但是只要 consumer 和 producer 处理好了这种可能性并进行恰当地重试,就不会有数据丢失。

Kafka 客户端的一致性

kafka 的客户端可以分为两类,producer 和 consumer。每一类都可以配置不同一致性程度的策略。

对于 producer 有 3 种策略。对于每条消息:

  1. 等待所有副本确认消息
  2. 等待 leader 确认消息
  3. 不等待任何确认

每种策略都有他们各自的优点与缺点。开发者要基于数据一致性要求、吞吐量等因素来为他们的系统选择合适的策略。

而在 consumer 这边,我们只能读到已提交的消息(例如已经写入所有副本的消息)。基于此,consumer 有 3 类策略, 对于每条消息:

  1. 最多只读一次
  2. 至少读一次
  3. 读且只读一次

每种策略都值得单独讨论:

对于最多只读一次,consumer 从 partition 读取数据后,提交刚刚的 offset,然后再处理数据。如果 consumer 在提交 offset 和处理数据之间崩溃了,就会导致下次从下一条消息开始处理,当前的这条消息就不会再处理了。这会导致潜在的不符合预期的数据丢失。

一个更好的方式就是每条消息至少读一次。这个策略中,consumer 从 partition 读取数据后,先进行处理,处理完了再提交这条消息的 offset。 这个场景下,consumer 如果在处理数据和提交offset之间崩溃了,重启后就会再次处理这条消息。这就会对下游系统产生重复的消息,但是数据不会丢失。

读且只读一次需要 consumer 处理消息并将处理结果和offset 提交到一个支持事务的系统。 如果 consumer 崩溃了,它可以重新读最近提交的一个事务,从这里重新开始处理。所以就没有数据丢失,也没有数据重复。但是在实践中,读且只读一次的策略会显著降低系统的吞吐量,因为每条消息和 offset 都要被当做一个事务提交。

实践中,大部分 consumer 应用选择的都是至少读一次的测录,因为它在吞吐量和正确性之间提供了最好的平衡。此时就要由下游系统自己来处理消息重复的问题。

总结

Kafka正在快速地成为许多公司数据流的主干,这不是没有道理的。将 kafka 作为消息总线,我们可以达到很高的并发量,同时解耦了数据生产者与消费者,使我们的架构更加灵活可变。本文从整体上介绍了 Kafka 的架构。之后有问题就查 Kafka 官方文档吧。 享受学习 Kafka 并将这个工具更多地用起来吧。