Kafka导致OOM的排查经历

本文探讨了线上Kafka系统因生产者缓存机制引发的OOM问题,通过排查堆外内存泄露,发现64M内存块和kafka-producer-network-thread的影响。作者分享了如何调整Kafka配置、解决堆外内存问题,并优化KafkaProducer实例池以提高吞吐量。

关键词:

kafka生产者的缓存机制

Kafka导致OOM

Kafka数据压缩

大量64M

堆外内存泄露

Docker使用内存不断升高直到重启即堆外内存泄露64M

kafka-producer-network-thread

1.背景

线上kafka异步系统一段时间会OOM,但是我们的测试环境部署在某平台容器虚拟机上,

2.排查过程

1.在测试环境能重现服务被打挂,以为重现线上问题,为了拿测试环境的dump文件,增加jvm启动参数:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/data/app/oom

以及deployment.yaml配置了卷挂载;

结果服务好不容易配置好起来了,能在平台检测到内存一直在飙升,把服务打挂,结果没拿到dump文件,以为配置问题,检查几遍没问题,怀疑不是OOM打挂服务,查询日志,证实服务是内存到达容器设置limit被系统killed了,并非OOM;

只能换思路先解决容器为什么内存飙升,就开始排查内存使用情况,先排查JVM内存情况,相关命令:top,jmap -heap pid ;jstat等等,看到JVM内存正常,怀疑堆外内存,开始排查堆外内存;刚开始以为是kafka开启了压缩导致的,检查参数并未开启压缩;

(kafka压缩相关知识:Kafka数据压缩_kafka中文社区的博客-优快云博客_kafka 压缩

转而排查堆外内存;

(堆外内存相关知识:Spring Boot引起的“堆外内存泄漏”排查及经验总结记一次java native memory增长问题的排查 - Axb的自我修养

pmap pid;查看堆外内存,发现大量64M的内存块,找了好久排查到是经典的64M问题;跟docker有关以及Java原生内存管理有关;解决:dockerfile增加

ENV MALLOC_ARENA_MAX=2

(相关知识:记录处理Docker使用内存不断升高直到重启即堆外内存泄露64M(已解决)_好好学习/天天向上的博客-优快云博客_docker容器java内存不断上升JAVA问题定位大杂汇之java 虚拟机内存占用超出 -Xmx设置_nature502的博客-优快云博客

Java原生内存管理(native memory management)(5)-MALLOC_ARENA_MAX - 知乎

一顿操作下来发现64M问题解决了,但还是有大量的堆外内存,pmap命令查看发现还有大量148k跟12k的内存块,开始怀疑是kafka;手动导出dump文件,用visualVM分析看到大量的kafka相关线程kafka-producer-network-thread

 怀疑跟kafka生产者有关,怀疑是kafka客户端缓存问题;

(kafka生产者客户端相关知识:kafka生产者的缓存机制_知食份子.的博客-优快云博客_kafka 缓存

开始检查代码;发现问题所在:

修改前代码

@Configuration
public class KafkaProducerConfig {
    @Value("${spring.kafka.bootstrap-servers}")
    private String servers;
    @Value("${spring.kafka.producer.retries}")
    private int retries;
    @Value("${spring.kafka.producer.batch-size}")
    private int batchSize;
    @Value("${spring.kafka.producer.properties.linger.ms}")
    private int linger;
    @Value("${spring.kafka.producer.buffer-memory}")
    private int bufferMemory;
    @Value("${spring.kafka.producer.acks}")
    private String acks;
    @Value("${spring.kafka.producer.key-serializer}")
    private String keySerializer;
    @Value("${spring.kafka.producer.value-serializer}")
    private String valueSerializer;
//    @Value("${spring.kafka.producer.max.block.ms}")
//    private int maxBlockMs;

    public Map<String, Object> producerConfigs() {

        Map<String, Object> props = new HashMap<>();

        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
        props.put(ProducerConfig.ACKS_CONFIG,acks);
        props.put(ProducerConfig.RETRIES_CONFIG, retries);

        props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);

        props.put(ProducerConfig.LINGER_MS_CONFIG, linger);

        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);

        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);

//        props.put(ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG,4);
//        props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG,maxBlockMs);
        props.put(ProducerConfig.MAX_BLOCK_MS_CONFIG,2500);


        return props;

    }

    public KafkaFuture<Set<String>> topicList() {
        Map<String, Object> stringObjectMap = this.producerConfigs();
        ListTopicsResult result = KafkaAdminClient.create(stringObjectMap).listTopics();
        KafkaFuture<Set<String>> names = result.names();
        KafkaFuture<Set<String>> set = result.names();
        return set;
    }
    public ProducerFactory<String, Object> producerFactory() {

        return new DefaultKafkaProducerFactory<>(producerConfigs());

    }

    public KafkaTemplate<String, Object> kafkaTemplate() {

        return new KafkaTemplate<String, Object>(producerFactory());
    }
}

使用:

 KafkaTemplate<String, Object> stringObjectKafkaTemplate = kafkaProducerConfig.kafkaTemplate();
            ListenableFuture<SendResult<String, Object>> future = stringObjectKafkaTemplate.send(, );

问题出在KafkaProducerConfig类;使用的时候循环数据发送kafka每次都会生成新的KafkaTemplate,生成新的ProducerRecord,KafkaProducer有消息累加器(RecordAccumulator);这里提贴上doSend源码

protected ListenableFuture<SendResult<K, V>> doSend(ProducerRecord<K, V> producerRecord) {
        if (this.transactional) {
            Assert.state(this.inTransaction(), "No transaction is in process; possible solutions: run the template operation within the scope of a template.executeInTransaction() operation, start a transaction with @Transactional before invoking the template method, run in a transaction started by a listener container when consuming a record");
        }

        Producer<K, V> producer = this.getTheProducer();
        this.logger.trace(() -> {
            return "Sending: " + producerRecord;
        });
        SettableListenableFuture<SendResult<K, V>> future = new SettableListenableFuture();
        producer.send(producerRecord, this.buildCallback(producerRecord, producer, future));
        if (this.autoFlush) {
            this.flush();
        }

        this.logger.trace(() -> {
            return "Sent: " + producerRecord;
        });
        return future;
    }

getTheProducer方法会生成新的Producer;

 修改代码,用单例模式生成KafkaTemplate;

思考优化增加吞吐量如下:

kafka producer实例池实现生产者多线程写入_张老七没脾气的博客-优快云博客_kafka producer 多线程

<think>好的,用户现在问的是Kafka服务宕机了怎么排查。之前用户问过类似的问题,是关于日志目录不可用导致Broker关闭的错误,所以用户可能是在实际运维中遇到了Kafka宕机的问题,现在需要更全面的排查步骤。 首先,用户可能对Kafka的基本架构有一定了解,但可能不太清楚具体的排查流程。需要分步骤引导,从基础检查开始,逐步深入,确保用户能一步步排查问题,而不是直接跳到复杂的步骤。 然后,要考虑用户可能的场景,比如生产环境中的Kafka宕机,这时候用户需要快速恢复服务,但同时也需要找到根本原因,避免再次发生。所以回答中需要包含快速恢复的建议,比如重启Broker,但也要提醒用户注意风险。 接下来,常见的宕机原因有哪些呢?可能包括资源不足(CPU、内存、磁盘)、配置错误、依赖服务问题(比如ZooKeeper)、网络问题、日志损坏或磁盘故障,还有可能的代码缺陷或版本问题。需要把这些可能的原因都覆盖到,并提供对应的检查命令和解决方法。 另外,用户可能对某些命令不太熟悉,比如如何检查JVM内存,如何查看日志,如何测试端口连通性等,所以需要给出具体的命令示例,让用户可以直接复制使用。 还需要注意权限问题,之前用户遇到过目录权限导致的错误,所以在这次排查步骤中要再次强调检查目录权限和文件系统状态。 同时,用户可能需要监控和预防措施,避免未来再次发生类似问题,所以最后要给出预防建议,比如资源监控、配置优化、定期维护等。 最后,要提醒用户如果自行排查困难,及时联系运维团队或寻求社区支持,这样用户不会卡在某个步骤无法继续。</think>当 Kafka 服务宕机时,可按以下步骤系统化排查问题: --- ### **1. 快速检查服务状态** ```bash # 确认 Kafka 进程是否存活 ps -ef | grep kafka.Kafka # 检查端口监听(默认 9092) netstat -tulnp | grep 9092 # 查看服务启动日志(默认在 logs/server.log) tail -n 100 logs/server.log ``` --- ### **2. 常见原因及排查方向** #### **场景1:资源不足** - **检查项** ```bash # CPU/Memory top -p $(pgrep -f kafka.Kafka) # 磁盘空间 df -h /path/to/log.dirs # 文件句柄数 cat /proc/$(pgrep -f kafka.Kafka)/limits | grep "open files" ``` - **解决方案** - 清理磁盘(删除旧日志或扩容) - 调整 JVM 内存参数(`KAFKA_HEAP_OPTS="-Xmx8G -Xms8G"`) - 增大系统文件句柄限制 --- #### **场景2:ZooKeeper 连接问题** - **检查项** ```bash # 确认 ZooKeeper 集群状态 echo stat | nc zk-host 2181 # 检查 Kafka 日志中的 ZK 连接错误 grep -i "exception.*zookeeper" logs/server.log ``` - **解决方案** - 重启 ZooKeeper 服务 - 检查网络防火墙规则 - 确认 `zookeeper.connect` 配置正确 --- #### **场景3:日志文件损坏** - **检查项** ```bash # 检查日志段文件状态 ls -l /path/to/log.dirs/*/__consumer_offsets-* # 查找日志中的 Corruption 错误 grep -i "corrupt\|invalid" logs/server.log ``` - **解决方案** - 删除损坏的日志段文件(需提前备份) - 使用 `kafka-check` 工具修复 - 启用日志恢复机制:`log.flush.offset.checkpoint.interval.ms=10000` --- #### **场景4:配置错误** - **检查项** ```bash # 验证关键配置 grep -E "broker.id|listeners|log.dirs" config/server.properties # 检查端口冲突 netstat -tulnp | grep :9092 ``` - **解决方案** - 修复错误配置(如重复的 broker.id) - 检查 listeners 的 IP 绑定是否正确 - 确保 `advertised.listeners` 配置可访问 --- #### **场景5:硬件/网络故障** - **检查项** ```bash # 磁盘健康检查 smartctl -a /dev/sdX # 网络连通性测试 tcping kafka-host 9092 mtr -r zk-host ``` - **解决方案** - 更换故障磁盘 - 修复网络路由问题 - 检查交换机/网卡状态 --- ### **3. 高级诊断工具** ```bash # 检查 Controller 状态 bin/kafka-metadata-quorum.sh --describe --status # 验证副本同步 bin/kafka-topics.sh --describe --bootstrap-server localhost:9092 # 线程堆栈分析 jstack $(pgrep -f kafka.Kafka) > kafka_thread_dump.txt ``` --- ### **4. 快速恢复建议** 1. **临时重启**(仅限紧急情况): ```bash bin/kafka-server-stop.sh sleep 10 bin/kafka-server-start.sh -daemon config/server.properties ``` 2. **优先恢复 Controller Broker** 确保 Controller 所在 Broker 先启动 --- ### **5. 预防措施** - **监控体系** 部署 Prometheus + Grafana 监控: - Broker 存活状态 - 未同步副本数(Under Replicated Partitions) - 请求队列大小(RequestQueueSize) - **配置优化** ```properties # 防止 OOM num.io.threads=16 num.network.threads=8 # 快速故障检测 zookeeper.session.timeout.ms=18000 ``` - **定期维护** 每月执行: ```bash bin/kafka-log-dirs.sh --bootstrap-server localhost:9092 --describe ``` --- ### **6. 典型错误日志解读** | 日志关键字 | 含义 | 处理建议 | |-------------------------------|-----------------------------|----------------------------| | `NotControllerException` | Broker 丢失 Controller 身份 | 等待 Controller 选举完成 | | `LeaderElectionNotAvailable` | 副本不足无法选举 Leader | 检查 ISR 副本状态 | | `SocketTimeoutException` | 网络延迟或负载过高 | 优化网络或调整 `request.timeout.ms` | 如果问题仍无法定位,建议通过 `jmap` 生成堆转储文件,使用 Eclipse MAT 分析内存泄漏。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值