問題規模
- 單日 20GB 日志文件
- 格式:
IP | 時間 | 客戶信息 | ... (管道符分隔)
- 目標:統計訪問量前 10 的 IP 地址
方案對比
| 方案 | 適用場景 | 優點 | 缺點 | 預估耗時 |
| Linux 命令行 | 單機、臨時分析、數據 < 50GB | 無部署、管道流式處理、內存友好 | 單線程、無法橫向擴展 | ~2-5 分鐘 |
| Python/Pandas | 數據 < 10GB、需複雜分析 | 代碼靈活、生態豐富 | 內存受限、需分塊讀取 | ~5-10 分鐘 |
| Spark/Flink | 生產環境、定期任務、TB 級 | 分布式、容錯、可擴展、SQL 支持 | 部署維護成本高、啟動開銷大 | ~30 秒-1 分鐘 (集群) |
| ClickHouse/Elasticsearch | 實時查詢、多維分析、儲存+計算 | 毫秒級查詢、壓縮存儲 | 需預先導入、運維複雜 | 查詢 < 1 秒 |
方案一:Linux 命令行(單機最快、零依賴)
# 1. 提取 IP 欄位 (假設第 1 欄) → 排序 → 去重計數 → 降序排序 → 取前 10
awk -F'|' '{print $1}' access.log | sort | uniq -c | sort -k1,1nr | head -10
# 解析:
# -F'|' 按管道符分隔
# {print $1} 打印第1列 (IP)
# sort 排序 (uniq -c 需相鄰相同)
# uniq -c 計數去重 (輸出: count IP)
# sort -k1,1nr 按第1列數值降序排序
# head -10 取前10行
# 進階: 顯示百分比
awk -F'|' '{print $1}' access.log | sort | uniq -c | sort -k1,1nr | awk '{sum+=$1} END {for(i=1;i<=NR;i++) print $2, $1, ($1/sum)*100"%"}' | head -10
方案二:Python 處理大文件 (分塊讀取、內存安全)
import sys
from collections import Counter
def top_ips(log_file, top_n=10, chunk_size=1024*1024):
counter = Counter()
with open(log_file, 'r', encoding='utf-8', errors='ignore') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
# 處理塊邊界可能切斷行的問題
lines = chunk.split('\n')
# 除最後一行外都是完整行
for line in lines[:-1]:
ip = line.split('|')[0].strip()
if ip: counter[ip] += 1
# 最後一行可能不完整,下次循環拼接
# 簡化版忽略邊界問題,生產環境需處理
for ip, count in counter.most_common(top_n):
print(f"{ip}\t{count}")
if __name__ == '__main__':
top_ips('access.log')
方案三:Spark SQL (生產級、分布式)
# spark-submit --master yarn top_ip.py
from pyspark.sql import SparkSession
from pyspark.sql.functions import col, desc
spark = SparkSession.builder.appName("TopIP").getOrCreate()
# 讀取文本文件,解析為 DataFrame
df = spark.read.text("hdfs:///logs/access.log") \
.selectExpr("split(value, '\\|')[0] as ip") \
.groupBy("ip") \
.count() \
.orderBy(desc("count")) \
.limit(10)
df.show(truncate=False)
# 寫入結果
df.write.mode("overwrite").csv("hdfs:///output/top10_ip")
方案四:ClickHouse (極速 OLAP、適合反覆查詢)
-- 建表
CREATE TABLE access_log (
ip String,
time DateTime,
client_info String
) ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(time)
ORDER BY (ip, time);
-- 導入數據 (clickhouse-client)
cat access.log | clickhouse-client --query="INSERT INTO access_log FORMAT CSVWithNames" --date_time_input_format=best_effort
-- 查詢 Top 10 (毫秒級)
SELECT ip, count() as cnt
FROM access_log
WHERE time >= today() - 1
GROUP BY ip
ORDER BY cnt DESC
LIMIT 10;
關鍵優化技巧
- 預過濾:
grep -v "healthcheck" access.log | awk ... 排除無效行
- 並行處理:
split -l 1000000 access.log part_ && parallel awk ... ::: part_*
- 內存映射:
mmap / madvise 加速大文件讀取
- 列式存儲:Parquet/ORC 只讀取 IP 列,I/O 減少 90%+
架構選型建議
一次性臨時分析 → Linux awk 命令行 (最快上手)
每日定時報表 → Spark/Flink 定時任務
實時大屏/多維探索 → ClickHouse/Druid/Elasticsearch
海量長期存儲+查詢 → 數據湖 + Presto/Trino
總結
- 🎯 **20GB 單機**:
awk -F'|' '{print $1}' log | sort | uniq -c | sort -k1,1nr | head -10 幾分鐘搞定
- 🏭 **生產環境**:上 Spark/Flink 集群,或導入 ClickHouse 實現秒級交互查詢
- 💡 **核心**:流式處理 (不載入全量內存) + 分布式計算 (水平擴展)