Kafka 是一种分布式的,基于发布/订阅的消息系统,原本开发自 LinkedIn,用作 LinkedIn 的事件流(Event Stream)和运营数据处理管道(Pipeline)的基础。
基础原理详解可见 Kafka 基本架构及原理
基础架构
Broker
:Kafka 集群包含一个或多个服务器,这种服务器被称为 brokerTopic
:每条发布到 Kafka 集群的消息都有一个类别,这个类别被称为 Topic。(物理上不同 Topic 的消息分开存储,逻辑上一个 Topic 的消息虽然保存于一个或多个 broker 上但用户只需指定消息的 Topic 即可生产或消费数据而不必关心数据存于何处)Partition
:Parition(分片)是物理上的概念,每个 Topic 包含一个或多个 Partition.Producer
:负责发布消息到 Kafka brokerConsumer
:消息消费者,向 Kafka broker 读取消息的客户端。Consumer Group
:每个 Consumer 属于一个特定的消费者组Consumer Group(可为每个 Consumer 指定 group name,若不指定 group name 则属于默认的 group)。
如上图所示,Producer 使用push
模式将消息发布到 broker,Consumer 使用pull
模式从 broker 订阅并消费消息。
Kafka 通过 Zookeeper 管理集群配置,选举 leader,以及在 Consumer Group 发生变化时进行
rebalance
优点及应用场景
优点
- 高吞吐量:单机每秒处理几十上百万的消息量。即使存储了TB及消息,也保持稳定的性能。
- 零拷贝 减少内核态到用户态的拷贝,磁盘通过sendfile实现DMA 拷贝Socket buffer
- 顺序读写 充分利用磁盘顺序读写的超高性能
- 页缓存mmap 将磁盘文件映射到内存, 用户通过修改内存就能修改磁盘文件。
- 高性能:单节点支持上千个客户端,并保证零停机和零数据丢失。
- 持久化:将消息持久化到磁盘。通过将数据持久化到硬盘以及replication防止数据丢失。
- 分布式系统:易扩展。所有的组件均为分布式的,无需停机即可扩展机器。
- 可靠性:Kafka是分布式,分区,复制和容错的。
- 客户端状态维护:消息被处理的状态是在Consumer端维护,当失败时能自动平衡。
应用场景
- 日志收集:用Kafka可以收集各种服务的Log,通过大数据平台进行处理;
- 消息系统:解耦生产者和消费者、缓存消息等;
- 用户活动跟踪:Kafka经常被用来记录Web用户或者App用户的各种活动,如浏览网页、搜索、点击等活动,这些活动信息被各个服务器发布到Kafka的Topic中,然后消费者通过订阅这些Topic来做运营数据的实时的监控分析,也可保存到数据库;
高性能
Kafka 的性能优化中是常见的方法:
- 时间轮(Time Wheel):时间轮是一种定时调度的算法,可以用于 Kafka 中的延迟消息处理。通过将消息按照一定的时间间隔划分到不同的槽位,可以实现高效的消息调度和处理。
- 零拷贝(Zero-copy):零拷贝是一种优化技术,通过避免数据在内核空间和用户空间之间的拷贝,减少了数据传输的开销。在 Kafka 中,通过使用零拷贝技术可以提高数据的传输效率和降低CPU的开销。
- IO多路复用(IO Multiplexing):IO多路复用是一种高效的IO处理模型,可以通过同时监听多个IO事件,以非阻塞的方式处理多个连接。在 Kafka 中,使用IO多路复用可以提高网络IO的处理性能,减少线程的数量,提高系统的吞吐量。
- 顺序读写(Sequential Read/Write):在 Kafka 中,数据是以分区为单位进行顺序写入和顺序读取的。通过保持写入和读取的顺序,可以减少磁盘的随机访问,提高IO的效率。
- 压缩批处理(Compression and Batch Processing):Kafka 支持对消息进行压缩,并且可以将多个消息批量发送。通过压缩和批量处理,可以减少网络传输的数据量,提高传输效率和吞吐量。
这些技术在 Kafka 的设计和实现中发挥了重要作用,帮助 Kafka 实现了高性能、高吞吐量的特性。在使用 Kafka 时,可以根据具体的场景和需求,结合这些技术来进行性能优化和调优。
零拷贝
当使用零拷贝技术时,数据在内核空间和用户空间之间的传输是通过以下几个关键组件和步骤完成的:
- 内核缓冲区(Kernel Buffer):内核缓冲区是位于内核空间的一块内存区域,用于存储从用户空间写入的数据或从网络接收的数据。
- 用户缓冲区(User Buffer):用户缓冲区是位于用户空间的一块内存区域,用于存储应用程序读取或写入的数据。
- 零拷贝系统调用:操作系统提供了一些特定的系统调用,例如
sendfile()
和writev()
,用于在内核空间和用户空间之间实现数据的零拷贝传输
下面是零拷贝技术在 Kafka 中的更详细工作流程:
生产者端
- 生产者将要发送的消息写入发送缓冲区,该缓冲区位于用户空间
- 生产者调用零拷贝系统调用(如
sendfile()
或writev()
),将发送缓冲区的数据直接传输到内核缓冲区 - 内核将数据从内核缓冲区传输到网络套接字缓冲区,而无需将数据从内核空间复制到用户空间
Kafka 服务端
- 客户端发送的消息到达 Kafka 服务端,数据存储在网络套接字缓冲区
- Kafka 服务端使用零拷贝技术,将网络套接字缓冲区的数据直接复制到内核缓冲区
- Kafka 服务端根据配置的存储策略,将数据写入磁盘或存储设备
消费者端
- 消费者从网络接收消息,数据存储在接收缓冲区(Receive Buffer)
- 消费者使用零拷贝技术,直接从接收缓冲区读取数据,而无需将数据从内核空间复制到用户空间
- 消费者对数据进行处理或存储,完成消费过程
通过使用零拷贝技术,Kafka 避免了不必要的数据拷贝,提高了数据的传输效率和整体性能。它减少了CPU的开销和内存带宽的使用,特别在处理大量数据和高吞吐量的场景中表现出色。同时,零拷贝技术还可以减少系统调用的次数,进一步提高性能
常见面试题
本段参考自阿里技术 这些年背过的面试题——Kafka篇
线上问题rebalance
因集群架构变动导致的消费组内重平衡,如果kafka集内节点较多,比如数百个,那重平衡可能会耗时导致数分钟到数小时,此时kafka基本处于不可用状态,对kafka的TPS影响极大。
产生的原因:
- 组成员数量发生变化
- 订阅主题数量发生变化
- 订阅主题的分区数发生变化
**组成员崩溃和组成员主动离开是两个不同的场景。**因为在崩溃时成员并不会主动地告知coordinator此事,coordinator有可能需要一个完整的session.timeout周期(心跳周期)才能检测到这种崩溃,这必然会造成consumer的滞后。可以说离开组是主动地发起rebalance;而崩溃则是被动地发起rebalance。
解决方案:
加大超时时间 session.timout.ms=6s
加大心跳频率 heartbeat.interval.ms=2s
增长推送间隔 max.poll.interval.ms=t+1 minutes
ZooKeeper 的作用
目前,Kafka 使用 ZooKeeper 存放集群元数据、成员管理、Controller 选举,以及其他一些管理类任务。之后,等 KIP-500 提案完成后,Kafka 将完全不再依赖于 ZooKeeper。
- 存放元数据是指主题分区的所有数据都保存在 ZooKeeper 中,其他“人”都要与它保持对齐。
- 成员管理是指 Broker 节点的注册、注销以及属性变更等 。
- Controller 选举是指选举集群 Controller,包括但不限于主题删除、参数配置等。
KIP-500 ,是使用社区自研的基于 Raft 的共识算法,实现 Controller 自选举。
同样是存储元数据,这几年基于Raft算法的etcd认可度越来越高。
越来越多的系统开始用它保存关键数据。比如,秒杀系统经常用它保存各节点信息,以便控制消费 MQ 的服务数量。还有些业务系统的配置数据,也会通过 etcd 实时同步给业务系统的各节点,比如,秒杀管理后台会使用 etcd 将秒杀活动的配置数据实时同步给秒杀 API 服务各节点。
Replica副本的作用
Kafka 只有 Leader 副本才能 对外提供读写服务,响应 Clients 端的请求。Follower 副本只是采用拉(PULL)的方 式,被动地同步 Leader 副本中的数据,并且在 Leader 副本所在的 Broker 宕机后,随时准备应聘 Leader 副本。
- 自 Kafka 2.4 版本开始,社区可以通过配置参数,允许 Follower 副本有限度地提供读服务。
- 之前确保一致性的主要手段是高水位机制, 但高水位值无法保证 Leader 连续变更场景下的数据一致性,因此,社区引入了 Leader Epoch 机制,来修复高水位值的弊端。
为什么不支持读写分离?
- 自 Kafka 2.4 之后,Kafka 提供了有限度的读写分离。
- 场景不适用。读写分离适用于那种读负载很大,而写操作相对不频繁的场景。
- 同步机制。Kafka 采用 PULL 方式实现 Follower 的同步,同时复制延迟较大。
如何防止重复消费
- 代码层面每次消费需提交offset;
- 通过Mysql的唯一键约束,结合Redis查看id是否被消费,存Redis可以直接使用set方法;
- 量大且允许误判的情况下,使用布隆过滤器也可以
如何保证顺序消费
- 单 topic,单partition,单 consumer,单线程消费,吞吐量低,不推荐;
- 如只需保证单key有序,为每个key申请单独内存 queue,每个线程分别消费一个内存 queue 即可,这样就能保证单key(例如用户id、活动id)顺序性
如何解决积压消费
- 修复consumer,使其具备消费能力,并且扩容N台;
- 写一个分发的程序,将Topic均匀分发到临时Topic中;同时起N台consumer,消费不同的临时Topic
如何避免消息积压
- 提高消费并行度
- 批量消费
- 减少组件IO的交互次数
- 优先级消费
if (maxOffset - curOffset > 100000) {
// TODO 消息堆积情况的优先处理逻辑
// 未处理的消息可以选择丢弃或者打日志
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
// TODO 正常消费过程
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
如何设计消息队列
需要支持快速水平扩容,broker+partition,partition放不同的机器上,增加机器时将数据根据topic做迁移,分布式需要考虑一致性、可用性、分区容错性
- 一致性:生产者的消息确认、消费者的幂等性、Broker的数据同步;
- 可用性:数据如何保证不丢不重、数据如何持久化、持久化时如何读写;
- 分区容错:采用何种选举机制、如何进行多副本同步;
- 海量数据:如何解决消息积压、海量Topic性能下降;
性能上,可以借鉴时间轮、零拷贝、IO多路复用、顺序读写、压缩批处理
- Kafka 基本架构及原理
- 这些年背过的面试题——Kafka篇