kafka

kafka

分布式的基于发布/订阅的消息系统

特点:

  • 同时为发布和订阅提高吞吐量
  • 消息持久化
  • 分布式,支持服务器间的消息分区及分布式消费,同时保证每个partition内的消息顺序传输,其内部的Producer、Broker和Consumer都是分布式架构,这更易于向外扩展
  • 消费消息采用pull模式,消息被处理的状态是在Consumer端维护的,而不是由服务器端维护,Broker无状态,Consumer自己保存offset
  • 支持online和offline场景,同时支持离线数据处理和实时数据处理

基本概念

  • Broker:Kafka 集群中的一台或多台服务器。
  • Topic: 发布到 Kafka 的每条消息都有一个类别, 这个类别就被称为 Topic (物理上,不同 Topic 的消息分开存储;逻辑上,虽然一个 Topic 的消息被保存在一个或多个 Broker 上, 但用户只需指定消息的 Topic 即可生产或消费数据, 而不必关心数据存于何处〉。
  • Partition:物理上的 Topic 分区, 一个 Topic 可以分为多个 Partition, 每个 Partition 都是 一个有序的队列。 Partition 中的每条消息都会被分配-个有序的 ID (offset)。
  • Producer:消息和数据的生产者,可以理解为向 Kafka 发消息的客户端。
  • Consumer:消息和数据的消费者,可以理解为从 Kafka 取消息的客户端。
  • Consumer Group (消费者组〉:每个消费者都属于一个特定的消费者组(可为每个消费 者指定组名, 若不指定组名,则属于默认的组)。这是 Kafka 用来实现一个 Topic 消息 的广播(发送给所有的消费者〉 和单播(发送给任意一个消费者) 的手段。 一个 Topic 可以有多个消费者组。 Topic 的消息会被复制(不是真的复制, 是概念上的)到所有的 消费者组中,但每个消费者组只会把消息发送给该组中的一个消费者。如果要实现广 播, 只要每个消费者都有一个独立的消费者组就可以了;如果要实现单播,只要所有 的消费者都在同一个消费者组中就行。使用消费者组还可以对消费者进行自由分组, 而不需要多次发送消息到不同的 Topic。

一个典型的 Kafka 集群中包含若干生产者(可以是前端浏览器发起的页面 访问或服务器日志等)、若干 Broker (Kafka 支持水平扩展, 一般 Broker 数量越多集群吞吐率越 大〉、若干消费者组以及一个 ZooKeeper 集群。 Kafka 通过 ZooKeeper 管理集群配置、选举 leader, 以及当消费者组发生变化时进行 Rebalance (再均衡)。生产者使用推模式将消息发布到 Broker, 消费者使用拉模式从 Broker 订阅并消费消息。

在这里插入图片描述

分区

实际上 Kafka 的基本存储单元是分区(Pa口ition),在一个 Topic 中会有一个或多个 Partition, 不同的 Partition 可位于不同的服务器节点上, 物理上一个 Partition 对应于一个文件夹。需要注意的是, Partition 不能在多个服务器节点之间再进行细分,也不能 在一台服务器的多个磁盘上再细分,所以其大小会受挂载点可用空间的限制。 Partition 内包含 一个或多个 Segment, 每个 Segment 又包含-个数据文件和一个与之对应的索引文件。虽然物 理上最小单位是 Segment, 但是 Kafka 并不提供同一个 Partition 内不同 Segment 的并行处理能 力。对于写操作, 每次只会写 Partition 内的一个 Segment;对于读操作, 也只会顺序读取同一 个 Partition 内的不同 Segment。从逻辑上看,可以把一个 Partition 当作一个非常伏的数组,使 用时通过这个数组的索引 C offset)访问数据。

由于不同的 Partition 可位于不同的机器上,因此可以实现机器间的并行处理。由于一个 Partition 对应一个文件夹,多个 Partition 也可位于同一台服务器上,这样就可以在同一台服务 器上便不同的 Partition 对应不同的磁盘,实现磁盘问的并行处理。 这就是 Kafka 设计的 Partition 所提供的并行处理能力。所以一般通过增加 Partition 的数量来提高系统的并行吞吐量,但这也 会增加轻微的延迟。

不过事无绝对,也不能无限增加 Partition 的数量,因为消费消息时还会受消费者组的制约。 当一个 Topic 有多个消费者时, 一条消息只会被一个消费者组里的一个消费者消费。由于消息 数据是以 Partition 为单位分配的,在不考虑 Rebalance 时同一个 Partition 的数据只会被一个消费 者消费,所以如果消费者的数量多于 Partition 的数量,就会存在部分消费者不能消费该 Topic 的情况,此时再增加消费者并不能提高系统的并行吞吐量。

简而言之,站在生产者和 Broker 的角度,对不同 Partition 的写操作是完全并行的,可是对 于消费者其并发数则取决于 Partition 的数量。所以在实际项目中需要配置合适的 Partition 数量, 而这个数值需要根据所设计的系统吞吐量来推算。假设 p 表示生产者写入单个 Partition 的最大吞吐量, c 表示消费者从单个 Partition 消费的最大吞吐量,系统需要的目 标吞吐量是 t,则 Partition 的数量应该是 印和的中较大的那个。一般建议 Partition 的数量要大于或等于消费 者的数量, 因为这可实现最大井发量。

复制

Kafka 还使用 ZooKeeper 实现了去中心化的集群功能,简单地讲, 其运行机制是利用 Zoo Keeper 维护集群成员 的信息, 每个 Broker 实例都会被设置一个唯一的标识街, Broker 在启 动时会通过创建临时节点的方式把 自己的唯一标识符注册到 ZooKeeper 中 , Kafka 中的其他组 件会监视 ZooKeeper 里的/brokers/ids 路径,所以当集群中有 Broker 加入或退出时其他组件就会 收到通知。

虽然 Kafka有集群功能, 但是在 0.8 版本以前一直存在一个严重的问题,就是一旦某个Broker 岩机, 该 Broker 上的所有 Partition 数据就不能被消费了 , 生产者也不能把数据存放在这些 Partition 中 了,这显然不是一个高可用的系统方案。为了让 Kafka 在集群中某些节点不能继续 提供服务的情况下, 集群对外整体依然可用,即生产者可继续发送消息,消费者可继续消费消 息, 所以需要提供一种集群间数据的复制机制。在 Kafka 中是通过使用 ZooKeeper 提供的 leader 选举方式实现数据复制方案的,其基本原理是: 首先选举出一个 leader, 其他副本作为 follower,所有的写操作都先发给 leader, 然后再由 leader 把消息发给 follower。

可以说复制功能是 Kafka 架构的核心之一,因为它可以在个别节点不可用时还能保证 Kafka 整体的可用性。上面提到了分区的概念, Kafka 中的复制操作也是针对分区的。一个分区有多 个副本,副本被保存在 Broker 上,每个 Broker 都可以保存上千个属于不同主题和分区的副本。 副本有两种类型: leader 副本(每个分区都会有)和 follower 副本(除 leader 副本以外的其他副 本〉。为了保证一致性,所有生产者和消费者的请求都会经过 leader。 而 follower 不处理客户端 的请求,它的职责是从 leader 处复制消息数据,使自己和 leader 的状态保持一致,如果 leader 节点岩机,那么某个 follower 就会被选为 leader 继续对外提供服务。

假设有四个 Broker 节点、 一个 Topic、两个 Partition、复制因子是 3 (即一 个分区有 3 个副本)。当生产者发送消息时会先选择一个分区,比如 topicI -part 1 分区,生产者 把一条消息发送给这个分区的 leader,也就是 Broker 1 。然后 Broker 2 和 Broker 3 作为 follower 会拉取这条消息, 一旦消息被拉取到, follower 就会发送 ack fl向应给生产者。

在这里插入图片描述

消息发送

消息发送方式:

  • 立即发送: 只需要把消息发送到服务端,而不关心消息发送的结果
  • 同步发送:调用 send 方法发送消息后,获取该方法返回的 Futur巳对象,根据该对象的结果查看 send 方法调用是否成功。
  • 异步发送:先注册一个回调函数,通过调用另一个 send 方法发送消息时把回调函数作 为入参传入,这样当生产者接收到 Kafka 服务器的响应时会触发执行回调函数。

消息发送确认:
消息发送到Broker怎么算投递成功

  • 不等 Broker 确认,消息被发送出去就认为是成功的。 这种方式延迟最小,但是不能保 证消息已经被成功投递到 Broker。
  • 由 leader 确认, 当 leader 确认接收到消息就认为投递是成功的,然后再由其他副本通 过异步方式拉取。这种方式相对比较折中 。
  • 由所有的 leader 和 follower 都确认接收到消息才认为是成功的。采用这种方式投边的 可靠性最高,但相对会损伤性能。

消息发送确认模式通过设置生产者对象初始化时的 acks 属性来表示, 0 表示第一种模式, 其吞吐量和带宽的利用率非常高,但可能会丢失消息: l 表示第二种模式: all 表示第三种模式。

消息重发:
当从 Broker 接收到的是临时可恢复的异常时,生产 者会向 Broker 重发消息,但不能无限制重发,如果重发次数达到限制值,生产者将不再重试井 返回错误。

批次发送:
当有多条消息要被发送到同一个分区时, 生产者会把它们放到同一个批次里, Kafka 通过 批次的概念来提高吞吐量, 但同时也会增加延迟, 在实际场景中生产者要对这两方面进行权衡。 对批次的控制主要是通过构建生产者对象时的两个属性来实现的。

  • batch.size: 当发往每个分区的缓存消息数量达到这个数值时,就会触发一次网络请求, 批次里的所有消息都会被发送出去。
  • linger.ms: 每条消息在缓存中的最长时间, 如果超过这个时间就会忽略 batch.size 的限 制, 由客户端立即把消息发送出去。

消费者组

消费者组(Consumer Group)是 Kafka 提供的可扩展且具有容错性的消费机制, 在一个消 费者组内可以有多个消费者,它们共享一个唯一标识,即分组 ID。 组内的所有消费者协调消 费它们订阅的主题下的所有分区的消息,但一个分区只能由同一个消费者组里的一个消费者 来消费。

广播和单播:
消费者组可以用来实现 Topic 消息的广播和单擂。一个 Topic 可以有多个消费者组, Topic 的消息会被复制到所有的消费者组中, 但每个消费者组只会把消息发送给一个消费者组里的某 一个消费者。所以,如果要实现广播方式,只需为每个消费者都分配一个单独的消费者组接口 就行:如果要实现单播方式,则需要把所有的消费者都设置在同一个消费者组里。

再均衡:
如果消费者组有一个新消费者加入,则新消费者读取的是原本由其他消费者读取的消息: 如果消费者因为某种原因离开了消费者组,则原本由它读取的分区消息将会由消费者组里的其 他消费者读取。这种分区所有权从一个消费者转移到另 一个消 费者 的行为就叫作再均衡 (Rebalance)。

再均衡本质上是一种协议,规定了一个消费者组下的所有消费者如何达成一致来 分配主题下的每个分区。 例如某个消费者组下有 20 个消费者,它们订阅了一个主题有 100 个分 区,在正常情况下 Kafka 会为每个消费者平均分配 5 个分区,这种分配的过程就是 Rebalance。 触发再均衡的场景有三种: 一是消费者组内成员发生变更(例如有新消费者加入组、原来的消 费者离开组等); 二是订阅的主题数量发生变更(例如订阅主题时使用的是正则表达式, 此时如 果新建了某个主题正好匹配该正则表达式); 三是订阅主题的分区数量发生变更。

如果看过 Kafka 源码就可以知道,消费者每次调用 poll 方法时都会向被指派为群组协调器 的 Broker 发送心跳信息,群组协调器会根据这个心跳信息判断该消费者的活性,如果超过指定 时间没有收到对应的心跳信息, 则群组协调器会认为该消费者己经死亡,因此会将该消费者负 责的分区分派给其他消费者消费。再均衡是一种很重要的设计,它为消费者组带来了高可用性 和可伸缩性。不过, 在一般情况下并不希望它发生,因为再均衡操作影响的范围是整个消费者 组,即消费者组中的所有消费者全部暂停消费直到再均衡完成,而且如果 Topic 的分区越长, 这个过程就会越慢。在实际工作中一般会通过控制发送心跳频率(heartbeat.interval.ms)和会话 过期时间 ( session.timeout.ms)来尽量避免这种情况的发生。

消费偏移量

主要保证再均衡消费者发生变化,如何正确读取消息,免于重复读取和漏读取,所以引入了偏移量。

从设计上来说,由于 Kafka 服务端并不保存消息的状态,所以在消费消息时就需要消费者 自己去做很多事情,消费者每次调用 poll 方法时,该方法总是返回由生产者写入 Kafka 中但还 没有被消费者消费的消息。 Kafka 在设计上有一个不同于其他 JMS 队列的地方是生产者的消息 并不需要消费者确认,而消息在分区中又都是顺序排列的,那么必然就可以通过一个偏移量 (offset) 来确定每一条消息的位置,偏移量在消费消息的过程中起着很重要的作用。

我们把更新分区当前位置的操作叫作提交,那么悄费者是如何提交偏移量的呢? Kafka 中 有一个叫作 consumer offset 的特殊主题用来保存消息在每个分区的偏移量,消费者每次消费时都会往这个主题中发送消息,消息包含每个分区的偏移量。 如果消费者一直处于运行状态,那 么偏移量没什么用:如果消费者崩溃或者有新的消费者加入消费者组从而触发再均衡操作,再 均衡之后该分区的消费者若不是之前的那个,那么新的消费者如何得知该分区的消息已经被之 前的消费者消费到哪个位置了呢?在这种情况下提交偏移量就有用了。再均衡之后,为了能继 续之前的工作,消费者需要读取每个分区最后一次提交的偏移量,然后再从偏移量开始继续往 下消费消息。

如果所提交的偏移量小于客户端处理的最后一条消息的偏移量,那么两个偏移量之间的消 息就会被重复处理:如果所提交的偏移量大于客户端处理的最后一条消息的偏移量,那么两个 偏移量之间的消息就会被丢掉。所以要想用好 Kafka,维护消息偏移量对于避免消息被重复消 费和遗漏消费,确保消息的 ExactlyOnce 是至关重要的, 在 org.apache.kafka.clients.consumer. KafkaConsum巳r 类中提供了很多种方式来提交偏移量。

自动提交:
Kafka 默认会定期自动提交偏移量(可通过消费者的属性 enable.auto.commit 来修改, 默认 是 true),提交的时间间隔默认是 5 秒 (可通过消费者的属性 auto.commit.interval.ms 来修改), 这种自动提交的方式看起来很简便,但会产生重复处理消息的问题。

假设自动提交的时间间隔是 5 秒,在最后一次提交之后的第 3 秒发生了再均衡,再均衡完 成之后消费者从最后一次提交的偏移量位置开始读取消息,此时得到的偏移量己经落后实际消 费情况 3 秒,从而导致在这 3 秒内己消费的消息会被重复消费。当然,你可以通过再缩短提交 的时间间隔(例如把 3 秒改成 l 秒〉来更频繁地提交偏移量,从而减小可能出现重复消息的时 间间隔,但这还是完全避免消息重复消费的。所以自动提交虽然方便,但没有给开发留有避免 重复处理消息的空间。

手动提交:
由于自动提交可能导致出现一些不可控的情况,所以很多开发者通过在程序中自己决定何 时提交的方式来消除丢失消息的可能,并在发生再均衡时减少重复消息的数量。

异步提交:
使用 commitSync 方法提交偏移量有一个不足之处,就是该方法在 Broker 对提交请求做出 回应前是阻塞的。因此,采用这种方式每提交一次偏移量就等待一次限制了消费端的吞吐量, 如果通过降低提交频率来保证吞吐量, 则又有增加消息重复消费概率的风险。所以 Kafka 还提供 了另一种方式,即异步提交方式,消费者只管发送提交请求,而不需要等待 Broker 的立即回应。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值