数据工程实战课程 · 实战项目阶段
构建完整的日志收集→解析→存储→分析管道,从访问日志中提取业务洞察。
# 日志分析管道
import re, time, random, json
from datetime import datetime, timedelta
from collections import defaultdict
class LogGenerator:
def __init__(self):
self.ips = [f"192.168.{random.randint(1,255)}.{random.randint(1,255)}" for _ in range(50)]
self.paths = ['/','/api/users','/api/orders','/products','/search','/login','/checkout']
self.codes = [200,200,200,200,301,404,500]
def generate(self, count=1000):
logs = []
for _ in range(count):
ts = datetime.now() - timedelta(seconds=random.randint(0,3600))
log = f'{self.ips[random.randint(0,len(self.ips)-1)]} - - [{ts.strftime("%d/%b/%Y:%H:%M:%S")}] "GET {random.choice(self.paths)} HTTP/1.1" {random.choice(self.codes)} {random.randint(100,50000)}'
logs.append(log)
return logs
class LogParser:
PATTERN = re.compile(r'(\S+) - - \[([^\]]+)\] "(\w+) (\S+) \S+" (\d+) (\d+)')
def parse(self, log_line):
m = self.PATTERN.match(log_line)
if m:
return {'ip':m.group(1),'timestamp':m.group(2),'method':m.group(3),'path':m.group(4),'status':int(m.group(5)),'size':int(m.group(6))}
return None
class LogAnalyzer:
def __init__(self): self.stats = defaultdict(lambda: defaultdict(int))
def analyze(self, parsed_logs):
for log in parsed_logs:
self.stats['status'][log['status']] += 1
self.stats['path'][log['path']] += 1
self.stats['ip'][log['ip']] += 1
def top_n(self, category, n=5):
return sorted(self.stats[category].items(), key=lambda x: x[1], reverse=True)[:n]
def error_rate(self):
total = sum(self.stats['status'].values())
errors = sum(v for k,v in self.stats['status'].items() if k >= 400)
return round(errors/total*100,2) if total else 0
def qps(self, duration_sec=3600):
total = sum(self.stats['status'].values())
return round(total/duration_sec, 2)
# 运行
gen = LogGenerator()
parser = LogParser()
analyzer = LogAnalyzer()
logs = gen.generate(5000)
parsed = [p for log in logs if (p := parser.parse(log))]
analyzer.analyze(parsed)
print("日志分析管道:")
print(f" 总请求: {len(parsed)}")
print(f" 错误率: {analyzer.error_rate()}%")
print(f" QPS: {analyzer.qps()}")
print(f" Top路径: {analyzer.top_n('path',3)}")
print(f" Top IP: {analyzer.top_n('ip',3)}")
print(f" 状态码: {dict(analyzer.stats['status'])}")
print("✅ 验证通过 - 日志分析管道运行正常")
🎁 下一课预告:实时监控平台!
| 阶段 | 目标 | 关键能力 | 周期 |
|---|---|---|---|
| MVP | 基础数据流转 | ETL+数仓+报表 | 1-3月 |
| 成长期 | 数据自助 | 自助分析+流处理+治理 | 3-6月 |
| 成熟期 | 数据驱动 | 实时+ML+数据产品 | 6-12月 |
| 领先期 | 数据创新 | AI+自动化+货币化 | 12月+ |
在生产环境中,我们经常遇到教科书不会告诉你的问题。以下是常见的实战经验和解决方案:
| 指标 | 告警阈值 | 监控方式 |
|---|---|---|
| 管道延迟 | 超过SLA 120% | 端到端时间戳追踪 |
| 数据量偏差 | 日均偏差超过30% | 与历史同期对比 |
| 错误率 | 超过0.1% | 错误计数/总记录数 |
| 数据新鲜度 | 超过SLA 150% | 最新数据时间戳 |
| 资源利用率 | CPU持续>80% | 系统监控 |
A: 根据业务延迟要求决定:秒级用流处理、分钟级用微批、小时级用批处理。大部分场景批处理足够,流处理用于实时业务场景(风控、监控、推荐)。
A: 不会完全取代,但边界在模糊。湖仓一体(如Iceberg/Delta Lake)正在融合两者优势。未来趋势是统一存储+多引擎计算。
A: 先用云原生服务(BigQuery/Snowflake + Fivetran + dbt + Looker),验证业务后再考虑自建。过早优化是万恶之源。
A: 用钱说话:数据质量问题的直接经济损失(错误决策、合规罚款、重复劳动)远大于治理投入。量化问题是第一步。
| 角色 | 职责 | 交付物 |
|---|---|---|
| 数据工程师 | 管道开发与维护 | 数据管道、基础设施 |
| 数据分析师 | 数据分析与报表 | SQL查询、BI报表 |
| 数据科学家 | 模型与实验 | ML模型、实验报告 |
| 数据产品经理 | 需求与优先级 | PRD、数据产品规划 |
| 数据管家 | 数据质量与治理 | 质量规则、数据字典 |
选择技术方案时,需要从多个维度综合评估,避免仅凭流行度或个人偏好决策:
| 评估维度 | 权重 | 评估要点 |
|---|---|---|
| 业务适配度 | 30% | 是否满足功能需求?延迟/吞吐是否达标? |
| 团队能力 | 25% | 团队是否有经验?学习曲线如何? |
| 社区生态 | 20% | 社区活跃度?文档质量?第三方集成? |
| 运维成本 | 15% | 部署复杂度?监控能力?故障恢复? |
| 成本效益 | 10% | License费用?资源消耗?扩展成本? |
数据量级? ├── <1GB/天 → SQLite/PostgreSQL + Python脚本 ├── 1-100GB/天 → 数据仓库(Snowflake/BQ) + dbt ├── 100GB-1TB/天 → Spark + 数据湖 └── >1TB/天 → Spark/Flink + 湖仓一体 延迟要求? ├── T+1 → 批处理(Spark) + Airflow ├── 小时级 → 微批(Spark Streaming) + Airflow ├── 分钟级 → 流处理(Flink) + Kafka └── 秒级 → 纯流(Flink) + Kafka + 缓存 数据类型? ├── 纯结构化 → 数据仓库 ├── 结构化+半结构化 → 湖仓一体 └── 全类型 → 数据湖 + 元数据管理