DataKit 服务CPU使用率报高问题分析

问题描述

        启动DataKit 服务后,在服务空载情况下,出现CPU使用率报高,维持在 80+% 左右。

问题分析

        通过问题分析脚本对当前DataKit进行异常分析:

#!/bin/bash

# ####################################
# Java进程CPU高占用紧急诊断脚本
# 针对PID 3434的高CPU使用率问题
# ####################################

set -euo pipefail

# 配置参数
PID="3434"
USER="wang"
PROCESS_NAME="java"
EMERGENCY_THRESHOLD=80
LOG_DIR="./java_diagnosis_logs"
REPORT_DIR="./emergency_reports"

# 创建日志目录
mkdir -p "${LOG_DIR}"
mkdir -p "${REPORT_DIR}"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

# 日志函数
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
log_emergency() { echo -e "${RED}🚨[EMERGENCY]${NC} $1"; }

# 检查进程状态
check_process_status() {
    log_info "检查进程 ${PID} 状态..."

    if ! ps -p "$PID" > /dev/null 2>&1; then
        log_error "进程 ${PID} 不存在或已退出"
        return 1
    fi

    local process_info
    process_info=$(ps -p "$PID" -o pid,user,pcpu,pmem,vsz,rss,etime,command --no-headers)

    if [ -n "$process_info" ]; then
        echo "当前进程状态:"
        echo "PID    USER    CPU%   MEM%   VSZ      RSS      ELAPSED    COMMAND"
        echo "$process_info"
        echo ""

        local current_cpu
        current_cpu=$(echo "$process_info" | awk '{print $3}')

        if (( $(echo "$current_cpu > $EMERGENCY_THRESHOLD" | bc -l 2>/dev/null) )); then
            log_emergency "检测到CPU使用率异常: ${current_cpu}%"
            return 2
        else
            log_info "当前CPU使用率: ${current_cpu}%"
            return 0
        fi
    fi
}

# 紧急诊断 - 分析高CPU原因
emergency_diagnosis() {
    local timestamp
    timestamp=$(date '+%Y%m%d_%H%M%S')
    local report_file="${REPORT_DIR}/emergency_report_${PID}_${timestamp}.txt"

    log_emergency "开始紧急诊断进程 ${PID}..."

    cat > "$report_file" <<EOF
🚨 Java进程紧急诊断报告
==================================================
诊断时间: $(date '+%Y-%m-%d %H:%M:%S')
进程PID: ${PID}
用户: ${USER}
问题: CPU使用率87.7% (严重过高)
==================================================

EOF

    # 1. 检查系统负载
    echo "1. 系统整体负载检查..." >> "$report_file"
    echo "--------------------------------------------------" >> "$report_file"
    uptime >> "$report_file"
    echo "" >> "$report_file"

    # 2. 检查进程详细信息
    echo "2. 进程详细信息..." >> "$report_file"
    echo "--------------------------------------------------" >> "$report_file"
    ps -p "$PID" -o pid,user,pcpu,pmem,vsz,rss,etime,state,pri,ni --no-headers >> "$report_file"
    echo "" >> "$report_file"

    # 3. 检查Java线程CPU使用
    analyze_java_threads >> "$report_file"

    # 4. 检查GC情况
    analyze_gc_status >> "$report_file"

    # 5. 生成紧急处理建议
    generate_emergency_actions >> "$report_file"

    log_info "紧急诊断报告已生成: $report_file"
    echo ""
    cat "$report_file"
}

# 分析Java线程CPU使用
analyze_java_threads() {
    log_info "分析Java线程CPU使用情况..."

    local thread_log="${LOG_DIR}/threads_${PID}_$(date '+%Y%m%d_%H%M%S').log"

    echo "3. Java线程CPU使用分析" >> "$report_file"
    echo "--------------------------------------------------" >> "$report_file"

    # 方法1: 使用top查看线程
    if top -H -b -n 1 -p "$PID" > "$thread_log" 2>/dev/null; then
        echo "线程CPU使用排行 (top -H -p $PID):" >> "$report_file"
        grep -E "^[[:space:]]*[0-9]" "$thread_log" | head -10 >> "$report_file"
        echo "" >> "$report_file"
    fi

    # 方法2: 使用ps查看线程
    echo "线程详细信息 (ps -L):" >> "$report_file"
    ps -L -p "$PID" -o tid,pcpu,pmem,state,time,comm --no-headers | sort -k2 -nr | head -10 >> "$report_file"
    echo "" >> "$report_file"

    # 方法3: 尝试使用jstack(如果可用)
    if command -v jstack >/dev/null 2>&1; then
        local jstack_file="${LOG_DIR}/jstack_${PID}_$(date '+%Y%m%d_%H%M%S').log"
        log_info "执行jstack分析..."
        jstack "$PID" > "$jstack_file" 2>/dev/null && echo "jstack输出已保存: $jstack_file" >> "$report_file"
    fi
}

# 分析GC状态
analyze_gc_status() {
    log_info "分析GC状态..."

    echo "4. GC状态分析" >> "$report_file"
    echo "--------------------------------------------------" >> "$report_file"

    # 检查jstat是否可用
    if command -v jstat >/dev/null 2>&1; then
        local gc_log="${LOG_DIR}/gc_${PID}_$(date '+%Y%m%d_%H%M%S').log"

        echo "当前GC统计:" >> "$report_file"
        jstat -gcutil "$PID" 1000 1 >> "$report_file" 2>/dev/null || echo "无法获取GC信息" >> "$report_file"
        echo "" >> "$report_file"

        # 监控GC变化
        echo "GC变化监控 (5次采样):" >> "$report_file"
        jstat -gcutil "$PID" 1000 5 >> "$report_file" 2>/dev/null || echo "无法监控GC变化" >> "$report_file"
        echo "" >> "$report_file"
    else
        echo "jstat不可用,跳过GC分析" >> "$report_file"
    fi
}

# 生成紧急处理建议
generate_emergency_actions() {
    echo "5. 🚨 紧急处理建议" >> "$report_file"
    echo "==================================================" >> "$report_file"

    cat >> "$report_file" <<EOF
🚨 立即行动建议 (按优先级排序):

1. 🎯 最高优先级 - 立即诊断
   ----------------------------------
   ✅ 立即执行线程转储分析:
      jstack -l ${PID} > jstack_emergency.log

   ✅ 检查热点线程:
      top -H -p ${PID}
      ps -L -p ${PID} -o tid,pcpu,pmem,state,time,comm | sort -k2 -nr | head -5

   ✅ 实时监控GC:
      jstat -gcutil ${PID} 1000 10

2. 🔧 短期缓解措施
   ----------------------------------
   ⚠️ 考虑重启应用 (如果业务允许)
   ⚠️ 调整JVM参数 (增加堆内存或调整GC策略)
   ⚠️ 检查应用日志中的错误或异常模式
   ⚠️ 检查数据库连接池状态

3. 📊 根本原因分析
   ----------------------------------
   🔍 分析线程转储,查找:
      - 死循环线程
      - 锁竞争问题
      - 大量GC线程
      - 阻塞的I/O操作

   🔍 检查应用配置:
      - JVM参数是否合理
      - 数据库连接池配置
      - 缓存配置
      - 外部服务调用超时设置

4. 🛡️ 预防措施
   ----------------------------------
   📈 建立监控告警 (CPU > 80% 时告警)
   📈 定期健康检查
   📈 性能压力测试
   📈 代码审查 (避免死循环、资源泄漏)

EOF
}

# 实时监控高CPU线程
monitor_hot_threads() {
    log_info "开始实时监控高CPU线程..."

    local monitor_log="${LOG_DIR}/hot_threads_monitor_${PID}.log"

    echo "时间,线程TID,CPU%,内存%,状态,命令" > "$monitor_log"

    for i in {1..10}; do
        local timestamp
        timestamp=$(date '+%Y-%m-%d %H:%M:%S')

        echo "=== 监控周期 $i ===" >> "$monitor_log"

        # 获取高CPU线程
        ps -L -p "$PID" -o tid,pcpu,pmem,state,time,comm --no-headers | sort -k2 -nr | head -5 | while read -r line; do
            echo "${timestamp},${line}" >> "$monitor_log"
        done

        echo "监控周期 $i - 高CPU线程:"
        ps -L -p "$PID" -o tid,pcpu,pmem,state,time,comm --no-headers | sort -k2 -nr | head -3

        sleep 2
    done

    log_info "线程监控完成: $monitor_log"
}

# 生成修复建议脚本
generate_fix_scripts() {
    log_info "生成修复建议脚本..."

    local fix_script="${REPORT_DIR}/suggested_fixes_${PID}.sh"

    cat > "$fix_script" <<'EOF'
#!/bin/bash

# Java进程CPU高占用修复建议脚本
# 生成的修复措施

set -euo pipefail

PID="$1"

echo "🔧 Java进程CPU高占用修复建议"

# 1. 立即诊断命令
echo ""
echo "1. 立即诊断命令:"
echo "   # 线程转储分析"
echo "   jstack -l $PID > jstack_analysis.log"
echo ""
echo "   # 实时监控高CPU线程"
echo "   top -H -p $PID"
echo "   ps -L -p $PID -o tid,pcpu,pmem,state,time,comm | sort -k2 -nr | head -5"
echo ""
echo "   # GC状态监控"
echo "   jstat -gcutil $PID 1000 5"

# 2. JVM参数优化建议
echo ""
echo "2. JVM参数优化建议:"
echo "   # 当前JVM参数检查"
echo "   jcmd $PID VM.flags"
echo ""
echo "   # 建议添加的JVM参数:"
echo "   -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
echo "   -XX:InitiatingHeapOccupancyPercent=35"
echo "   -Xlog:gc*=info:file=gc.log:time,uptime,level,tags"
echo ""

# 3. 应用层面检查
echo ""
echo "3. 应用层面检查:"
echo "   # 检查数据库连接池"
echo "   # 查看应用日志中的异常"
echo "   # 检查外部服务调用超时"
echo "   # 验证缓存配置"

# 4. 系统层面检查
echo ""
echo "4. 系统层面检查:"
echo "   # 检查系统负载"
echo "   uptime"
echo "   # 检查内存使用"
echo "   free -h"
echo "   # 检查IO状态"
echo "   iostat -x 1 5"

EOF

    chmod +x "$fix_script"
    log_info "修复建议脚本已生成: $fix_script"
}

# 主菜单
show_menu() {
    echo ""
    echo "======================================="
    echo "    🚨 Java进程CPU紧急诊断工具"
    echo "======================================="
    echo "当前进程: PID=$PID, USER=$USER, CPU=87.7%"
    echo "======================================="
    echo "1. 🔍 立即诊断高CPU原因"
    echo "2. 📊 实时监控高CPU线程"
    echo "3. 📈 检查GC状态"
    echo "4. 🛠️ 生成修复建议脚本"
    echo "5. 📋 查看系统整体状态"
    echo "6. 🚨 紧急线程转储分析"
    echo "0. 🔚 退出"
    echo "======================================="
    echo -n "请选择操作 [0-6]: "
}

# 查看系统整体状态
check_system_status() {
    log_info "检查系统整体状态..."

    echo "=== 系统整体状态 ==="
    echo "1. 系统负载:"
    uptime
    echo ""

    echo "2. 内存使用:"
    free -h
    echo ""

    echo "3. 最耗CPU的进程:"
    ps aux --sort=-%cpu | head -10
    echo ""

    echo "4. 最耗内存的进程:"
    ps aux --sort=-%mem | head -10
}

# 紧急线程转储分析
emergency_thread_dump() {
    log_emergency "执行紧急线程转储分析..."

    local dump_file="${LOG_DIR}/emergency_thread_dump_${PID}_$(date '+%Y%m%d_%H%M%S').log"

    if command -v jstack >/dev/null 2>&1; then
        echo "执行jstack线程转储..."
        jstack -l "$PID" > "$dump_file"

        if [ $? -eq 0 ]; then
            log_info "线程转储完成: $dump_file"

            # 简单分析线程状态
            echo "线程状态统计:"
            grep -o 'java.lang.Thread.State: [A-Z_]*' "$dump_file" | sort | uniq -c | sort -nr

            echo ""
            echo "建议使用专业工具分析: $dump_file"
        else
            log_error "线程转储失败"
        fi
    else
        log_error "jstack命令不可用"
    fi
}

# 主函数
main() {
    log_emergency "检测到Java进程CPU使用率过高: 87.7%"

    # 检查进程状态
    if ! check_process_status; then
        if [ $? -eq 2 ]; then
            log_emergency "确认CPU使用率超过阈值,需要紧急处理!"
        else
            exit 1
        fi
    fi

    while true; do
        show_menu
        read -r choice

        case $choice in
            1)
                emergency_diagnosis
                ;;
            2)
                monitor_hot_threads
                ;;
            3)
                analyze_gc_status
                ;;
            4)
                generate_fix_scripts
                ;;
            5)
                check_system_status
                ;;
            6)
                emergency_thread_dump
                ;;
            0)
                log_info "退出诊断工具"
                exit 0
                ;;
            *)
                log_error "无效选择"
                ;;
        esac
    done
}

# 运行主函数
main

通过堆栈分析,发现当前引入的disruptor 相关业务引起的CPU使用率过高。

分析disruptor配置

当前disruptor配置代码

Disruptor<MetricEvent> disruptor = new Disruptor<>(
    MetricEvent::new,              // 1. 事件工厂
    bufferSize * 1024,             // 2. 环形缓冲区大小
    Executors.defaultThreadFactory(), // 3. 线程工厂
    ProducerType.SINGLE,           // 4. 生产者类型
    new SleepingWaitStrategy(100, 1000) // 5. 等待策略
);

各参数对CPU空转时占用率的影响分析

1. 缓冲区大小bufferSize * 1024

  • 影响:缓冲区大小直接影响内存占用,但对空转时CPU占用基本无影响

  • 建议

    • 过大:浪费内存

    • 过小:容易触发背压

    • 合理值:根据业务吞吐量估算,通常设置为2的幂次方

2. 生产者类型ProducerType.SINGLE

  • 单生产者 vs 多生产者对CPU的影响

    • 单生产者:空转时CPU占用较低,因为不需要CAS操作竞争序列号

    • 多生产者:需要CAS操作,空转时仍有一定CPU开销

  • 建议

    // 根据实际情况选择
    ProducerType.SINGLE    // 只有一个生产者线程时使用(CPU占用低)
    ProducerType.MULTI     // 多个生产者线程时使用(CPU占用较高)

3. 等待策略SleepingWaitStrategy(100, 1000)

这是影响空转CPU占用率的关键参数!

SleepingWaitStrategy 的工作原理:
new SleepingWaitStrategy(
    100,     // retries: 在休眠前尝试获取数据的次数
    1000     // sleepTimeNs: 休眠的纳秒时间(这里是1000ns = 1微秒)
)

空转时CPU占用高的原因:

  1. 重试次数过高retries = 100

    • 每次空转循环会尝试100次后才休眠

    • 100次无意义的循环会消耗CPU时间

  2. 休眠时间过短sleepTimeNs = 1000ns(1微秒)

    • 1微秒的休眠时间极短

    • 线程会频繁唤醒和休眠,上下文切换开销大

    • 实际可能因为休眠时间太短,线程调度器还来不及让线程真正休眠

4. 线程工厂Executors.defaultThreadFactory()

  • 对CPU占用影响不大,但线程优先级会影响调度

  • 可以考虑设置线程名以便调试

比较不同等待策略的空转CPU占用:

// 1. SleepingWaitStrategy - 平衡型(默认推荐)
//    空转CPU占用:中
//    延迟:中等
new SleepingWaitStrategy(10, 1000000)  // 1ms休眠

// 2. BlockingWaitStrategy - 最低CPU占用
//    空转CPU占用:极低(使用锁和条件变量阻塞)
//    延迟:较高(唤醒需要上下文切换)
new BlockingWaitStrategy()

// 3. YieldingWaitStrategy - 低延迟但高CPU
//    空转CPU占用:高(忙等待)
//    延迟:极低
new YieldingWaitStrategy()

// 4. BusySpinWaitStrategy - 最高性能和最高CPU
//    空转CPU占用:100%(完全忙等待)
//    延迟:最低
new BusySpinWaitStrategy()

// 5. TimeoutBlockingWaitStrategy - 带超时的阻塞
//    空转CPU占用:低
//    延迟:中等
new TimeoutBlockingWaitStrategy(1000, TimeUnit.MILLISECONDS)

// 6. LiteBlockingWaitStrategy - 轻量级阻塞
//    空转CPU占用:低
//    延迟:中等
new LiteBlockingWaitStrategy()

最终修改方案

修改disruptor等待策略

 new TimeoutBlockingWaitStrategy(1000, TimeUnit.MILLISECONDS)

方案验证

通过 pidstat -p 5461 2 20  命令查看当前datakit 进程cpu使用率

前期cpu出现6次 200~400 之间峰值,为进程启动阶段。启动完成后cpu使用率回落到2.5-5之间。

经确认当前修改方案通过

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仰望星空@脚踏实地

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值