3、HDFS小文件检测:用命令、脚本和可视化看清你的数据

做小文件治理,第一步不是动手删,而是先搞清楚——你的集群里到底有多少小文件?它们分布在哪些目录?占了多少块?

我见过不少团队,上来就写脚本批量合并,结果把大文件也误伤了。说白了,没有检测就没有治理。这一章,我们就来聊聊怎么把HDFS里的小文件“揪出来”。

3.1 用HDFS自带命令快速摸底

最直接的方式,就是用hdfs dfs -count和hdfs dfs -ls。这两个命令虽然简单,但用好了能快速定位问题目录。

3.1.1 统计目录下的文件数量和大小

# 查看某个目录的统计信息
hdfs dfs -count /user/hive/warehouse

# 输出格式:目录数 文件数 文件总大小(字节) 路径

输出示例:

目录数 文件数 总大小 路径
12 8,934 45,678,901,234 /user/hive/warehouse

你看,光看这个数字,8,934个文件,总大小45GB,平均每个文件才5MB左右。嗯,这已经低于HDFS默认块大小128MB了,属于典型的小文件场景。

我的习惯:先用hdfs dfs -count扫一遍顶层目录,哪个目录文件数异常多,就重点排查哪个。这比直接全量扫描快得多。

3.1.2 递归列出所有文件并过滤

# 递归列出所有文件,并过滤出小于128MB的文件
hdfs dfs -ls -R /user/hive/warehouse | awk '{if($5 < 134217728) print $8, $5}' | head -20

这个命令会把每个文件的大小和路径打印出来。但说实话,生产环境目录层级深、文件多,跑起来很慢。我一般只在排查单个目录时用。

注意:hdfs dfs -ls -R在文件数超过百万级时,会非常慢,甚至导致NameNode负载飙升。别在生产高峰期跑。

3.2 编写脚本批量扫描小文件

命令行的方式适合临时查一下,但要做定期巡检、生成报告,就得写脚本了。我个人习惯用Python,因为处理数据、生成报表都方便。

3.2.1 核心思路

  1. 通过subprocess调用HDFS命令,获取文件列表
  2. 解析输出,提取文件路径、大小、块数
  3. 按阈值(比如128MB)过滤出小文件
  4. 按目录聚合,统计每个目录下的小文件数量和总大小

3.2.2 一个可用的扫描脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
HDFS小文件扫描脚本
用法:python3 scan_small_files.py --path /user/hive --threshold 134217728
"""

import subprocess
import sys
import argparse
from collections import defaultdict

def get_file_list(hdfs_path):
    """递归获取目录下所有文件信息"""
    cmd = f"hdfs dfs -ls -R {hdfs_path}"
    result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    
    files = []
    for line in result.stdout.strip().split('\n'):
        parts = line.split()
        if len(parts) < 8:
            continue
        # 只处理文件(以-开头)
        if line.startswith('-'):
            file_size = int(parts[4])
            file_path = parts[7]
            files.append((file_path, file_size))
    return files

def scan_small_files(hdfs_path, threshold=128*1024*1024):
    """扫描小文件并按目录聚合"""
    print(f"正在扫描路径: {hdfs_path}")
    print(f"小文件阈值: {threshold / 1024 / 1024} MB")
    
    files = get_file_list(hdfs_path)
    print(f"共发现 {len(files)} 个文件")
    
    # 按父目录聚合
    dir_stats = defaultdict(lambda: {'count': 0, 'total_size': 0})
    small_file_count = 0
    
    for file_path, file_size in files:
        parent_dir = '/'.join(file_path.split('/')[:-1])
        if file_size < threshold:
            dir_stats[parent_dir]['count'] += 1
            dir_stats[parent_dir]['total_size'] += file_size
            small_file_count += 1
    
    print(f"其中小文件 {small_file_count} 个,占比 {small_file_count/len(files)*100:.2f}%")
    print("\n按目录统计小文件分布(Top 20):")
    print(f"{'目录':<60} {'小文件数':<10} {'总大小(MB)':<15}")
    print("-" * 85)
    
    # 按小文件数排序,取前20
    sorted_dirs = sorted(dir_stats.items(), key=lambda x: x[1]['count'], reverse=True)[:20]
    for dir_path, stats in sorted_dirs:
        size_mb = stats['total_size'] / 1024 / 1024
        print(f"{dir_path[:58]:<60} {stats['count']:<10} {size_mb:<15.2f}")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='HDFS小文件扫描工具')
    parser.add_argument('--path', required=True, help='要扫描的HDFS路径')
    parser.add_argument('--threshold', type=int, default=128*1024*1024, help='小文件阈值(字节)')
    args = parser.parse_args()
    
    scan_small_files(args.path, args.threshold)
避坑指南:我曾经在生产环境直接跑这个脚本,没加超时控制,结果NameNode卡了5分钟。后来我加了timeout参数,并且只扫描最近有数据写入的目录,避免全量扫描。

3.3 可视化小文件分布

光看数字不够直观。你想想看,给老板汇报的时候,一张图比一堆数字有说服力得多。我通常用两种方式:一是生成HTML报告,二是用SVG画分布图。

3.3.1 生成目录级小文件分布图

下面这张SVG图,展示了某个Hive数仓下各目录的小文件分布情况。一眼就能看出哪个目录是“重灾区”。

HDFS小文件分布图(按目录) 小文件数 > 1000 500 ~ 1000 100 ~ 500 < 100 2,340 ods 1,890 dwd 780 dws 320 ads 3,560 tmp 45 dim 0 1000 2000 3000 说明:tmp目录小文件最多(3,560个),是治理的重点。ods和dwd也超过1000,需要关注。

3.3.2 生成HTML报告

我习惯把扫描结果输出成一个自包含的HTML文件,里面包含表格和图表。这样每次巡检后,直接打开浏览器就能看。

# 生成HTML报告的示例(Python)
def generate_html_report(dir_stats, total_files, small_files):
    html = """
    <!DOCTYPE html>
    <html>
    <head>
        <title>HDFS小文件巡检报告</title>
        <style>
            body { font-family: Arial, sans-serif; margin: 20px; }
            table { border-collapse: collapse; width: 100%; }
            th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
            th { background-color: #3498db; color: white; }
            .highlight { background-color: #ffeaa7; }
        </style>
    </head>
    <body>
        <h2>HDFS小文件巡检报告</h2>
        <p>扫描时间: 2025-01-15 10:00:00</p>
        <p>总文件数: {total_files}, 小文件数: {small_files}</p>
        <table>
            <tr><th>目录</th><th>小文件数</th><th>总大小(MB)</th></tr>
    """.format(total_files=total_files, small_files=small_files)
    
    for dir_path, stats in sorted_dirs:
        html += f"<tr><td>{dir_path}</td><td>{stats['count']}</td><td>{stats['total_size']/1024/1024:.2f}</td></tr>"
    
    html += "</table></body></html>"
    return html

3.4 检测策略总结

说了这么多,我整理了一个检测策略对比表,方便你根据场景选择。

检测方式 适用场景 优点 缺点
hdfs dfs -count 快速查看目录级统计 速度快,对NameNode压力小 无法看到单个文件详情
hdfs dfs -ls -R 小范围目录排查 能看到每个文件 文件多时极慢
Python扫描脚本 定期巡检、生成报告 可定制、可聚合、可输出报表 需要开发维护
可视化报告 汇报、趋势分析 直观、易于理解 需要额外生成步骤
我的建议:日常巡检用hdfs dfs -count就够了,每周跑一次Python脚本生成报告。如果发现某个目录小文件激增,再用ls -R去细查。分层检测,效率最高。

检测只是第一步。拿到数据后,你就能回答三个关键问题:哪些目录最严重?小文件总量有多大?增长趋势如何?有了这些,下一步的治理才有方向。