
第一章 - 认识 Apache Kafka
Kafka 的核心功能是什么?一言以蔽之,高性能的消息发送与高性能的消息消费。
消息引擎系统(Messaging system)
用于在不同应用间传输消息的系统,消息引擎系统中的消息自然是最关键的因素之一。
其实,这里的消息可以是任何形式的数据,比如电子邮件、传真、即时消息,甚至是其他服务等,总之都是对企业有价值的数据。
在设计一个消息引擎系统时需要考虑的两个重要因素:
- 消息设计。
- 传输协议设计。
消息设计
消息引擎系统在设计消息时一定要考虑语义的清晰和格式上的通用性。
一条消息要有能够完整清晰表达业务的能力,它不能是含糊不清、语义不明甚至无法处理的。同时,为了更好地表达语义以及最大限度地提高重用性,消息通常都采用结构化的方式进行设计。
比如SOAP协议中的消息就采用了XML格式,而Web Service也支持JSON格式的消息。Kafka的消息是用二进制方式来保存的,但依然是结构化的消息。
传输协议设计
消息传输协议指定了消息在不同系统之间传输的方式。
这类协议可能包括任何能够在不同系统间传输消息或是执行语义操作的协议或框架。
比如现在主流的 RPC 及序列化框架,包括Google的Protocol Buffers、阿里系的Dubbo等。
Kafka自己设计了一套二进制的消息传输协议。
消息引擎范型
一个消息引擎范型是一个基于网络的架构范型,描述了消息引擎系统的两个不同的子部分是如何互连且交互的。
如果把消息引擎系统的这两个子系统比喻成两座城市,那么之前谈到的传输协议就是需要铺设的沥青公路,而引擎范型决定了来往穿梭于这两座城市的路线。
最常见的两种消息引擎范型是消息队列模型和发布/订阅模型。
消息队列(message queue)模型是基于队列提供消息传输服务的,多用于进程间通信(inter-process communication,IPC)以及线程间通信。
该模型定义了消息队列(queue)、发送者(sender)和接收者(receiver),提供了一种点对点(point-to-point,p2p)的消息传递方式,即发送者发送每条消息到队列的指定位置,接收者从指定位置获取消息。一旦消息被消费(consumed),就会从队列中移除该消息。
每条消息由一个发送者生产出来,且只被一个消费者(consumer)处理——发送者和消费者之间是一对一的关系。
生活中接线生的工作就是一个典型的基于队列的消息引擎模型。每个打进来的电话都进入一个排队队列,然后只由一个接线生进行处理。同一个客户不会被第二个接线生处理。
另一种模型就是发布/订阅模型(publish/subscribe,或简称为 pub/sub),与前一种模型不同,它有主题(topic)的概念:一个 topic 可以理解为逻辑语义相近的消息的容器。
这种模型也定义了类似于生产者/消费者这样的角色,即发布者(publisher)和订阅者(subscriber)。发布者将消息生产出来发送到指定的 topic 中,所有订阅了该 topic 的订阅者都可以接收到该topic下的所有消息。
通常具有相同订阅 topic的所有订阅者将接收到同样的消息。生活中报纸的订阅就是一种典型的发布/订阅模型:很多读者都会订阅同一个报社(类比于同一个 topic)出版的报纸,这样每当报纸更新(生产新的消息)时,这些读者都可以收到最新的报纸(接收最新的消息)。
Kafka引入了消息组(consumer group)的概念来同时支持这两种模型。
Kafka 概要设计
Kafka的设计初衷就是为了解决互联网公司超大量级数据的实时传输。为了实现这个目标,Kafka在设计之初就需要考虑以下4个方面的问题。
- 吞吐量/延时。
- 消息持久化。
- 负载均衡和故障转移。
- 伸缩性。
吞吐量/延时
通常来说,吞吐量是某种处理能力的最大值。而对于 Kafka而言,它的吞吐量就是每秒能够处理的消息数或者每秒能够处理的字节数。很显然,我们自然希望消息引擎的吞吐量越大越好。
消息引擎系统还有一个名为延时的性能指标。它衡量的是一段时间间隔,可能是发出某个操作与接收到操作响应(response)之间的时间,或者是在系统中导致某些物理变更的起始时刻与变更正式生效时刻之间的间隔。
对于 Kafka而言, 延时可以表示客户端发起请求与服务器处理请求并发送响应给客户端之间的这一段时间。显而易见,延时间隔越短越好。
在实际使用场景中,这两个指标通常是一对矛盾体,即调优其中一个指标通常会使另一个指标变差。
Kafka而言它是如何做到高吞吐量、低延时的呢?首先,Kafka 的写入操作是很快的,这主要得益于它对磁盘的使用方法的不同。
虽然Kafka 会持久化所有数据到磁盘,但本质上每次写入操作其实都只是把数据写入到操作系统的页缓存(page cache)中,然后由操作系统自行决定什么时候把页缓存中的数据写回磁盘上。这样的设计有3个主要优势。
- 操作系统页缓存是在内存中分配的,所以消息写入的速度非常快。
- Kafka不必直接与底层的文件系统打交道。所有烦琐的I/O操作都交由操作系统来处理。
- Kafka写入操作采用追加写入(append)的方式,避免了磁盘随机写操作。(虽然通常认为物理磁盘读写操作是很慢的,但是磁盘的顺序读写速度还是非常快的)
Kafka 在设计时采用了追加写入消息的方式,即只能在日志文件末尾追加写入新的消息,且不允许修改已写入的消息,因此它属于典型的磁盘顺序访问型操作,所以Kafka 消息发送的吞吐量是很高的。
在实际使用过程中可以很轻松地做到每秒写入几万甚至几十万条消息。
Kafka是把消息写入操作系统的页缓存中的。那么同样地,Kafka 在读取消息时会首先尝试从 OS的页缓存中读取,如果命中便把消息经页缓存直接发送到网络的 Socket上。
这个过程就是利用 Linux 平台的 sendfile 系统调用做到的,而这种技术就是大名鼎鼎的零拷贝(Zero Copy)技术。
关于 sendfile 与零拷贝
传统的Linux操作系统中的I/O接口是依托于数据拷贝来实现的,但在零拷贝技术出现之前,一个I/O操作会将同一份数据进行多次拷贝。
数据传输过程中还涉及内核态与用户态的上下文切换,CPU 的开销非常大,因此极大地限制了 OS 高效进行数据传输的能力。
零拷贝技术很好地改善了这个问题:首先在内核驱动程序处理 I/O 数据的时候,它不再需要进行上下文的切换,节省了内核缓冲区与用户态应用程序缓冲区之间的数据拷贝,同时它利用直接存储器访问技术(Direct Memory Access,DMA)执行I/O操作,因此也避免了OS内核缓冲区之间的数据拷贝,故而得名零拷贝。
传统方式(没有零拷贝):
- 步骤 1:内核调用
read()
从磁盘读取数据到 内核缓冲区(Page Cache)。 - 步骤 2:再调用
write()
将数据从 内核缓冲区 拷贝到 用户缓冲区(User Buffer)。 - 步骤 3:用户态再调用
send()
,将数据拷贝回 内核缓冲区(Socket Buffer)。 - 步骤 4:内核将数据写入 网卡缓冲区,最终发送给消费者。
- 步骤 1:内核调用
Kafka 采用
sendfile()
(零拷贝):- 步骤 1:内核直接将数据从 磁盘 Page Cache 复制到 Socket Buffer,绕过用户态,不经过用户缓冲区。
- 步骤 2:数据从 Socket Buffer 直接发送到网卡,传输给 Consumer。
除了零拷贝技术,Kafka 由于大量使用页缓存,故读取消息时大部分消息很有可能依然保存在页缓存中,因此可以直接命中缓存,不用“穿透”到底层的物理磁盘上获取消息,从而极大地提升了消息读取的吞吐量。
事实上,如果我们监控一个经过良好调优的 Kafka生产集群便可以发现,即使是那些有负载的 Kafka服务器,其磁盘的读操作也很少,这是因为大部分的消息读取操作会直接命中页缓存。
消息持久化
Kafka是要持久化消息的,而且要把消息持久化到磁盘上。这样做的好处如下。
- 解耦消息发送与消息消费 :本质上来说,Kafka 最核心的功能就是提供了生产者-消费者模式的完整解决方案。通过将消息持久化使得生产者方不再需要直接和消费者方耦合,它只是简单地把消息生产出来并交由 Kafka 服务器保存即可,因此提升了整体的吞吐量。
- 实现灵活的消息处理 :很多 Kafka 的下游子系统(接收 Kafka 消息的系统)都有这样的需求——对于已经处理过的消息可能在未来的某个时间点重新处理一次,即所谓的消息重演(message replay)。消息持久化便可以很方便地实现这样的需求。
另外,Kafka 实现持久化的设计也有新颖之处。
普通的系统在实现持久化时可能会先尽量使用内存,当内存资源耗尽时,再一次性地把数据“刷盘”;
而 Kafka 则反其道而行之,所有数据都会立即被写入文件系统的持久化日志中,之后Kafka服务器才会返回结果给客户端通知它们消息已被成功写入。
这样做既实时保存了数据,又减少了 Kafka程序对于内存的消耗,从而将节省出的内存留给页缓存使用,更进一步地提升了整体性能。
负载均衡和故障转移
作为一个功能完备的分布式系统,Kafka 如果只提供了最基本的消息引擎功能肯定不足以帮助它脱颖而出。一套完整的消息引擎解决方案中必然要提供负载均衡(load balancing)和故障转移(fail-over)功能。
负载均衡就是让系统的负载根据一定的规则均衡地分配在所有参与工作的服务器上,从而最大限度地提升系统整体的运行效率。
具体到Kafka来说,默认情况下 Kafka的每台服务器都有均等的机会为 Kafka 的客户提供服务,可以把负载分散到所有集群中的机器上,避免出现“耗尽某台服务器”的情况发生。
Kafka 实现负载均衡实际上是通过智能化的分区领导者选举(partition leader election)来实现的。
故障转移是指当服务器意外中止时,整个集群可以快速地检测到该失效(failure),并立即将该服务器上的应用或服务自动转移到其他服务器上。
故障转移通常是以“心跳”或“会话”的机制来实现的,即只要主服务器与备份服务器之间的心跳无法维持或主服务器注册到服务中心的会话超时过期了,那么就认为主服务器已无法正常运行,集群会自动启动某个备份服务器来替代主服务器的工作。
Kafka 服务器支持故障转移的方式就是使用会话机制。每台 Kafka 服务器启动后会以会话的形式把自己注册到ZooKeeper 服务器上。一旦该服务器运转出现问题,与ZooKeeper 的会话便不能维持从而超时失效,此时 Kafka 集群会选举出另一台服务器来完全代替这台服务器继续提供服务。
伸缩性
伸缩性,英文名是 scalability。根据 Java 大神 Brian Goetz 在其经典著作 Java Concurrency in Practice 中的定义,伸缩性表示向分布式系统中增加额外的计算资源(比如CPU、内存、存储或带宽)时吞吐量提升的能力。
举一个例子来说,对于计算密集型(computation-intensive)的业务而言,CPU的消耗一定是最大的,这类系统上的操作我们称之为 CPU-bound。
那么如果一个 CPU 的运算能力是 U,我们自然希望两个 CPU 的运算能力是2U,即可以线性地扩容计算能力,这种线性伸缩性是最理想的状态,但在实际中几乎不可能达到,毕竟分布式系统中有很多隐藏的“单点”瓶颈制约了这种线性的计算能力扩容。
阻碍线性扩容的一个很常见的因素就是状态的保存。
我们知道,不论是哪类分布式系统,集群中的每台服务器一定会维护很多内部状态。如果由服务器自己来保存这些状态信息,则必须要处理一致性的问题。
相反,如果服务器是无状态的,状态的保存和管理交于专门的协调服务来做(比如 ZooKeeper),那么整个集群的服务器之间就无须繁重的状态共享,这极大地降低了维护复杂度。
倘若要扩容集群节点,只需简单地启动新的节点机器进行自动负载均衡就可以了。
Kafka服务器上的状态统一交由 ZooKeeper保管。但也并不是所有状态都不保存,它只保存了很轻量级的内部状态,因此在整个集群间维护状态一致性的代价是很低的。
Kafka 基本概念和术语
Kafka 自推出伊始的确是以消息引擎的身份出现的,其强大的消息传输效率和完备的分布式解决方案,使它很快成为业界翘楚。
随着 Kafka 的不断演进,Kafka 开发团队日益发现经Kafka交由下游数据处理平台做的事情Kafka自己也可以做,因此在Kafka 0.10.0.0版本正式推出了Kafka Streams,即流式处理组件。
自此Kafka正式成为了一个流式处理框架,而不仅仅是消息引擎了。
不管是消息引擎还是流式处理平台,它的处理流程并没有发生变化,核心架构也总是类似的,无非是生产一些消息然后再消费一些消息。如果总结起来那就是三句话:
- 生产者发送消息给Kafka服务器。
- 消费者从Kafka服务器读取消息。
- Kafka服务器依托ZooKeeper集群进行服务的协调管理。
另外,Kafka 服务器有一个官方名字:broker。
消息
Kafka 中的消息格式由很多字段组成,其中的很多字段都是用于管理消息的元数据字段,对用户来说是完全透明的。
Kafka 消息格式共经历过3次变迁,它们被分别称为 V0、V1和 V2版本。目前大部分用户使用的应该还是 V1版本的消息格式。
消息由消息头部、key和value组成。消息头部包括消息的CRC码、消息版本号、属性、时间戳、键长度和消息体长度等信息。
- Key:消息键,对消息做partition时使用,即决定消息被保存在某topic下的哪个partition。
- Value:消息体,保存实际的消息数据。
- Timestamp:消息发送时间戳,用于流式处理及其他依赖时间的处理语义。如果不指定则取当前时间。
另外,消息的属性字段,Kafka 为该字段分配了1字节。目前只使用了最低的3位用于保存消息的压缩类型,1位保存时间戳类型,高4位尚未使用。
- Bit 0-2(3 bits):压缩类型(Compression Type)
- Bit 3(1 bit):时间戳类型(Timestamp Type)
- Bit 4-7(4 bits):未使用(保留为
0
)
Attributes 值 |
二进制表示 | 时间戳类型 | 压缩类型 |
---|---|---|---|
0x00 (0) |
0000 0000 |
CreateTime | 无压缩 |
0x01 (1) |
0000 0001 |
CreateTime | Gzip |
0x02 (2) |
0000 0010 |
CreateTime | Snappy |
0x04 (4) |
0000 0100 |
CreateTime | Zstd |
0x08 (8) |
0000 1000 |
LogAppendTime | 无压缩 |
0x0A (10) |
0000 1010 |
LogAppendTime | Snappy |
- CreateTime(默认):时间戳由 Producer 设置,适用于事件时间(Event Time)语义。
- LogAppendTime:时间戳由 Broker 记录,适用于写入时间(Processing Time)语义。
Kafka 使用紧凑的二进制字节数组来保存上面这些字段,也就是说没有任何多余的比特位浪费。让我想起了之前看的 Redis 内部用的数据结构,也是十分的紧凑,没有多余比特位的浪费。
topic 和 partition
topic 是一个逻辑概念,代表了一类消息,也可以认为是消息被发送到的地方。通常我们可以使用topic来区分实际业务,比如业务A使用一个topic,业务B使用另外一个topic。
Kafka中的 topic通常都会被多个消费者订阅,因此出于性能的考量,Kafka并不是 topic-message 的两级结构,而是采用了 topic-partition-message的三级结构来分散负载。从本质上说,每个Kafka topic都由若干个partition组成。
topic是由多个partition组成的,而Kafka的partition是不可修改的有序消息序列,也可以说是有序的消息日志。
每个 partition 有自己专属的 partition 号,通常是从0开始的。用户对partition 唯一能做的操作就是在消息序列的尾部追加写入消息。
partition 上的每条消息都会被分配一个唯一的序列号——按照Kafka的术语来讲,该序列号被称为位移(offset)。该位移值是从0开始顺序递增的整数。
位移信息可以唯一定位到某partition下的一条消息。
Kafka 的 partition 实际上并没有太多的业务含义,它的引入就是单纯地为了提升系统的吞吐量,因此在创建 Kafka topic 的时候可以根据集群实际配置设置具体的partition数,实现整体性能的最大化。
offset
topic partition 下的每条消息都被分配一个位移值。
实际上,Kafka 消费者端也有位移(offset)的概念,但这两个offset属于不同的概念
每条消息在某个 partition的位移是固定的,但消费该 partition的消费者的位移会随着消费进度不断前移,但终究不可能超过该分区最新一条消息的位移。
Kafka 中的一条消息其实就是一个<topic,partition,offset>三元组(tuple),通过该元组值我们可以在 Kafka 集群中找到唯一对应的那条消息。
replica
分布式系统要实现高可靠性,就要通过冗余机制来保证。partition 的消息不能只保存一份,而是要保存多份,因此每个 partition 都会分配多个副本(replica),每个副本都保存着该 partition 的消息。
副本分为两类:领导者副本(leader replica)和追随者副本(follower replica)。
follower replica 是不能提供服务给客户端的,也就是说不负责响应客户端发来的消息写入和消息消费请求。
它只是被动地向领导者副本(leader replica)获取数据,而一旦 leader replica 所在的broker宕机,Kafka会从剩余的 replica中选举出新的 leader继续提供服务。
leader 和 follower
Kafka的 replica分为两个角色:领导者(leader)和追随者(follower)。
如今这种角色设定几乎完全取代了过去的主备的提法(Master-Slave)。和传统主备系统(比如MySQL)不同的是,在这类 leader-follower系统中通常只有 leader对外提供服务,follower只是被动地追随 leader 的状态,保持与 leader 的同步。
follower 存在的唯一价值就是充当 leader的候补:一旦 leader 挂掉立即就会有一个追随者被选举成为新的 leader 接替它的工作。
Kafka保证同一个partition的多个replica一定不会分配在同一台broker上。 毕竟如果同一个broker上有同一个partition的多个replica,那么将无法实现备份冗余的效果。
ISR
ISR的全称是in-sync replica,翻译过来就是与leader replica保持同步的replica集合。
Kafka为partition动态维护一个replica集合。该集合中的所有replica保存的消息日志都与leader replica保持同步状态。
只有这个集合中的 replica才能被选举为 leader,也只有该集合中所有replica都接收到了同一条消息,Kafka才会将该消息置于“已提交”状态,即认为这条消息发送成功。
正常情况下,partition的所有replica(含leader replica)都应该与leader replica保持同步,即所有 replica都在 ISR中。
因为各种各样的原因,一小部分 replica开始落后于 leader replica的进度。当滞后到一定程度时,Kafka会将这些 replica“踢”出 ISR。
相反地,当这些 replica重新“追上”了 leader的进度时,那么 Kafka会将它们加回到 ISR中。
这一切都是自动维护的,不需要用户进行人工干预。
Kafka 使用场景
- 消息传输
Kafka非常适合替代传统的消息总线(message bus)或消息代理(message broker)。Kafka特别适合用于实现一个超大量级消息处理应用。 - 网站行为日志追踪
Kafka 最早就是用于重建用户行为数据追踪系统的。很多网站上的用户操作都会以消息的形式发送到 Kafka 的某个对应的 topic 上。 - 审计数据收集
对关键的操作和运维进行监控和审计。需要从各个运维应用程序处实时汇总操作步骤信息进行集中式管理。 - 日志收集
对于大量分散在不同机器上的服务日志。我们可以使用 Kafka对它们进行全量收集,并集中送往下游的分布式存储中(比如 HDFS 等)。 - Event Sourcing
Event Sourcing实际上是领域驱动设计(Domain-Driven Design,DDD)的名词,它使用事件序列来表示状态变更,这种思想和 Kafka 的设计特性不谋而合。Kafka 也是用不可变更的消息序列来抽象化表示业务消息的,因此Kafka特别适合作为这种应用的后端存储。 - 流式处理
自0.10.0.0版本开始,Kafka社区推出了一个全新的流式处理组件Kafka Streams。
第二章 - Kafka 发展历史
挑选我感兴趣的内容,部分略过。
Kafka这个名字的由来。应该是Kafka三位原作者之一Jay Kreps的这句话:
I thought that since Kafka was a system optimized for writing using a writer’s name would make sense.
I had taken a lot of lit classes in college and liked Franz Kafka.Plus the name sounded cool for an open source project.
因为 Kafka 系统的写操作性能特别强,所以找个作家的名字来命名似乎是一个好主意。
我在大学时上了很多文学课,非常喜欢Franz Kafka。另外为开源项目起Kafka这个名字听上去很酷。
Kafka三位原作者之一(另外两位分别是Jun Rao和Neha Narkhede)。
以下是基于搜索结果的Kafka重要版本功能变化汇总表,结合多个来源信息整理而成:
版本 | 功能变化 | 说明 |
---|---|---|
0.8.x | 副本机制 | 引入多副本机制(Replication),提升数据可靠性 |
新Producer API | 异步发送消息,提升客户端效率,但初期存在稳定性问题 | |
Offset存储优化 | 将消费者位移从ZooKeeper迁移至__consumer_offsets 主题,减少ZooKeeper压力 |
|
0.9.x | 安全认证 | 支持SSL/SASL认证、授权管理及数据加密,增强外网传输安全性 |
Kafka Connect | 引入高性能数据集成框架,支持与外部系统(如数据库、HDFS)对接 | |
新Consumer API | 消费者自主管理Offset,支持多线程消费和细粒度控制,取代旧版High-level Consumer | |
0.10.x | Kafka Streams | 正式成为流处理平台,支持基于时间戳的流计算,但初期功能尚不完善 |
消息时间戳 | 消息体中增加时间戳字段,支持基于时间的窗口操作和回溯查询 | |
0.11.x | Exactly-Once语义 | 支持生产者幂等性和事务功能,确保消息不重复处理(需配合Kafka Streams) |
消息格式重构 | 优化消息头结构,支持Header字段存储元数据,提升批量消息压缩效率 | |
1.0.x | 磁盘故障转移 | Broker单块磁盘损坏时,数据自动迁移至其他磁盘,提升可用性 |
跨磁盘副本迁移 | 分区副本可在同一Broker的不同磁盘目录间迁移,优化磁盘负载均衡 | |
2.0.x | 安全增强 | 支持前缀通配符ACL和OAuth2令牌认证,默认启用SSL主机名验证 |
消费者组管理优化 | 默认Offset保留时间从1天延长至7天,减少消费者组重建时的数据丢失风险 | |
2.8.x | KRaft模式 | 引入Raft共识协议替代ZooKeeper管理元数据,简化架构(早期版本不建议生产使用) |
3.0.x | 移除ZooKeeper依赖 | KRaft模式正式支持,元数据完全由Kafka自身管理,提升云原生兼容性 |
默认交付保证增强 | 生产者默认启用acks=all 和幂等性(enable.idempotence=true ),确保消息持久化与顺序性 |
- 版本演进趋势:从消息队列逐步发展为流处理平台,核心改进集中在可靠性(副本、事务)、性能(异步发送、压缩优化)、云原生(KRaft、存储分离)和安全性(SSL、ACL)。
- 兼容性注意:
- 0.11.x后消息格式变更需客户端同步升级;
- 3.0.x起弃用Java 8和Scala 2.12,计划在4.0移除。
第三章 - Kafka 线上环境部署
集群环境规划
操作系统优先选择Linux,这里考虑的主要是系统的IO模型、Kafka底层网络库的设计和网络传输效率。
IO模型主流的有五种:阻塞 I/O、非阻塞 I/O、I/O多路复用、信号驱动 I/O和异步 I/O。
linux epoll取消了轮询机制,取而代之的是回调机制(callback)。这样当底层连接 Socket 数较多时,可以避免很多无意义的 CPU 时间浪费。
windows IOCP是异步 I/O的实现。
Kafka新版本clients在设计底层网络库时采用了Java的Selector机制。
Java的Selector 在 Linux上的实现机制就是 epoll;
但是在 Windows平台上,Java NIO的 Selector底层是使用 select模型,在 Java NIO2才是使用 IOCP 实现的。
网络与磁盘的数据传输 Kafka 通过 Java 的FileChannel.transferTo方法实现。
在Linux平台上该方法底层会调用sendfile系统调用,即采用了Linux提供的零拷贝(Zero Copy)技术。
对于Windows平台,虽然它也提供了TransmitFile函数来支持零拷贝技术,但是直到Java 8u60版本Windows平台才正式让FileChannel的transferTo方法调用该函数。
磁盘规划
机械硬盘(HDD)和固态硬盘(SSD)对于Kafka没有太大的区别。(当然,SSD会有更好性能,HDD会有更好的性价比)
因为Kafka主要是顺序写磁盘,SSD顺序IO不需要频繁移动磁头。
JBOD(Just Bunch Of Disks 普通磁盘集合) 与磁盘阵列(RAID)。普通磁盘性价比更高。
RAID会以容量为代价换来数据的冗余和负载均衡。但是Kafka集群本身可以设置副本数,是自带数据冗余的。
使用RAID会增加额外不需要的数据冗余,会是一笔很大的成本开销。
但JBOD方案也会有缺陷:
- 任意磁盘损坏都会导致 broker 宕机——普通磁盘损坏的概率是很大的,因此这个缺陷从某种程度上来说是致命的。
- JBOD 的管理需要更加细粒度化。
- JBOD 需要提供类似于负载均衡的功能。
磁盘容量规划
Kafka的每条消息都保存在实际的物理磁盘中,这些消息默认会被broker保存一段时间之后清除。
这段时间是可以配置的,因此用户可以根据自身实际业务场景和存储需求来大致计算线上环境所需的磁盘容量。
对于磁盘容量的规划和以下多个因素有关:
- 新增消息数。
- 消息留存时间。
- 平均消息大小。
- 副本数。
- 是否启用压缩。
内存规划
Kafka 虽然会持久化每条消息,但其实这个工作都是底层的文件系统来完成的,Kafka 仅仅将消息写入page cache而已,之后将消息“冲刷”到磁盘的任务完全交由操作系统来完成。
consumer 在读取消息时也会首先尝试从该区域中查找,如果直接命中则完全不用执行耗时的物理 I/O 操作,从而提升了 consumer 的整体性能。
Kafka对于Java堆内存的使用并不多,因为Kafka的消息很快就会被垃圾回收(GC)。
一般情况下,broker 所需的堆内存都不会超过6GB。但消息会占用大量系统的 page cache。
对于内存规划的建议如下。
- 尽量分配更多的内存给操作系统的page cache。
- 不要为broker设置过大的堆内存,最好不超过6GB。
- page cache大小至少要大于一个日志段的大小。
CPU 规划
Kafka不属于计算密集型(CPU-bound)的系统,属于IO密集型(I/O-bound)的系统。
所以多核系统是最佳的选择。
当然,如果启用了消息压缩,那么 broker 也可能需要大量的 CPU 资源。
带宽规划
对带宽资源规划的建议如下:
- 尽量使用高速网络。
- 根据自身网络条件和带宽来评估Kafka集群机器数量。
- 避免使用跨机房网络。
部署
这里使用 Docker 部署,关于集群与相关参数设置这里省略,使用的时候根据官方文档设置即可。
1 | docker pull apache/kafka:3.9.0 |