未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用范围与待维护事项

本文适用于具备Java MapReduce开发、Linux运维及YARN管理基础的人员,案例仅作教学排障示例,未提供实际事故证据或版本验证记录。日志路径、yarn logs参数、REST响应、服务名和端口均须按部署核对;Java片段还需补齐依赖、导入和作业配置。节点重启与文件清理须经授权并先评估影响,禁止直接在生产执行。监控脚本尚缺HTTP失败、鉴权、空响应及时间窗口处理;坏数据备份未接入输出提交机制,不能保证重试或推测执行下的去重与一致性。

🩺 MapReduce故障处理 - 分布式计算诊断指南

abstract 📋 学习目标

  • 🔍 掌握故障诊断思路:系统化地定位和分析MapReduce任务问题
  • 🛠️ 学会调试技巧:使用日志、界面、工具进行问题排查
  • 🔧 理解容错机制:了解Hadoop如何自动处理各种故障
  • 📊 建立监控体系:预防性监控和告警机制

tip 💡 前置知识

建议先学习 MapReduce开发 和 MapReduce性能优化,理解MapReduce的执行流程


🩺 故障诊断思维

MapReduce故障排查就像医生看病

想象你是一位"集群医生",需要诊断分布式系统的"病情":

%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
    classDef default fill:#FDFBF5,stroke:#6E6A60,color:#2D2A26,stroke-width:1.5px
    classDef startend fill:#2D2A26,stroke:#2D2A26,color:#FDFBF5,font-weight:bold
    classDef decision fill:#F6EAD8,stroke:#C08A1A,color:#2D2A26,font-weight:bold
    classDef hub fill:#3B5A80,stroke:#3B5A80,color:#FDFBF5,font-weight:bold
    classDef io fill:#E5EAF0,stroke:#3B5A80,color:#2D2A26
    classDef accent fill:#A6432C,stroke:#A6432C,color:#FDFBF5,font-weight:bold
    classDef storage fill:#F3EEDF,stroke:#9A9488,color:#2D2A26
    subgraph "医生诊断流程"
        A[患者症状]
        B[初步检查]
        C[详细诊断]
        D[病因分析]
        E[制定治疗方案]
        F[治疗和康复]
        
        A --> B
        B --> C
        C --> D
        D --> E
        E --> F
    end
    
    subgraph "故障诊断流程"
        B1[任务失败]
        B2[查看监控]
        C1[分析日志]
        D1[定位根因]
        E1[修复问题]
        F1[验证恢复]
        
        B1 --> B2
        B2 --> C1
        C1 --> D1
        D1 --> E1
        E1 --> F1
    end
    class A,B,C,D,E,F,B1,B2,C1,D1,E1,F1 default

故障诊断四步法:

  1. 🚨 症状观察:任务失败、性能下降、资源异常等
  2. 🔍 信息收集:日志、监控数据、系统状态等
  3. 💡 原因分析:代码问题、配置错误、资源不足等
  4. 🔧 问题解决:修复代码、调整配置、扩容资源等

tip 💡 诊断原则

  • 先急后缓:优先解决影响生产的问题
  • 由表及里:从表面现象深入到根本原因
  • 系统思维:考虑问题的关联性和影响范围
  • 记录经验:建立故障知识库

🔍 常见故障类型

任务执行失败

note 🚨 常见失败症状

  • 任务反复重启:Map/Reduce任务多次失败重试
  • 任务hang住不动:长时间没有进度更新
  • 内存溢出:OutOfMemoryError异常
  • 权限错误:Permission denied错误
  • 连接超时:网络相关的timeout错误

应用程序错误分类

public class MapReduceErrorHandler {
    
    /**
     * 📝 常见异常类型和处理策略
     */
    public enum ErrorType {
        
        // 🧠 内存相关错误
        OUT_OF_MEMORY("java.lang.OutOfMemoryError", 
            "增加JVM堆内存或优化代码内存使用"),
            
        // 🗂️ 文件系统错误  
        FILE_NOT_FOUND("java.io.FileNotFoundException",
            "检查输入路径是否存在,权限是否正确"),
            
        // 🌐 网络连接错误
        CONNECT_TIMEOUT("java.net.ConnectException",
            "检查网络连接,增加超时时间配置"),
            
        // 🔐 权限错误
        PERMISSION_DENIED("org.apache.hadoop.security.AccessControlException",
            "检查HDFS权限设置,确认用户权限"),
            
        // 📊 数据格式错误
        NUMBER_FORMAT("java.lang.NumberFormatException",
            "检查输入数据格式,增加数据验证"),
            
        // 🔄 资源不足
        RESOURCE_SHORTAGE("Container killed by ResourceManager",
            "增加内存/CPU配置或优化资源使用");
        
        private final String pattern;
        private final String solution;
        
        ErrorType(String pattern, String solution) {
            this.pattern = pattern;
            this.solution = solution;
        }
        
        public static String getSolution(String errorMessage) {
            for (ErrorType type : values()) {
                if (errorMessage.contains(type.pattern)) {
                    return type.solution;
                }
            }
            return "未知错误,请检查完整日志进行分析";
        }
    }
}

📊 日志分析和调试

日志文件位置

# 🗂️ YARN应用日志
yarn logs -applicationId application_xxx

# 📍 具体容器日志
yarn logs -applicationId application_xxx -containerId container_xxx

# 🖥️ JobHistory日志
ls $HADOOP_HOME/logs/history/

# 📁 本地任务日志(开发调试)
ls $HADOOP_HOME/logs/userlogs/

# 🔍 实时查看运行中的任务日志
yarn logs -applicationId application_xxx -log_files stdout -f

日志分析工具

#!/bin/bash
# mapreduce_log_analyzer.sh - MapReduce日志分析工具

APPLICATION_ID=$1
ERROR_KEYWORDS="ERROR|Exception|OutOfMemory|killed|failed"

if [ -z "$APPLICATION_ID" ]; then
    echo "🩺 MapReduce日志分析工具"
    echo "用法: $0 <application_id>"
    echo "示例: $0 application_1234567890123_0001"
    exit 1
fi

echo "🔍 分析应用日志: $APPLICATION_ID"
echo "开始时间: $(date)"
echo "─" | tr '─' '─' | head -c 50; echo

# 1️⃣ 获取应用基本信息
echo "📋 应用基本信息:"
yarn application -status $APPLICATION_ID | grep -E "(Application-Id|Application-Name|State|Final-State)"

# 2️⃣ 分析错误日志
echo -e "\n🚨 错误信息统计:"
yarn logs -applicationId $APPLICATION_ID 2>/dev/null | \
    grep -iE "$ERROR_KEYWORDS" | \
    cut -d' ' -f4- | \
    sort | uniq -c | sort -rn | head -10

# 3️⃣ 任务执行时间分析
echo -e "\n⏱️ 任务执行时间分析:"
yarn logs -applicationId $APPLICATION_ID 2>/dev/null | \
    grep -E "Task.*finished|Task.*failed" | \
    tail -20

# 4️⃣ 资源使用分析
echo -e "\n💾 资源使用情况:"
yarn logs -applicationId $APPLICATION_ID 2>/dev/null | \
    grep -E "Memory|CPU|Container" | \
    head -10

# 5️⃣ 输出详细错误日志
echo -e "\n📄 详细错误信息:"
yarn logs -applicationId $APPLICATION_ID 2>/dev/null | \
    grep -iE -A 5 -B 5 "$ERROR_KEYWORDS" | head -50

echo -e "\n✅ 日志分析完成"

调试技巧

public class MapReduceDebugger {
    
    /**
     * 🐛 在Mapper中添加调试信息
     */
    public static class DebugMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
        
        private long lineNumber = 0;
        private static final int DEBUG_INTERVAL = 1000;
        
        @Override
        public void map(LongWritable key, Text value, Context context) 
                throws IOException, InterruptedException {
            
            lineNumber++;
            
            try {
                // 📊 定期输出调试信息
                if (lineNumber % DEBUG_INTERVAL == 0) {
                    System.out.println(String.format(
                        "🐛 DEBUG: 已处理 %d 行,当前行: %s", 
                        lineNumber, value.toString().substring(0, Math.min(50, value.toString().length()))));
                }
                
                // 正常处理逻辑
                String[] words = value.toString().split("\\s+");
                for (String word : words) {
                    if (!word.isEmpty()) {
                        context.write(new Text(word), new IntWritable(1));
                    }
                }
                
            } catch (Exception e) {
                // 🚨 记录异常信息
                System.err.println(String.format(
                    "❌ ERROR at line %d: %s, data: %s", 
                    lineNumber, e.getMessage(), value.toString()));
                
                // 💾 写入错误计数器
                context.getCounter("DEBUG", "PROCESSING_ERRORS").increment(1);
                
                // 📊 继续处理还是抛出异常?
                // throw e;  // 严格模式:遇到错误就停止
                // 或者跳过错误行继续处理
            }
        }
        
        @Override
        protected void cleanup(Context context) throws IOException, InterruptedException {
            System.out.println(String.format("✅ Mapper完成,总计处理 %d 行", lineNumber));
        }
    }
    
    /**
     * 📊 数据质量检查Mapper
     */
    public static class DataQualityMapper extends Mapper<LongWritable, Text, Text, Text> {
        
        enum DataQuality {
            TOTAL_RECORDS,
            EMPTY_LINES,
            INVALID_FORMAT,
            NULL_VALUES,
            DUPLICATES
        }
        
        @Override
        public void map(LongWritable key, Text value, Context context) 
                throws IOException, InterruptedException {
            
            context.getCounter(DataQuality.TOTAL_RECORDS).increment(1);
            
            String line = value.toString().trim();
            
            // ✅ 检查空行
            if (line.isEmpty()) {
                context.getCounter(DataQuality.EMPTY_LINES).increment(1);
                return;
            }
            
            // ✅ 检查数据格式
            String[] fields = line.split(",", -1);
            if (fields.length < 3) {
                context.getCounter(DataQuality.INVALID_FORMAT).increment(1);
                context.write(new Text("INVALID_FORMAT"), value);
                return;
            }
            
            // ✅ 检查空值
            for (String field : fields) {
                if (field.trim().isEmpty() || "null".equals(field.toLowerCase())) {
                    context.getCounter(DataQuality.NULL_VALUES).increment(1);
                    context.write(new Text("NULL_VALUE"), value);
                    return;
                }
            }
            
            // ✅ 正常数据
            context.write(new Text("VALID_DATA"), value);
        }
    }
}

🛡️ 容错机制详解

任务级容错

Hadoop有强大的自动容错能力,就像是"自动修复系统"。

<configuration>
    <!-- 🔄 Map任务最大重试次数 -->
    <property>
        <name>mapreduce.map.maxattempts</name>
        <value>4</value>
        <description>Map任务失败后最多重试3次</description>
    </property>
    
    <!-- 🔄 Reduce任务最大重试次数 -->
    <property>
        <name>mapreduce.reduce.maxattempts</name>
        <value>4</value>
        <description>Reduce任务失败后最多重试3次</description>
    </property>
    
    <!-- ⏱️ 任务超时时间 -->
    <property>
        <name>mapreduce.task.timeout</name>
        <value>600000</value>
        <description>任务无响应10分钟后被杀死</description>
    </property>
    
    <!-- 🚫 失败任务比例阈值 -->
    <property>
        <name>mapreduce.map.failures.maxpercent</name>
        <value>0</value>
        <description>允许的Map任务失败比例</description>
    </property>
    
    <!-- 🔧 推测执行开关 -->
    <property>
        <name>mapreduce.map.speculative</name>
        <value>true</value>
        <description>启用Map任务推测执行</description>
    </property>
</configuration>

节点级容错

# 🔍 检查节点健康状态
yarn node -list -all

# 输出示例:
# node1:8041    RUNNING    0    4096    0    4
# node2:8041    UNHEALTHY  0    4096    0    4  # 不健康节点
# node3:8041    LOST       0    4096    0    4  # 丢失节点

# 🛠️ 节点故障恢复步骤
echo "🩺 节点故障诊断和恢复流程:"

# 1️⃣ 检查节点是否可达
ping -c 3 node2
ssh node2 'echo "节点可访问"'

# 2️⃣ 检查NodeManager服务
ssh node2 'jps | grep NodeManager'
ssh node2 'systemctl status hadoop-yarn-nodemanager'

# 3️⃣ 查看节点日志
ssh node2 'tail -100 $HADOOP_HOME/logs/yarn-hadoop-nodemanager-*.log'

# 4️⃣ 重启NodeManager(如果需要)
# 仅在确认该systemd服务名与部署一致并获得重启授权后执行,避免调用集群级启停脚本
ssh node2 'sudo systemctl restart hadoop-yarn-nodemanager'

数据级容错

public class DataRecoveryMapper extends Mapper<LongWritable, Text, Text, Text> {
    
    private static final String BACKUP_PATH = "/backup/corrupted_data/";
    private FileSystem fs;
    private FSDataOutputStream backupStream;
    
    @Override
    protected void setup(Context context) throws IOException {
        // 📁 初始化备份输出流
        Configuration conf = context.getConfiguration();
        fs = FileSystem.get(conf);
        
        String taskId = context.getTaskAttemptID().toString();
        Path backupFile = new Path(BACKUP_PATH + "corrupted_" + taskId + ".txt");
        backupStream = fs.create(backupFile);
    }
    
    @Override
    public void map(LongWritable key, Text value, Context context) 
            throws IOException, InterruptedException {
        
        try {
            // 🧹 尝试处理数据
            processData(value, context);
            
        } catch (Exception e) {
            // 🚨 处理失败,备份原始数据
            String errorRecord = String.format(
                "输入字节偏移:%d, 错误:%s, 数据:%s%n", 
                key.get(), e.getMessage(), value.toString());
            
            backupStream.write(errorRecord.getBytes(java.nio.charset.StandardCharsets.UTF_8));
            backupStream.flush();
            
            // 📊 记录错误统计
            context.getCounter("DATA_RECOVERY", "CORRUPTED_RECORDS").increment(1);
            
            // 💡 决定是否继续处理
            if (shouldContinueOnError(e)) {
                System.err.println("⚠️ 跳过错误数据: " + e.getMessage());
            } else {
                throw new IOException("严重错误,停止处理", e);
            }
        }
    }
    
    private void processData(Text value, Context context) throws IOException, InterruptedException {
        // 正常的数据处理逻辑(仅适用于不含引号、转义或字段内逗号的简单分隔文本)
        String[] fields = value.toString().split(",", -1);
        
        if (fields.length < 2) {
            throw new IllegalArgumentException("数据格式错误:字段数不足");
        }
        
        // 验证数据格式
        validateData(fields);
        
        // 输出处理结果:以第一个字段作为键
        context.write(new Text(fields[0]), value);
    }
    
    private void validateData(String[] fields) {
        // 🔍 数据验证逻辑
        for (String field : fields) {
            if (field.trim().isEmpty()) {
                throw new IllegalArgumentException("包含空字段");
            }
        }
    }
    
    private boolean shouldContinueOnError(Exception e) {
        // 💡 决定哪些错误可以跳过
        return e instanceof IllegalArgumentException || 
               e instanceof NumberFormatException;
    }
    
    @Override
    protected void cleanup(Context context) throws IOException {
        if (backupStream != null) {
            backupStream.close();
        }
    }
}

🛠️ 实际问题排查案例

案例1:内存溢出问题

# 🚨 问题症状:任务频繁失败,日志显示OutOfMemoryError

echo "🩺 内存溢出问题诊断"

# 1️⃣ 检查错误日志
yarn logs -applicationId application_xxx | grep -i "outofmemory" -A 10 -B 10

# 2️⃣ 查看当前内存配置
# 在JobHistory或作业详情中查看该作业实际提交的配置,检索mapreduce.*memory与java.opts;
# 不应仅以集群配置文件代替作业配置,应用代码和提交参数可能覆盖它们。

# 3️⃣ 分析堆转储文件(如果有)
ls -la /tmp/hadoop-*/heap_dump_*.hprof

# 4️⃣ 检查数据特征
hdfs dfs -du -h /input/path/
echo "检查是否有异常大的输入文件"
<!-- 💾 增加内存配置 -->
<property>
    <name>mapreduce.map.java.opts</name>
    <value>-Xmx4096m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/</value>
</property>

<property>
    <name>mapreduce.map.memory.mb</name>
    <value>6144</value>
</property>

<!-- 🗜️ Map输出压缩用于减少中间数据传输量,不保证降低堆内存使用或解决内存溢出 -->
<property>
    <name>mapreduce.map.output.compress</name>
    <value>true</value>
</property>

案例2:数据倾斜导致任务挂起

public class DataSkewDetector extends Mapper<LongWritable, Text, Text, LongWritable> {
    
    private Map<String, Long> keyCountMap = new HashMap<>();
    private static final int SAMPLE_SIZE = 10000;
    private int recordCount = 0;
    
    @Override
    public void map(LongWritable key, Text value, Context context) 
            throws IOException, InterruptedException {
        
        recordCount++;
        String[] fields = value.toString().split(",", -1);
        
        // 📊 采样分析
        if (recordCount <= SAMPLE_SIZE) {
            if (fields.length > 0) {
                String keyField = fields[0];  // 假设第一个字段是key
                keyCountMap.merge(keyField, 1L, Long::sum);
            }
        }
        
        // 📈 定期报告采样结果
        if (recordCount == SAMPLE_SIZE) {
            analyzeDataDistribution(context);
        }
        
        // 正常处理:以第一个字段作为键
        context.write(new Text(fields[0]), new LongWritable(1));
    }
    
    private void analyzeDataDistribution(Context context) {
        
        long totalSamples = keyCountMap.values().stream().mapToLong(Long::longValue).sum();
        long avgCount = totalSamples / keyCountMap.size();
        
        System.out.println("📊 数据分布分析结果:");
        System.out.println("采样记录数: " + totalSamples);
        System.out.println("唯一键数量: " + keyCountMap.size());
        System.out.println("平均每键记录数: " + avgCount);
        
        // 🚨 检测数据倾斜
        keyCountMap.entrySet().stream()
            .filter(entry -> entry.getValue() > avgCount * 10)  // 超过平均值10倍
            .sorted(Map.Entry.<String, Long>comparingByValue().reversed())
            .limit(5)
            .forEach(entry -> {
                System.out.println(String.format(
                    "⚠️ 数据倾斜键: %s (记录数: %d, 是平均值的 %.1f 倍)",
                    entry.getKey(), entry.getValue(), 
                    (double) entry.getValue() / avgCount));
                
                // 📊 写入倾斜计数器
                context.getCounter("DATA_SKEW", "SKEWED_KEYS").increment(1);
            });
    }
}

案例3:网络连接问题

#!/bin/bash
# network_diagnosis.sh - 网络连接问题诊断

echo "🌐 网络连接问题诊断"

# 1️⃣ 检查关键端口连通性
NAMENODE_HOST="namenode"
RESOURCEMANAGER_HOST="resourcemanager"

echo "📍 检查HDFS连接..."
nc -zv $NAMENODE_HOST 9000 2>&1 | grep -E "(succeeded|refused)"

echo "📍 检查YARN连接..."
nc -zv $RESOURCEMANAGER_HOST 8032 2>&1 | grep -E "(succeeded|refused)"

# 2️⃣ 检查DNS解析
echo "🔍 DNS解析检查..."
nslookup $NAMENODE_HOST
nslookup $RESOURCEMANAGER_HOST

# 3️⃣ 检查网络延迟
echo "⏱️ 网络延迟检查..."
ping -c 5 $NAMENODE_HOST | tail -1
ping -c 5 $RESOURCEMANAGER_HOST | tail -1

# 4️⃣ 检查防火墙状态
echo "🔒 防火墙状态..."
systemctl status firewalld 2>/dev/null || echo "firewalld未安装"
iptables -L | grep -E "(REJECT|DROP)" | head -5

# 5️⃣ 检查网络配置
echo "⚙️ 网络配置..."
cat /etc/hosts | grep -E "(namenode|resourcemanager)"

📊 监控和预警系统

自定义监控脚本

#!/bin/bash
# MapReduce健康监控脚本

ALERT_EMAIL="admin@company.com"
LOG_FILE="/var/log/mapreduce_monitor.log"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}

check_resource_manager() {
    log "🔍 检查ResourceManager状态..."
    
    if ! curl -s http://localhost:8088/ws/v1/cluster/info > /dev/null; then
        log "❌ ResourceManager无响应"
        return 1
    fi
    
 # 检查活跃节点数
    ACTIVE_NODES=$(curl -s http://localhost:8088/ws/v1/cluster/nodes | jq '[.nodes.node[]? | select(.state == "RUNNING")] | length')
    
    if [ "$ACTIVE_NODES" -lt 3 ]; then  # 假设最少需要3个节点
        log "⚠️ 活跃节点数过少: $ACTIVE_NODES"
        return 1
    fi
    
    log "✅ ResourceManager状态正常,活跃节点: $ACTIVE_NODES"
    return 0
}

check_failed_applications() {
    log "🔍 检查失败的应用程序..."
    
    FAILED_APPS=$(yarn application -list -appStates FAILED | grep -c "application_")
    
    if [ "$FAILED_APPS" -gt 5 ]; then  # 失败应用超过5个
        log "⚠️ 失败应用过多: $FAILED_APPS"
        return 1
    fi
    
    log "✅ 失败应用数量正常: $FAILED_APPS"
    return 0
}

check_cluster_memory() {
    log "🔍 检查集群内存使用..."
    
    MEMORY_INFO=$(curl -s http://localhost:8088/ws/v1/cluster/metrics | jq '.clusterMetrics')
    TOTAL_MEMORY=$(echo $MEMORY_INFO | jq '.totalMB')
    AVAILABLE_MEMORY=$(echo $MEMORY_INFO | jq '.availableMB')
    
    if [ "$TOTAL_MEMORY" -gt 0 ]; then
        USAGE_PERCENT=$((($TOTAL_MEMORY - $AVAILABLE_MEMORY) * 100 / $TOTAL_MEMORY))
        
        if [ "$USAGE_PERCENT" -gt 85 ]; then  # 内存使用超过85%
            log "⚠️ 集群内存使用率过高: ${USAGE_PERCENT}%"
            return 1
        fi
        
        log "✅ 集群内存使用正常: ${USAGE_PERCENT}%"
    fi
    
    return 0
}

send_alert() {
    local message="$1"
    log "📧 发送告警: $message"
    
 # 发送邮件告警
    echo "$message" | mail -s "MapReduce集群告警" $ALERT_EMAIL
    
 # 可以添加其他告警方式:钉钉、微信、Slack等
}

main() {
    log "🚀 开始MapReduce健康检查..."
    
    local issues=0
    
 # 执行各项检查
    check_resource_manager || ((issues++))
    check_failed_applications || ((issues++))
    check_cluster_memory || ((issues++))
    
    if [ $issues -eq 0 ]; then
        log "✅ 所有检查项正常"
    else
        log "❌ 发现 $issues 个问题"
        send_alert "MapReduce集群存在 $issues 个问题,请及时处理"
    fi
    
    log "🏁 健康检查完成"
}

# 设置定时执行:*/5 * * * * /path/to/mapreduce_health_monitor.sh
main

性能指标收集

public class MetricsCollector extends Mapper<LongWritable, Text, Text, IntWritable> {
    
    private long startTime;
    private long recordCount = 0;
    private long totalBytes = 0;
    
    // 📊 性能指标
    private static final String METRICS_PREFIX = "PERFORMANCE_";
    
    @Override
    protected void setup(Context context) {
        startTime = System.currentTimeMillis();
        
        // 📝 记录任务开始
        System.out.println(String.format(
            "📊 [%s] 任务开始 - TaskID: %s",
            new java.util.Date(), context.getTaskAttemptID()));
    }
    
    @Override
    public void map(LongWritable key, Text value, Context context) 
            throws IOException, InterruptedException {
        
        recordCount++;
        totalBytes += value.getLength();
        
        // 📈 定期收集性能指标
        if (recordCount % 10000 == 0) {
            collectMetrics(context);
        }
        
        // 正常处理逻辑...
        context.write(new Text("processed"), new IntWritable(1));
    }
    
    private void collectMetrics(Context context) {
        long currentTime = System.currentTimeMillis();
        long elapsedTime = currentTime - startTime;
        
        // 📊 计算性能指标
        double recordsPerSecond = (double) recordCount / (elapsedTime / 1000.0);
        double mbPerSecond = (double) totalBytes / (1024 * 1024) / (elapsedTime / 1000.0);
        
        // 💾 获取内存使用情况
        Runtime runtime = Runtime.getRuntime();
        long usedMemory = runtime.totalMemory() - runtime.freeMemory();
        double memoryUsageMB = usedMemory / (1024.0 * 1024.0);
        
        // 📝 记录指标
        System.out.println(String.format(
            "📊 性能指标 - 记录数/秒: %.2f, MB/秒: %.2f, 内存使用: %.2f MB",
            recordsPerSecond, mbPerSecond, memoryUsageMB));
        
        // 📈 写入计数器
        context.getCounter(METRICS_PREFIX + "RECORDS_PER_SECOND", 
            String.valueOf((int) recordsPerSecond)).increment(1);
        context.getCounter(METRICS_PREFIX + "MEMORY_USAGE_MB", 
            String.valueOf((int) memoryUsageMB)).increment(1);
    }
    
    @Override
    protected void cleanup(Context context) {
        long totalTime = System.currentTimeMillis() - startTime;
        
        // 📊 最终性能报告
        double avgRecordsPerSecond = (double) recordCount / (totalTime / 1000.0);
        double avgMBPerSecond = (double) totalBytes / (1024 * 1024) / (totalTime / 1000.0);
        
        System.out.println(String.format(
            "✅ [%s] 任务完成 - 总记录: %d, 总耗时: %d ms, 平均速度: %.2f 记录/秒, %.2f MB/秒",
            new java.util.Date(), recordCount, totalTime, avgRecordsPerSecond, avgMBPerSecond));
        
        // 📊 写入最终指标
        context.getCounter("FINAL_METRICS", "TOTAL_RECORDS").setValue(recordCount);
        context.getCounter("FINAL_METRICS", "TOTAL_TIME_MS").setValue(totalTime);
        context.getCounter("FINAL_METRICS", "AVG_RECORDS_PER_SEC").setValue((long) avgRecordsPerSecond);
    }
}

🎯 故障预防最佳实践

代码层面预防

success ✅ 代码质量检查清单

  • 输入验证:检查输入数据格式和范围
  • 异常处理:捕获和处理可能的异常
  • 资源管理:正确关闭文件和连接
  • 内存管理:避免内存泄漏和过度使用
  • 日志记录:记录关键操作和错误信息

配置层面预防

<configuration>
    <!-- 🛡️ 增强容错配置 -->
    <property>
        <name>mapreduce.map.maxattempts</name>
        <value>4</value>
        <description>Map任务最大尝试次数,包含首次执行;值为4时失败后最多重试3次</description>
    </property>
    
    <!-- ⏱️ 合理的超时设置 -->
    <property>
        <name>mapreduce.task.timeout</name>
        <value>1800000</value>
        <description>30分钟超时</description>
    </property>
    
    <!-- 💾 内存安全边际 -->
    <property>
        <name>mapreduce.map.java.opts</name>
        <value>-Xmx2048m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError</value>
        <description>保守的内存配置和GC优化</description>
    </property>
    
    <!-- 🔍 配置任务环境变量(此项不启用详细日志) -->
    <property>
        <name>mapreduce.admin.user.env</name>
        <value>LD_LIBRARY_PATH=/usr/local/lib</value>
        <description>环境变量配置</description>
    </property>
</configuration>

运维层面预防

#!/bin/bash
# preventive_maintenance.sh - 预防性维护

echo "🛠️ MapReduce预防性维护"

# 1️⃣ 清理临时文件
echo "🧹 清理临时文件..."
# 先列出候选文件;确认不属于运行中的任务、满足日志保留要求并获得授权后再单独清理
find /tmp/hadoop* -type f -mtime +7 -print
find "$HADOOP_HOME/logs" -name "*.log.*" -mtime +30 -print

# 2️⃣ 检查磁盘空间
echo "💿 检查磁盘空间..."
df -P | awk 'NR > 1 {usage = $5; sub(/%$/, "", usage); if (usage + 0 > 85) print "⚠️ 磁盘使用率过高: " $0}'

# 3️⃣ 检查系统负载
echo "📊 检查系统负载..."
# Linux环境:读取1分钟平均负载,避免依赖uptime输出格式
LOAD=$(awk '{print $1}' /proc/loadavg)
CPU_COUNT=$(nproc)
if (( $(echo "$LOAD > $CPU_COUNT * 0.8" | bc -l) )); then
    echo "⚠️ 系统负载过高: $LOAD"
fi

# 4️⃣ 检查网络连接
echo "🌐 检查网络连接..."
netstat -tuln | grep -E ":8088|:9000|:50070" | grep LISTEN || echo "⚠️ 关键端口未监听"

# 5️⃣ 检查Java进程
echo "☕ 检查Java进程..."
jps | grep -E "(NameNode|DataNode|ResourceManager|NodeManager)" || echo "⚠️ 关键进程未运行"

echo "✅ 预防性维护完成"

🔗 下一步学习

掌握MapReduce故障处理后,建议继续学习:

  1. YARN资源管理 - 深入理解任务调度和资源分配
  2. YARN集群 - 学习集群级别的运维管理
  3. 性能监控与调优 - 建立完整的监控体系
  4. 故障排查与运维 - 掌握企业级运维技能

tip 💡 故障处理建议

  • 预防为主:通过监控和维护减少故障发生
  • 快速响应:建立故障告警和响应机制
  • 经验积累:记录故障案例和解决方案
  • 持续改进:根据故障经验优化系统
  • 团队协作:建立故障处理标准流程

最后更新:2024-06-29