Kafka搭建、基本配置和调优

576 阅读18分钟

Apache Kafka Quick Start

Kafka安装

1. 下载资源包

Apache Kafka Downloads 下载Binary资源包

Apache ZooKeeper 下载Binary资源包(不带Source Release的那个)

Apache项目发布的时候基本都会有Binary和Source两个版本, Binary是编译好的能够直接使用的,Source是未编译的源代码。 为了更快地时候,我们一般会直接下载Binary二进制包

2. 启动Zookeeper

ZooKeeper: Because Coordinating Distributed Systems is a Zoo (apache.org) 添加配置文件:注意这个后缀是cfg

vim conf/zoo.cfg

tickTime=200
dataDir=/var/lib/zookeeper
clientPort=2181

启动zookeeper

bin/zkServer.sh start

如果Macos上运行Zookeeper出现了问题,可以参考一下文章: 解决Mac上安装Zookeeper问题:FAILED TO WRITE PID - 夜深nps - 博客园 (cnblogs.com)

3. 启动Kafka

进入Kafka应用目录

# 后台运行
bin/kafka-server-start.sh -daemon config/server.properties
# 查看日志
tail -f $KAFKA_HOME/logs/zookeeper.out

Kafka服务会默认占用9092端口

4. 创建主题测试收发

使用kafka-topics.sh命令行工具创建主题

➜ bin/kafka-topics.sh --create --topic quickstart-events --bootstrap-server localhost:9092
Created topic quickstart-events.

➜ bin/kafka-topics.sh --describe --topic quickstart-events --bootstrap-server localhost:9092
Topic: quickstart-events	TopicId: 9wroEws9SkiToPXeGV9sDg	PartitionCount: 1	ReplicationFactor: 1	Configs:
	Topic: quickstart-events	Partition: 0	Leader: 0	Replicas: 0	Isr: 0

可以看出kafka-topics.sh工具的命令行格式为

kafka-topics.sh --<operatetion> --<topicName> --boostrap-server <kafkaServerHost>

使用控制台工具测试收发:


➜ bin/kafka-console-producer.sh --topic quickstart-events --bootstrap-server localhost:9092
>first event
>second event
>^C%


➜ bin/kafka-console-consumer.sh --topic quickstart-events --from-beginning --bootstrap-server localhost:9092
first event
second event

安装Kafdrop 运维面板

如果你想通过Web UI的方式来对Kafka进行运维以减少使用Kafka运维工具的记忆存储的压力。那么obsidiandynamics/kafdrop: Kafka Web UI (github.com)会是一个不错的选择其支持Jar和Docker的运行方式,只需要简单的配置即可运行。

Docker运行

为了更快的部署Kafdrop,我们可以通过Docker快速构建:

docker run -d --rm -p 9000:9000 \
    -e KAFKA_BROKERCONNECT=host.docker.internal:9092 \
    -e JVM_OPTS="-Xms32M -Xmx64M" \
    -e SERVER_SERVLET_CONTEXTPATH="/" \
    obsidiandynamics/kafdrop

你可以通过host.docker.internal地址将主机的IP地址映射至docker容器中

随后Kafdrop会运行在9000端口,我们可以在浏览器中访问,它看起来像这样:

Pasted image 20230809173831.png

基本配置

Broker

broekr.id 任何broker都需要指定一个集群内部唯一的整型的标识符。 默认配置下这个值为0。

port Kafka服务器占用端口,默认为9092。这个值可以被设置为任意可用的端口,但是如果低于1024,Kafka必须通过root身份进行启动,这并不是一个推荐的配置方式。

zookeeper.connect连接ZK的地址,默认值为2181,可以通过hostname:port/path的格式来着指定,如果不指定path则默认为/根目录。 如果你的ZK集群除了Kafka还被其他应用所使用,那么指定/path能够保证数据不会冲突。

log.dirs Kafka中Broker数据(包括消息、偏移量等)的存储位置,默认为/tmp/kafka-logs。可以通过,逗号分割多个文件路径,而Broker会将新的分区方在多个路径中最少存储分区的那个路径下,并且能够保证同一个分区的数据总是会放入同一个文件路径。

num.recovery.threads.per.data.dir Kafka中用于打开每一个目录下的分区日志文件的线程数量。

  • 当Kafka正常启动时,用于打开每个线程的日志文件

  • 当Kafka启动失败时,用于检查和阶段日志文件

  • 当Kafka关闭时,用于关闭日志文件。 这个配置仅用于Kafka刚启动和关闭阶段用于文件的打开和关闭,所以理论上将其配置的更大一点以保证多个线程并行操作是合理的。 当从异常关闭的Broker中进行回复时,这意味重新启动Broker可能需要较长的时间。 线程数量是对于一个文件路径而言的,如果设置了3个文件存储路径,并且将这个线程数来那个设置为8,那么Kafka总共会启动24个线程。

  • auto.create.topics.enable当任何客户端访问某一个不存在的主题时,是否允许Broker自动创建主题。 这个配置会可能会引发非预期的行为,所以尽可能的保证这个配置项是关闭的。

Topic默认配置项

Kafka的服务器(Broker)配置还能够为主题的创建指定许多默认的配置。其中包括一些较为重要的分区数消息保留。 服务器应该将其中的配置项设置为基线值,以适应大多数主题。

  • num.partitions分区数量,默认值为1。主题的分区数量可以通过管理工具进行增加,但是注意,分区数量不能减少。 如果想要手动创建比默认设置更少的分区数,那么可以在通过kafka-topics工具手动指定分区数量。

分区数量是Kafka集群能够横向扩展的具体已实现,使用更多分区数量能够在集群中新增更多的的Broker时进行负载均衡。

尽可能的保证Partition分区数量等于或者是倍数于Broker的数量。使得Partition分区能够均衡的负载在每一个Broker上。 这并不是一个必须的选项,你也可以通过创建多个Topic来平衡消息的负载。

分区数量的设置是Kafka使用者面临的一个关键点,以下是影响分区数量的选择的几个重要因素:

  1. 你期望Topic达到多少的吞吐量? 100KB/s还是1GB/s?
  2. 你期望消费单个分区的最大吞吐量是多少?你总是至少一个消费者从分区中读取数据。 如果你的消费者消费速度较慢,比如说需要将数据写入DB,那么DB的写入速度限制这你的消费者速度。(也就是说要考虑到单个分区的吞吐量是否和消费者的消费速度相匹配)。
  3. 同样,你期望生产者的最大吞吐量是多少?通常情况下生产者的生产速度会比消费者要快,
  4. 如果你需要使用Keys来指定消息存放的分区,那么在创建主题的时候就基于未来的使用率,而不是当前的使用率。(这样就能够避免未来需要增加分区数量,进而导致同样Key的新消息和旧消息不再存放于同一个分区上。)
  5. 考虑到磁盘的可用空间和网络的带宽。
  6. 要避免过度高估分区的数量,因为每个分区都会占用Broker上的资源,以及增加Leader的选举时间。
  • log.retention.ms/hours/minutes 这是三个参数,都表示着日志的过期时间,默认情况下这个参数为168hours,也就是7天。 如果三个参数都指定了,那么kafka只会保证最小的时间单位的配置项生效,所以推荐使用ms作为配置。

    过期时间是根据消息的最后修改时间决定的,正常情况下这就是消息被消费的时间。但是当使用管理工具修改了分区数量,可能会导致分区消息的最后修改时间变更,进而产生非预期的行为。

  • log.retention.bytes 一个分区中的日志保留的大小,当分区下的日志文件超过这个大小的时候,超过这个大小的日志文件会被清除。 注意这是一个分区中的大小,意味着一个主题的日志的大小取决于这个值 * 分区数量,并且也会随着分区数量的增加而增加。

    log.rentention.bytes和ms 基于时间和大小的两个策略是“或”的逻辑运算。 也就是当日志文件只要超过了时间点或者是大小,那么其就会被清除。

  • log.segement.bytes 分区中一个日志文件(日志段这个概念可能会有些不好理解)的大小,默认值为1GB。一个日志文件包含着当前分区中的一段连续的日志,当达到文件大小上限之后关闭,创建出一个新的日志文件。

    减少日志文件的大小,意味着文件会更加频繁的关闭和创建,会减少整个磁盘的写入效率。 但是当主题有着较低的生产效率时,减少日志的文件的大小,却变得十分重要,因为log.retention的两个策略仅对于关闭的日志文件生效

    假设一个文件的写入效率为100M每天,而log.retention.ms使用了默认值七天,这意味着消息文件在达到第7天的时候,会由于日志还未关闭,进而无法按预期被删除。而仅仅在第十天消息日志文件中的日志达到了上限,进而关闭该文件,随后打开新的日志文件时。这个本应该过期的日志文件才会被删除。

  • log.segement.ms 由时间控制的日志文件的关闭策略,同retention一样,两者策略之间是“或”的逻辑运算。

    使用log.segement.ms时,如果多个分区的日志文件总是由于时间问题被同时关闭和创建,那么可能会产生磁盘的突刺,从而影响磁盘的性能。

  • message.max.bytes 消息的最大字节数,默认值为1MB,当生产者发送超过了这个大小的消息时,Broker会返回一个错误。 注意这个参数需要和消费者客户端的fetch.message.max.bytes以及Broker端的replica.fetch.max.bytes配置保持一致。否则,会导致消息的拉取无法获得完整的消息数据。

硬件选择

一个应用绕不开底层硬件的依赖,而影响应用性能的核心无外乎硬盘、内存、网络和CPU四个核心硬件。

磁盘

生产者的性能会直接的影响到Broker的磁盘吞吐量,因为只有当Broker将消息写入磁盘时,一个消息才能够被视为真正被生产出来。而生产者客户端会等待直至消息成功写入,才会响应成功。这意味着更快的磁盘写入能够有更低的生产者写入延迟。

这就是来到了经典的SSD和HHD的抉择。即使Kafka对于消息的写入是顺序写的,能够很大的利用磁盘的特性。但使用SSD磁盘总是要比HHD的性能更好;如果Kafka对于存储的容量有要求(比如说一天流量为1TB,而消息过期策略为7天,那么至少磁盘要有7TB的空间)。那么采用机械硬盘是更加经济的选择。

磁盘还和Broker中的Retention过期配置相关,你想要保留的日志时间越长、日志体积越大,那么你就应该需要越多的磁盘空间。

内存

内存影响着消费者的消费速度,正常情况下生产者的消息追加到Broker之后会立即的被消费者消费,生产者和消费者之间的消息进度差异十分的小。 这种情况下,Broker会将消息存储在系统的PageCache页缓存中,使得消费者的读取速度比Broker从磁盘中重新读取消息更快。 因此提供更多的内存空间用于页缓存将提高客户端的性能。

需要注意的是操作系统的PageCache页缓存会在多个应用程序之间共享,所以不推荐的将Kafka与其他的重要应用部署在同一台机器上。

网络

每当一个消息从生产者中通过网络入站连接至Broker写入数据, 可能会产生N个消费者的出站连接用于消息的消费。 一个生产者可能产生1MB的数入站据每秒,那么消费者可能消费nMB的出战数据每秒。

同样为了保证Kafka数据的高可用,其他Kafka应用级别的操作也会占用网络带宽,比如说集群复制。 当网络的带宽被生产和消费打满时,集群复制的滞后会变成一个十分常见的问题。

  • 所以每当发生集群复制滞后时,你就需要检查是否网络带宽一直处于占满的状态。

CPU

CPU在Kafka中仅用于消息的压缩,理想情况下,生产者和消费者今天当消息压缩后从发送至Broker能够减少磁盘和网络的占用。 但是为了校验消息中的checksum校验和以及分配消息的offset偏移量,Kafka需要在接收到来自生产中的压缩消息后,将其解压出来进行操作。校验无误后,然后再压缩存入磁盘。 这是Kafka中对于CPU有所要求的地方。

但是CPU不应该是Kafka选择硬件的主要因素,优先考虑磁盘和内存。

OS调优

Linux的众多发行版中都有能够对内核参数进行配置。 默认配置会对绝大多数的应用都工作的很好,但是依然有一小部分的配置项更改能够使得kafka broker具有更好的性能。

虚拟内存

虚拟内存优化的核心点在于避免内存交换(memory swapping)的成本。kafka是一个重度依赖于虚拟内存和页缓存的应用,如果虚拟内存页交换到磁盘,那么将会显著的影响到Kafka的性能。 交换空间能够防止OS由于内存不足而突然禁止进程。

关于虚拟内存的优秀文章: Linux虚拟内存(swap)调优篇-“swappiness”,“vm.dirty_background_ratio”和“vm.dirty_ratio” - 尹正杰 - 博客园 (cnblogs.com)

使用虚拟内存会导致数据频繁的从内存写入磁盘,导致性能的下降。而通过配置项 vm.swappiness = 1 能够使得操作系统尽可能的不使用交换内存,而是使用真实物理内存。 查看当前配置

cat /proc/sys/vm/swappiness

临时修改配置

sysctl vw.swappiness=1

永久修改配置

echo "vm.swappiness=1" >> /etc/sysctl.conf

激活设置

sysctl -p

脏页Dirty Page

脏页是Linux内核中的概念,因为磁盘中的读写速度远赶不上内存的速度,所以系统可以将读写频繁的数据先放到内存中,这就叫高速缓存。而Linux中页是高速缓存的单位,当进程应用修改了高速缓存中的页数据时,该内存就会被标记为脏页(因为内存中的数据和磁盘中不一致),直至合适的时候将脏页的数据写入磁盘,以保持高速缓存中的数据和磁盘的一致。

Linux中提供了两个参数用于调整脏页的处理方式

  1. vm.dirty_background_ratio表示当前脏页所占内存比例达到多少时,Linux会启动后台会写线程,来将脏页异步的写回磁盘。 这个参数的默认值为10,而将其设置为5能够适用于绝大部分应用;
  2. vm.dirty_ratio 表示当脏页占内存比例达到多少时,Linux会阻塞直到脏页被写回磁盘。这个参数的默认值为20,但是我们可以将其设置为更大的值,60~80是一个合理的选择,但是这会带来一些风险,包括未刷新磁盘的数量和同步刷新引起的长时间I/O等待。 如果设置了较高的值,建议开启Kafka的复制功能,以防止系统崩溃造成数据丢失。

设置这两个参数前,你可以先观察当前操作系统的脏页数量,以调优至一个合理的值

cat /proc/vmstat | egrep "dirty|writeback"

磁盘文件系统

除了磁盘硬件的选择,Linux上的文件系统也影响着进程的性能。 目前有许多文件系统可供选择,最常见的两个就是EXT4(Fourth Extended File System)和XFS(Extends File System)。 XFS已经变成许多Linux发行版的默认文件系统了,因为其更适用于绝大多数的场景。

而EXT4在经过一些参数的调整之后能够表现得更好。

还有一个Linux参数会影响着磁盘的效率,那就是开启noatime选项,在文件系统的元数据中包含了三个时间戳:创建时间(ctime)、修改时间(mtime)和访问时间(atime)。前两者仅在文件被创建和修改的时候所变更,但接触时间不一样,每一次对文件的访问,不论是否修改了数据都会导致atime的变更。而atime的实际作用却并不大,很少有场景会使用它。 所以禁用atime是一个明智的选择。

网络Networking

调整Linux的默认网络参数是一个使得任何应用程序产生更高网络流量的常见手段。因为默认的Linux参数并不是为了大体量、高速数据传输而设置的。 在这一步的设置除了kafka,也是由于大多数的Web应用程序。

  • net.core.wmem_defaultnet.core.rmem_default控制着套接字发送和接收缓冲区大小,建议设置为131072(128K)是一个较为合理的数值。

  • net.core.wmem_maxnet.core.rmem_max控制着套接字发送和接收缓冲区的最大大小,设置为2097152(2MB)是一个较为合理的数值。

  • 另外要控制TCP套接字的缓存区大小还需要额外通过ipv4.tcp_w/rmem来进行配置,其接受由空格隔开的由三个数值。建议将其设置为4096 65536 2048000其意味着缓冲区的最小、默认和最大大小为4KB、64KB和2MB。 注意这个参数不应该超过net.core.w/rmem_max所设定的值。

  • 还有一个十分有用的TCP调优参数net.ipv4.tcp_window_scaling=1。 能够使得客户端数据传输得更加有效率。允许数据在Broker端传输。

另外设置tcp_max_syn_backlog大于默认的1024能够使得LInux接受更大数量的并发连接。net.core.netdev_max_max_backlog大于默认的1000能够应对网络流量的突发,允许更多的流量包在内核排队等待处理。

G1 JVM垃圾收集器

G1垃圾收集器已经在JDK11之后的多个版本中被设置为了默认垃圾收集器,并且在生产环境中得到了稳定的保证。

在G1垃圾收集器中有两个参数影响着其垃圾收集的性能,分别为

  • MaxGCPausMills:预期的最大GC停顿时间,G1能够保证尽可能的使得每次垃圾回收都接近这个数值,默认为200。而Kafka对于堆内存的使用十分的高效,所以我们能够将其设置在20,以保证GC停顿不影响到Kafka正常的工作。
  • InitiatingHeapOccupancyPercent:初始GC时内存占用的百分比,默认值为45,也意味着只有当JVM的堆内存占用达到45%的时候,G1收集器才会开始第一次GC。而我们可以将其设置得更低一点,比如说35。

但Kafka中默认使用的JDK8中,使用的是CMS + Parnew。而G1垃圾收集器会有有着比CMS更高的内存占用,所以在切换Kafka的垃圾收集器前,先确保Kafka的堆内存至少能够分配4GB的大小,否则或许使用CMS能够以更低的内存开销获得更高的性能。

要想使得Kafka更换垃圾收集器,我们可以设置环境变量KAFKA_JVM_PERFORMANCE_OPTS

# export KAFKA_JVM_PERFORMANCE_OPTS="-server -XX:+UseG1GC -XX:MaxGCPauseMills=20 -XX:InitiatingHeapOccupancyPercent=35 -XX:+DisableExplicitGC -Djava.awt.headless=true"

# bin/kafka-server-start.sh -daemon config/server.properties

Zookeeper

Kafka使用Zookeeper来存储自身的元数据包括Brokers、Topics和Partitions。 Kafka仅当集群自身发生调整时或者是Consumer Group的关系发生了变化时会对Zookeeper进行写入。 所以Kafka对于Zookeeper的流量占用是较小的。

所以我们能够使用一个Zookeeper集群来治理多个Kafka集群,并且通过Zookeeper的chroot path来隔离每一个Kafka集群中的数据。

前面提到了Consumer Group会对Kafka造成写入,Consumer消费者能够配置使用Zookeeper或者是Kafka来提交自身的消费便宜来那个。如果选择了Zookeeper,那么每一个Consumer会对他所消费的分区的偏移量对Zookeeper进行一次写入操作。这会对Zookeeper产生相当大的流量。 所以推荐的做法是使用Kafka Broker本身来提交偏移量从而消除对Zookeeper的依赖。

另外不建议将使用一个Zookeeper集群来同时管理Kafka集群和其他应用。因为当其他应用对Zookeeper产生了较大的流量时,可能会影响Kafka集群自身的治理,进而导致Kafka集群的所有Broker节点由于丢失了Zookeeper的连接导致同时下线。