其他更多java基础文章:
java基础学习(目录)
这系列是根据极客时间《Kafka核心技术与实战》这个课程做的笔记
- 主题Topic管理命令
- 消费者组消费进度监控
- Kafka动态配置
- 常见工具脚本汇总
- 跨集群备份解决方案MirrorMaker
- 怎么监控Kafka
- 调优Kafka
主题Topic管理
创建Kafka主题:
bin/kafka-topic.sh --bootstarp-server broker_host:port --create –topic my_topic --partitions 1 --replication-factor 1
create 表明我们要创建主题,而partitions和replication factor分布设置了主题的分区数以及每个分区下的副本数。
查询主题
查询所有主题的列表:/bin/kafka-topic.sh --bootstrap-server broker_host:port --list
查询单个主题的详细数据:/bin/kafka-topic.sh --bootstrap-server broker_host:port --describe --topic <topic name>
修改主题
A :修改分区:/bin/kafka-topic.sh --bootstrap-server broker_host : port --alter --topic <topic_name> --partitions <新分区数>
分区数一定要比原有分区数大。
B :修改主题级别参数:使用kafka-configs脚本修改对应的参数。
修改主题级别的max.message.bytes :/bin/kafka-configs.sh --zookeeper zookeeper_host:port --entity-type topic --entity-name <topic_name> --alter --add-config max.message.bytes=10485760
这个命令里使用的 –zookeeper,也可以使用 --bootstrap-server,只是他是用来设置动态参数的。现在,你只需要了解设置常规的主题级别参数,还是使用 --zookeeper。
变更副本数
使用kafka-reassign-partitions 脚本,增加副本数
修改主题限速
这是指设置Leader副本和follower 副本使用的带宽。有时候,需要让某个主题的副本在执行副本同步机制时,不要消耗过多的带宽。
要做到这个需要先设置leader.replication.throttled.rate
和follower.replication.throttled.rate
bin/kafka-configs.sh --zookeeper zookeeper_host:port --alter --add-config 'leader.replication.throttled.rate=104857600,follower.replication.throttled.rate=104857600' --entity-type brokers --entity-name 0
主题分区迁移
同样是使用kafka-ressign-partitions脚本。
删除主题
/bin/kafka-topic.sh –bootstrap-server broker_host:port --delete --topic <topic_name>
删除主题的操作是异步的,执行完这条命令不代表主题立即就被删除了,它仅仅是被标记成“已删除”状态而已。Kafka会在后台默默地开启主题删除操作。
特殊主题的管理和运维
_consumer_offsets和_transaction_state
- 默认50个分区
- 0.11之前,当 Kafka 自动创建该主题时,它会综合考虑当前运行的 Broker 台数和 Broker 端参数
offsets.topic.replication.factor
值,然后取两者的较小值作为该主题的副本数,但这就违背了用户设置offsets.topic.replication.factor
的初衷。这正是很多用户感到困扰的地方:我的集群中有 100 台 Broker,offsets.topic.replication.factor
也设成了 3,为什么我的 __consumer_offsets 主题只有 1 个副本?其实,这就是因为这个主题是在只有一台 Broker 启动时被创建的。 - 0.11之后,严格遵守Offsets.topic.replication.factor,如果运行的Broker数量小于其值,Kafka会创建主题失败,显式抛出异常
增加副本
如何将副本为1增加为副本为3?
- 创建一个json文件
{"version":1, "partitions":[
{"topic":"__consumer_offsets","partition":0,"replicas":[0,1,2]},
{"topic":"__consumer_offsets","partition":1,"replicas":[0,2,1]},
{"topic":"__consumer_offsets","partition":2,"replicas":[1,0,2]},
{"topic":"__consumer_offsets","partition":3,"replicas":[1,2,0]},
...
{"topic":"__consumer_offsets","partition":49,"replicas":[0,1,2]}
]}
- 执行Kafka-reassign-partitions
bin/kafka-reassign-partitions.sh --zookeeper zookeeper_host:port --reassignment-json-file reassign.json --execute
查看内部主题的消费者组的消费位移
bin/kafka-console-consumer.sh --bootstrap-server kafka_host:port --topic __consumer_offsets --formatter "kafka.coordinator.group.GroupMetadataManager\$OffsetsMessageFormatter" --from-beginning
查看消费者组的状态
bin/kafka-console-consumer.sh --bootstrap-server kafka_host:port --topic __consumer_offsets --formatter "kafka.coordinator.group.GroupMetadataManager\$GroupMetadataMessageFormatter" --from-beginning
常见主题错误处理
主题删除失败
造成主题删除失败的原因有很多,最常见的原因有两个:
- 副本所在Broker宕机了;
- 待删除主题的部分分区依然在执行迁移过程。
解决:
- 手动删除Zookeeper节点/admin/delete_topics 下待删除主题为名的znode。
- 手动删除该主题的磁盘上的分区目录。
- 在Zookeeper中执行rmr/controller,触发Controller重选举,刷新Controller缓存。
- 在执行最后一步时,要慎重,因为他可能造成大面积的分区Leader重选举。事实上,仅仅执行前两步也是可以的,只是Controller缓存中没有清空删除主题,不影响使用。
_consumer_offset占用太多的磁盘
如果发现这个主题占用了过多的磁盘空间,就要显示的使用jstack 命令查看kafka-log-cleaner-thread前缀线程状态。
Q: 查看cleaner-thread 线程,是不是先执行jps查出kafak的pid,再执行jps kafkapid,检查是否cleaner-thread 前缀的线程,有就说明启动着,但是我查了几遍都没有发现这个线程信息。是不是操作方式不对
A: 1. 执行top -Hp ,会看到所有找个进程的线程信息,但是由于线程数太多,没展出全部
2. 使用jstack pid > /opt/1.txt,打印堆栈信息,然后搜索clean就可以会发现有一个“kafka-log-cleaner-thread”
消费者组进度监控
对于Kafka消费者,最重要的事情就是监控它们的消费进度(消费的滞后程度),常称为:Consumer Lag
- Lag的单位是消息数,他直接反映了一个消费者的运行情况。一个正常的消费者的Lag应当很小,设置为0。这表明消费者能够及时地消费生产者生产出来的消息。反之,一个消费者Lag值很大的话表明它无法跟上生产者的速度。
- 如果消费者速度无法匹及生产者的数据,极有可能导致它消费的数据已经不在操作系统的页缓存中了,那些数据就失去了享有Zero Copy技术的条件,不得不从磁盘中读取,进一步拉大了与生产者的差距。并且会越来大。
所以:在实际业务场景中必须时刻关注消费者的消费进度。一旦出现Lag逐步增加的趋势,就要立即定位问题,及时处理,避免问题扩散。
如何监控
- 使用Kafka自带的命令行工具kafka-consumer-groups脚本
- 使用Kafka Java Conssumer API编程
- 使用Kafka自带的JMX监控指标
Kafka自带命令
kafka-consumer-groups脚本是kafka为我们提供的最直接的监控消费者消费进度工具。
$ bin/kafka-consumer-groups.sh --bootstrap-server <Kafka broker连接信息 > --describe --group <group 名称 >
<Kafka broker 连接信息 >:主机:端口
<group 名称 > :要监控的消费组的 group.id值
展示的信息:主题,分区,该消费者组最新消费消息的位移值(CURRENT-OFFSET值),每个分区当前最新生产的消息的位移值(LOG-END-OFFSET),LAG(前两者的差值),消费者实例ID,消费者连接Broker的主机名以及消费者的CLENT-ID信息。
Kafka Java Consumer API
- 首先获取给定的消费者组的最新消费消息的位移
- 在获取订阅分区的最新消息位移
- 最后执行相应的减法操作,获取Lag值并封装进一个Map对象。
public static Map<TopicPartition, Long> lagOf(String groupID, String bootstrapServers) throws TimeoutException {
Properties props = new Properties();
props.put(CommonClientConfigs.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
try (AdminClient client = AdminClient.create(props)) {
ListConsumerGroupOffsetsResult result = client.listConsumerGroupOffsets(groupID);//(1)
try {
Map<TopicPartition, OffsetAndMetadata> consumedOffsets = result.partitionsToOffsetAndMetadata().get(10, TimeUnit.SECONDS);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 禁止自动提交位移
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupID);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
try (final KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props)) {
Map<TopicPartition, Long> endOffsets = consumer.endOffsets(consumedOffsets.keySet());//(2)
return endOffsets.entrySet().stream().collect(Collectors.toMap(entry -> entry.getKey(),
entry -> entry.getValue() - consumedOffsets.get(entry.getKey()).offset()));//(3)
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 处理中断异常
// ...
return Collections.emptyMap();
} catch (ExecutionException e) {
// 处理 ExecutionException
// ...
return Collections.emptyMap();
} catch (TimeoutException e) {
throw new TimeoutException("Timed out when getting lag for consumer group " + groupID);
}
}
}
Kafka JMX监控指标
使用Kafka默认提供 的JMX监控指标来监控消费者的Lag值。
- Kafka消费者提供了一个名为Kafka.consumer:type=consumer-fetch-manager-metrics,client-id=”{client-id}”的JMX指标。
- 有两个重要的属性:records-lag-max 和 records-lead-min 分别表示消费者在测试窗口时间内曾经达到的最大的Lag值和最小的Lead值。
- Lead值是指消费者最新消费消息的位移和分区当前第一条消息的位移的差值。即:Lag越大,Lead就越小。
Kafka动态配置
在之前的版本,一次性在server.properties文件中配置好所有参数后,启动Broker。但是在需要变更任何参数后,就必须要重启Broker,这很不方便。在1.1.0版本中正是引入了动态Broker参数(Dynamic Broker Configs)。所谓动态,就是指修改参数值后,无需重启Broker就能立即生效,在server.properties中配置的参数称之为静态参数(Static Configs)。
分类
在打开 1.1 版本之后(含 1.1)的 Kafka 官网,你会发现Broker Configs中增加了
Dynamic Update Mode 列。该列有 3 类值,分别是 read-only、per-broker 和 clusterwide。我来解释一下它们的含义。
- read-only:被标记为read-only的参数和原来的参数行为一样,只有重启Broker,才能令修改生效。
- per-broker:被标记为per-broker的参数属于动态参数,修改它之后,只会在对应的Broker上生效。
- cluster-wide:被标记为cluster-wide的参数也属于动态参数,修改它之后,会在整个集群范围内生效。
使用场景:
- 动态调整Broker端各种线程池大小,实时应对突发流量
- 动态调整Broker端连接信息或安全配置信息
- 动态更新SSL Keystore有效期
- 动态调整Broker端Compact操作性能
- 实时变更JMX指示收集器(JMX Metrics Reporter)
动态参数的保存:
- Kafka将动态Broker参数保存在Zookeeper中
- changes是用来实时监控动态参数变更的,不会保存参数值;
- topic是用来保存Kafka主题级别参数的。
- user和clients是用于动态调整客户端配额(Quota)的znode节点。所谓配额是指Kafka运维人员限制连入集群的客户端的吞吐量或是限定他们的使用CPU资源。
- /config/brokers znode才是真正保存动态Broker参数的地方。该znode下有两大类子节点。
- 第一类子节点只有一个,固定叫,保存cluster-wide范围的动态参数
- 第二类以Broker.id为名,保存特定Broker的per-broker范围参数。
- 参数的优先级别:per-broker参数 > cluster-wide参数 > static参数 > Kafka默认值
如何动态配置
cluster级别
如何配置
bin/kafka-configs.sh --bootstrap-server kafka-host:port --entity-type brokers --entity-default --alter --add-config unclean.leader.election.enable=true
如果要设置cluster-wide范围的动态参数,需要显示指定 entity-default
查看是否生效
bin/kafka-configs.sh --bootstrap-server kafka-host:port --entity-type brokers --entity-default --describe
Default config for brokers in the cluster are:
unclean.leader.election.enable=true sensitive=false synonyms={DYNAMIC_DEFAULT_BROKER_CONFIG:unclean.leader.election.enable=true}
broker级别
bin/kafka-configs.sh --bootstrap-server kafka-host:port --entity-type brokers --entity-name 1 --alter --add-config unclean.leader.election.enable=false
Completed updating config for broker: 1.
查看是否生效
bin/kafka-configs.sh --bootstrap-server kafka-host:port --entity-type brokers --entity-name 1 --describe
Configs for broker 1 are:
unclean.leader.election.enable=false sensitive=false synonyms={DYNAMIC_BROKER_CONFIG:unclean.leader.election.enable=false, DYNAMIC_DEFAULT_BROKER_CONFIG:unclean.leader.election.enable=true, DEFAULT_CONFIG:unclean.leader.election.enable=false}
删除配置
# 删除 cluster-wide 范围参数
$ bin/kafka-configs.sh --bootstrap-server kafka-host:port --entity-type brokers --entity-default --alter --delete-config unclean.leader.election.enable
Completed updating default config for brokers in the cluster,
# 删除 per-broker 范围参数
$ bin/kafka-configs.sh --bootstrap-server kafka-host:port --entity-type brokers --entity-name 1 --alter --delete-config unclean.leader.election.enable
Completed updating config for broker: 1.
较大几率被动态调整的参值
log.retention.ms
:修改日志留存时间num.io.threads
和num.network.threads
,前面Kafka核心技术与实战 <五>网络模型提到的两组线程池。- 与SS相关的参数,
ssl.keystore.type
,ssl.keystore.location
,ssl.kestore.password
和ssl.key.password
。允许动态实时调整他们,就能创建过期时间很短的SSL证书,使用新的Keystore,阶段性的调整这组参数,提升安全性。 num.replica.fetchers
:这个可以增加该参数值,确保有充足的线程可以执行Follower副本向Leader副本的拉取。
常见工具脚本汇总
kafka-log-dirs.sh --help会得到脚本的用法说明
connect-standalone connect-distributed两个脚本 单点启动connect 集群启动connect
kafka-acls 脚本,设置Kafka权限 比如设置哪些用户可以访问哪些主题
kafka-broker-api-versions 这个脚本的主要目的是验证不同Kafka版本之间的服务器和客户端的适配性
0.10.2.0支持双向兼容,低版本的Broker也能处理高版本的Client请求
kafka-configs 动态参数配置
kafka-console-consumer kafka-console-producer
kafka-producer-perf-test kafka-consumer-perf-test 性能测试工具
kafka-consumer-groups 重设消费组位移
kafka-delegation-tokens脚本 管理Delegations Token轻量级认证
kafka-delete-records 用于删除Kafka分区消息
kafka-dump-log 查看Kafka消息文件的内容,包含消息的各种元数据信息,甚至消息体本身
kafka-log-dirs 帮助查询各个Broker上各个日志路径的磁盘占用情况
Kafka-mirror-maker 帮助实现Kafka集群之间消息的同步
kafka-perferred-replica-election 执行Preferred Leader选举
kafka-reassihn-partitions 脚本用于执行分区副本迁移以及副本文件路径迁移
kafka-topic 主题操作
kafka-run-class 运行任何带main方法的Kafka类
kafka-server-start kafka-server-stop启动停止Broker脚本
Kafka-streams-application-reset脚本 用来给Kafka Streams应用程序重设位移
Kafka-verifiable-producer kafka-verifiable-consumer 测试生产者消费者
trogdor脚本 执行各种基准测试和负载测试
重点脚本
生产消息
bin/kafka-console-producer.sh --broker-list kafka-host:port --topic test-topic --request-required-acks -1 --producer-property compression.type=lz4
消费消息
bin/kafka-console-consumer.sh --bootstrap-server kafka-host:port --topic test-topic --group test-group --from-beginning --consumer-property enable.auto.commit=false
测试生产者性能
bin/kafka-producer-perf-test.sh --topic test-topic --num-records 10000000 --throughput -1 --record-size 1024 --producer-props bootstrap.servers=kafka-host:port acks=-1 linger.ms=2000 compression.type=lz4
2175479 records sent, 435095.8 records/sec (424.90 MB/sec), 131.1 ms avg latency, 681.0 ms max latency.
4190124 records sent, 838024.8 records/sec (818.38 MB/sec), 4.4 ms avg latency, 73.0 ms max latency.
10000000 records sent, 737463.126844 records/sec (720.18 MB/sec), 31.81 ms avg latency, 681.00 ms max latency, 4 ms 50th, 126 ms 95th, 604 ms 99th, 672 ms 99.9th.
向指定主题发送1千万条消息 ,每条消息大小是1kb
测试消费者性能
bin/kafka-consumer-perf-test.sh --broker-list kafka-host:port --messages 10000000 --topic test-topic
start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg, nMsg.sec, rebalance.time.ms, fetch.time.ms, fetch.MB.sec, fetch.nMsg.sec
2019-06-26 15:24:18:138, 2019-06-26 15:24:23:805, 9765.6202, 1723.2434, 10000000, 1764602.0822, 16, 5651, 1728.1225, 1769598.3012
查看主题消息总数
查看主题在分区中的偏移量 (time为-1时表示最大值,time为-2时表示最小值),使用Kafka提供的工具类GetOffsetShell来计算给定主题特定分区当前的最早位移和最新位移,将两者的差值累计起来,就能的到主题当前总的消息数
bin/kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list kafka-host:port --time -2 --topic test-topic
test-topic:0:0
test-topic:1:0
$ bin/kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list kafka-host:port --time -1 --topic test-topic
test-topic:0:5500000
test-topic:1:5500000
查询消费者组位移
bin/kafka-consumer-groups.sh --bootstrap-server localhost:9092 --describe --group test-group
跨集群备份解决方案MirrorMaker
从本质上说,MirrorMaker 就是一个消费者 + 生产者的程序。消费者负责从源集群(Source Cluster)消费数据,生产者负责向目标集群(Target Cluster)发送消息。整个镜像流程如下图所示:
MirrorMaker 连接的源集群和目标集群,会实时同步消息。当然,你不要认为你只能使用一套 MirrorMaker 来连接上下游集群。事实上,很多用户会部署多套集群,用于实现不同的目的。
我们来看看下面这张图。图中部署了三套集群:左边的源集群负责主要的业务处理;右上角的目标集群可以用于执行数据分析;而右下角的目标集群则充当源集群的热备份。
./kafka-mirror-maker.sh --consumer.config …/config/consumer.properties --producer.config …/config/producer.properties --whitelist ‘test-perf’ --num.streams 6
-
consumer.config 参数。它指定了 MirrorMaker 中消费者的配置文件地址,最主要的配置项是bootstrap.servers,也就是该 MirrorMaker 从哪个 Kafka 集群读取消息。因为MirrorMaker 有可能在内部创建多个消费者实例并使用消费者组机制,因此你还需要设置 group.id 参数。另外,我建议你额外配置 auto.offset.reset=earliest,否则的话,MirrorMaker 只会拷贝那些在它启动之后到达源集群的消息。
-
producer.config 参数。它指定了 MirrorMaker 内部生产者组件的配置文件地址。通常来说,Kafka Java Producer 很友好,你不需要配置太多参数。唯一的例外依然是bootstrap.servers,你必须显式地指定这个参数,配置拷贝的消息要发送到的目标集群。
-
num.streams 参数。我个人觉得,这个参数的名字很容易给人造成误解。第一次看到这个参数名的时候,我一度以为 MirrorMaker 是用 Kafka Streams 组件实现的呢。其实并不是。这个参数就是告诉 MirrorMaker 要创建多少个 KafkaConsumer 实例。当然,它使用的是多线程的方案,即在后台创建并启动多个线程,每个线程维护专属的消费者实例。在实际使用时,你可以根据你的机器性能酌情设置多个线程。
-
whitelist 参数。如命令所示,这个参数接收一个正则表达式。所有匹配该正则表达式的主题都会被自动地执行镜像。可以指定了“.*”,这表明我要同步源集群上的所有主题
怎么监控Kafka
我们有必要从 Kafka 主机、JVM和 Kafka 集群本身这三个维度进行监控
主机监控
常见的主机监控指标包括但不限于以下几种:
- 机器负载(Load)
- CPU 使用率
- 内存使用率,包括空闲内存(Free Memory)和已使用内存(Used Memory)
- 磁盘 I/O 使用率,包括读使用率和写使用率
- 网络 I/O 使用率
- TCP 连接数
- 打开文件数
- inode 使用情况
JVM 监控
因此,我们来总结一下。要做到 JVM 进程监控,有 3 个指标需要你时刻关注:
- Full GC 发生频率和时长。这个指标帮助你评估 Full GC 对 Broker 进程的影响。长时间的停顿会令 Broker 端抛出各种超时异常。
- 活跃对象大小。这个指标是你设定堆大小的重要依据,同时它还能帮助你细粒度地调优JVM 各个代的堆大小。
- 应用线程总数。这个指标帮助你了解 Broker 进程对 CPU 的使用情况。 总之,你对 Broker 进程了解得越透彻,你所做的 JVM 调优就越有效果。
自 0.9.0.0 版本起,社区将默认的 GC 收集器设置为 G1,而 G1 中的 Full GC 是由单线程执行的,速度非常慢。因此,你一定要监控你的 Broker GC 日志,即以kafkaServergc.log 开头的文件。注意不要出现 Full GC 的字样。一旦你发现 Broker 进程频繁 Full
GC,可以开启 G1 的 -XX:+PrintAdaptiveSizePolicy
开关,让 JVM 告诉你到底是谁引发了 Full GC
集群监控
查看 Broker 进程是否启动,端口是否建立
千万不要小看这一点。在很多容器化的 Kafka 环境中,比如使用 Docker 启动 KafkaBroker 时,容器虽然成功启动了,但是里面的网络设置如果配置有误,就可能会出现进程已经启动但端口未成功建立监听的情形。因此,你一定要同时检查这两点,确保服务正常运行。
查看 Broker 端关键日志
这里的关键日志,主要涉及 Broker 端服务器日志 server.log,控制器日志 controller.log以及主题分区状态变更日志 state-change.log。其中,server.log 是最重要的,你最好时刻对它保持关注。很多 Broker 端的严重错误都会在这个文件中被展示出来。因此,如果你的 Kafka 集群出现了故障,你要第一时间去查看对应的 server.log,寻找和定位故障原 因。
查看 Broker 端关键线程的运行状态。
这些关键线程的意外挂掉,往往无声无息,但是却影响巨大。比方说,Broker 后台有个专属的线程执行 Log Compaction 操作,由于源代码的 Bug,这个线程有时会无缘无故地“死掉”,社区中很多 Jira 都曾报出过这个问题。当这个线程挂掉之后,作为用户的你不会得到任何通知,Kafka 集群依然会正常运转,只是所有的 Compaction 操作都不能继续了,这会导致 Kafka 内部的位移主题所占用的磁盘空间越来越大。因此,我们有必要对这些关键线程的状态进行监控。
最重要的两类线程。在实际生产环境中,监控这两类线程的运行情况是非常有必要的。
- Log Compaction 线程,这类线程是以 kafka-log-cleaner-thread 开头的。就像前面提到的,此线程是做日志 Compaction 的。一旦它挂掉了,所有 Compaction 操作都会中断,但用户对此通常是无感知的。
- 副本拉取消息的线程,通常以 ReplicaFetcherThread 开头。这类线程执行 Follower 副本向 Leader 副本拉取消息的逻辑。如果它们挂掉了,系统会表现为对应的 Follower 副本不再从 Leader 副本拉取消息,因而 Follower 副本的 Lag 会越来越大。
查看 Broker 端的关键 JMX 指标。
Kafka 提供了超多的 JMX 指标供用户实时监测,我来介绍几个比较重要的 Broker 端 JMX指标:
- BytesIn/BytesOut:即 Broker 端每秒入站和出站字节数。你要确保这组值不要接近你的网络带宽,否则这通常都表示网卡已被“打满”,很容易出现网络丢包的情形。
- NetworkProcessorAvgIdlePercent:即网络线程池线程平均的空闲比例。通常来说,你应该确保这个 JMX 值长期大于 30%。如果小于这个值,就表明你的网络线程池非常繁忙,你需要通过增加网络线程数或将负载转移给其他服务器的方式,来给该 Broker 减负。
- RequestHandlerAvgIdlePercent:即 I/O 线程池线程平均的空闲比例。同样地,如果该值长期小于 30%,你需要调整 I/O 线程池的数量,或者减少 Broker 端的负载。
- UnderReplicatedPartitions:即未充分备份的分区数。所谓未充分备份,是指并非所有的 Follower 副本都和 Leader 副本保持同步。一旦出现了这种情况,通常都表明该分区有可能会出现数据丢失。因此,这是一个非常重要的 JMX 指标。
- ISRShrink/ISRExpand:即 ISR 收缩和扩容的频次指标。如果你的环境中出现 ISR 中副本频繁进出的情形,那么这组值一定是很高的。这时,你要诊断下副本频繁进出 ISR 的原因,并采取适当的措施。
- ActiveControllerCount:即当前处于激活状态的控制器的数量。正常情况下,Controller 所在 Broker 上的这个 JMX 指标值应该是 1,其他 Broker 上的这个值是 0。如果你发现存在多台 Broker 上该值都是 1 的情况,一定要赶快处理,处理方式主要是查看网络连通性。这种情况通常表明集群出现了脑裂。脑裂问题是非常严重的分布式故障,Kafka 目前依托 ZooKeeper 来防止脑裂。但一旦出现脑裂,Kafka 是无法保证正常工作的
监控 Kafka 客户端。
首先要关心的是客户端所在的机器与 Kafka Broker 机器之间的网络往返时延(Round-Trip Time,RTT)。通俗点说,就是你要在客户端机器上 ping 一下 Broker 主机 IP,看看 RTT 是多少。
对于生产者而言,有一个以kafka-producer-network-thread 开头的线程是你要实时监控的。它是负责实际消息发送的线程。一旦它挂掉了,Producer 将无法正常工作,但你的 Producer 进程不会自动挂掉,因此你有可能感知不到。
对于消费者而言,心跳线程事关 Rebalance,也是必须要监控的一个线程。它的名字以 kafka-coordinator-heartbeat-thread 开头。
从 Producer 角度,你需要关注的 JMX 指标是 request-latency,即消息生产请求的延时。这个 JMX 最直接地表征了 Producer 程序的 TPS;
而从 Consumer 角度来说,records-lag 和 records-lead 是两个重要的 JMX 指标。
如果你使用了 Consumer Group,那么有两个额外的 JMX 指标需要你关注下,一个是 joinrate,另一个是 sync rate。它们说明了 Rebalance 的频繁程度。如果在你的环境中,它们的值很高,那么你就需要思考下 Rebalance 频繁发生的原因了。
调优Kafka
- 应用程序层。它是指优化 Kafka 客户端应用程序代码。比如,使用合理的数据结构、缓存计算开销大的运算结果,抑或是复用构造成本高的对象实例等。这一层的优化效果最为明显,通常也是比较简单的。
- 框架层。它指的是合理设置 Kafka 集群的各种参数。毕竟,直接修改 Kafka 源码进行调优并不容易,但根据实际场景恰当地配置关键参数的值,还是很容易实现的。
- JVM 层。Kafka Broker 进程是普通的 JVM 进程,各种对 JVM 的优化在这里也是适用的。优化这一层的效果虽然比不上前两层,但有时也能带来巨大的改善效果。
- 操作系统层。对操作系统层的优化很重要,但效果往往不如想象得那么好。与应用程序层的优化效果相比,它是有很大差距的。
操作系统调优
- 挂载(Mount)文件系统时禁掉 atime 更新。
- 至于文件系统,我建议你至少选择 ext4 或 XFS。尤其是 XFS 文件系统,它具有高性能、高伸缩性等特点,特别适用于生产服务器。
- 另外就是 swap 空间的设置。我个人建议将 swappiness 设置成一个很小的值,比如 1~10 之间,以防止 Linux 的 OOM Killer 开启随意杀掉进程。
- ulimit -n 和 vm.max_map_count。前者如果设置得太小,你会碰到 Too Many File Open 这类的错误,而后者的值如果太小,在一个主题数超多的 Broker 机器上,你会碰到OutOfMemoryError:Map failed的严重错误,因此,我建议在生产环境中适当调大此值,比如将其设置为 655360。
- 操作系统页缓存大小了,这对 Kafka 而言至关重要。在某种程度上,我们可以这样说:给 Kafka 预留的页缓存越大越好,最小值至少要容纳一个日志段的大小,也就是 Broker 端参数 log.segment.bytes 的值。该参数的默认值是 1GB。
JVM 层调优
-
设置堆大小。我来给出一个朴素的答案:将你的 JVM 堆大小设置成 6~8GB。 如果你想精确调整的话,我建议你可以查看 GC log,特别是关注 Full GC 之后堆上存活对象的总大小,然后把堆大小设置为该值的 1.5~2 倍。如果你发现 Full GC 没有被执行过,手动运行 jmap -histo:live < pid > 就能人为触发 Full GC。
-
GC 收集器的选择。 我强烈建议你使用 G1 收集器,主要原因是方便省事,至少比 CMS 收集器的优化难度小得多。 使用 G1 还很容易碰到的一个问题,就是大对象(Large Object),反映在 GC 上的错误,就是“too many humongous allocations”。 除了增加堆大小之外,你还可以适当地增加区域大小, 设置方法是增加 JVM 启动参数 -XX:+G1HeapRegionSize=N。默认情况下,如果一个对象超过了 N/2,就会被视为大对象,从而直接被分配在大对象区。
Broker 端调优
- 即尽力保持客户端版本和 Broker 端版本一致。不要小看版本间的不一致问题,它会令 Kafka 丧失很多性能收益,比如 Zero Copy。
应用层调优
- 不要频繁地创建 Producer 和 Consumer 对象实例。构造这些对象的开销很大,尽量复用它们。
- 用完及时关闭。这些对象底层会创建很多物理资源,如 Socket 连接、ByteBuffer 缓冲区等。不及时关闭的话,势必造成资源泄露。
- 合理利用多线程来改善性能。Kafka 的 Java Producer 是线程安全的,你可以放心地在多个线程中共享同一个实例;而 Java Consumer 虽不是线程安全的
性能指标调优
调优吞吐量
- Broker 端参数
num.replica.fetchers
表示的是 Follower 副本用多少个线程来拉取消息,默认使用 1 个线程。如果你的 Broker 端 CPU 资源很充足,不妨适当调大该参数值,加快 Follower 副本的同步速度。因为在实际生产环境中,配置了 acks=all 的 Producer 程序吞吐量被拖累的首要因素,就是副本同步性能。增加这个值后,你通常可以看到 Producer 端程序的吞吐量增加。 - 另外需要注意的,就是避免经常性的 Full GC。目前不论是 CMS 收集器还是 G1 收集器,其 Full GC 采用的是 Stop The World 的单线程收集策略,非常慢,因此一定要避免
- 在 Producer 端,如果要改善吞吐量,通常的标配是增加消息批次的大小以及批次缓存时间,即 batch.size 和 linger.ms。 目前它们的默认值都偏小,特别是默认的 16KB 的消息批次大小一般都不适用于生产环境。假设你的消息体大小是 1KB,默认一个消息批次也就大约 16 条消息,显然太小了。我们还是希望 Producer 能一次性发送更多的消息。
- 除了这两个,你最好把压缩算法也配置上,以减少网络 I/O 传输量,从而间接提升吞吐量。当前,和 Kafka 适配最好的两个压缩算法是LZ4 和 zstd,不妨一试。
- Consumer 端提升吞吐量的手段是有限的,你可以利用多线程方案增加整体吞吐量,也可以增加
fetch.min.bytes
参数值。默认是 1 字节,表示只要 Kafka Broker 端积攒了 1 字节的数据,就可以返回给 Consumer 端,这实在是太小了。我们还是让 Broker 端一次性多返回点数据吧。
调优延时
- 在 Broker 端,我们依然要增加 num.replica.fetchers 值以加快 Follower 副本的拉取速度,减少整个消息处理的延时。
- 在 Producer 端,我们希望消息尽快地被发送出去,因此不要有过多停留,所以必须设置 linger.ms=0,同时不要启用压缩。因为压缩操作本身要消耗 CPU 时间,会增加消息发送的延时。另外,最好不要设置 acks=all。我们刚刚在前面说过,Follower 副本同步往往是降低 Producer 端吞吐量和增加延时的首要原因。
- 在 Consumer 端,我们保持 fetch.min.bytes=1 即可,也就是说,只要 Broker 端有能返回的数据,立即令其返回给 Consumer,缩短 Consumer 消费延时。