数据工程实战课程 · ETL基础阶段
数据工程是构建数据基础设施的工程学科,负责数据的采集、存储、处理和交付,使数据能够在组织中高效流动并被有效利用。
数据工程涉及设计、构建和维护数据管道与基础设施,确保数据从源头到消费端的可靠流转。
import time, json, hashlib, random
from datetime import datetime
from collections import defaultdict
from dataclasses import dataclass
@dataclass
class DataSource:
name: str
source_type: str
schema: dict
record_count: int = 0
def generate_record(self):
record = {}
for field_name, field_type in self.schema.items():
if field_type == 'int': record[field_name] = random.randint(1, 1000)
elif field_type == 'float': record[field_name] = round(random.uniform(0, 100), 2)
elif field_type == 'str': record[field_name] = f"{field_name}_{random.randint(1000,9999)}"
elif field_type == 'timestamp': record[field_name] = datetime.now().isoformat()
self.record_count += 1
return record
class DataIngestion:
def __init__(self):
self.buffer = []
self.cdc_log = []
self.stats = defaultdict(lambda: {'batch': 0, 'cdc': 0})
def batch_ingest(self, source, count):
records = [source.generate_record() for _ in range(count)]
self.buffer.extend(records)
self.stats[source.name]['batch'] += count
return records
def cdc_ingest(self, source):
changes = []
for _ in range(random.randint(1, 5)):
record = source.generate_record()
change = {'operation': random.choice(['INSERT','UPDATE','DELETE']),
'source': source.name, 'after': record, 'timestamp': datetime.now().isoformat()}
changes.append(change)
self.cdc_log.append(change)
self.stats[source.name]['cdc'] += len(changes)
return changes
class DataTransformer:
def __init__(self): self.transform_log = []
def clean(self, records, rules):
cleaned, removed = [], 0
for r in records:
skip = False
for field, rule in rules.items():
if field in r:
val = r[field]
if rule.get('not_null') and val is None: skip = True; removed += 1; break
if rule.get('min') is not None and isinstance(val,(int,float)) and val < rule['min']: skip = True; removed += 1; break
if not skip: cleaned.append(r)
self.transform_log.append(f"清洗: {len(records)}→{len(cleaned)} (移除{removed})")
return cleaned
def normalize(self, records, field, method='minmax'):
values = [r[field] for r in records if field in r and isinstance(r[field],(int,float))]
if not values: return records
min_v, max_v = min(values), max(values)
result = []
for r in records:
r = r.copy()
if field in r and isinstance(r[field],(int,float)) and max_v > min_v:
r[f'{field}_norm'] = round((r[field]-min_v)/(max_v-min_v), 4)
result.append(r)
return result
def aggregate(self, records, group_by, agg_field, agg_fn='sum'):
groups = defaultdict(list)
for r in records:
if group_by in r and agg_field in r: groups[r[group_by]].append(r[agg_field])
result = {}
for key, vals in groups.items():
if agg_fn == 'sum': result[key] = sum(vals)
elif agg_fn == 'avg': result[key] = round(sum(vals)/len(vals),2)
elif agg_fn == 'count': result[key] = len(vals)
return result
class DataLake:
def __init__(self, name):
self.name = name
self.zones = {'raw':[],'cleaned':[],'curated':[]}
def write(self, zone, records, table_name):
entry = {'table':table_name,'records':records,'count':len(records),
'created_at':datetime.now().isoformat(),
'checksum':hashlib.md5(json.dumps(records,default=str).encode()).hexdigest()[:8]}
self.zones[zone].append(entry)
return entry
def stats(self):
return {zone:{'tables':len(e),'total_records':sum(x['count'] for x in e)}
for zone,e in self.zones.items()}
print("=" * 60)
print("数据工程生态系统模拟")
print("=" * 60)
sources = {
'orders': DataSource('orders','database',{'order_id':'int','amount':'float','customer':'str','ts':'timestamp'}),
'clicks': DataSource('clicks','log',{'session_id':'str','page':'str','duration':'float','ts':'timestamp'}),
}
print("数据源:", list(sources.keys()))
ingestion = DataIngestion()
batch = ingestion.batch_ingest(sources['orders'], 100)
print(f"批量采集: {len(batch)}条")
cdc = ingestion.cdc_ingest(sources['orders'])
print(f"CDC采集: {len(cdc)}事件")
transformer = DataTransformer()
cleaned = transformer.clean(batch, {'amount':{'not_null':True,'min':0}})
normalized = transformer.normalize(cleaned, 'amount')
agg = transformer.aggregate(cleaned, 'customer', 'amount', 'sum')
print(f"清洗: {len(batch)}→{len(cleaned)}, 聚合: {len(agg)}组")
lake = DataLake("demo")
lake.write('raw', batch, 'raw_orders')
lake.write('cleaned', cleaned, 'clean_orders')
print(f"数据湖: {lake.stats()}")
print("✅ 验证通过 - 生态系统模型运行正常")
| 层次 | 技能领域 | 关键工具 | 重要度 |
|---|---|---|---|
| L1 | 编程语言 | Python, SQL, Scala, Java | ⭐⭐⭐⭐⭐ |
| L2 | 关系型数据库 | PostgreSQL, MySQL | ⭐⭐⭐⭐⭐ |
| L2 | 数据仓库 | Snowflake, BigQuery | ⭐⭐⭐⭐ |
| L3 | 批处理 | Spark, MapReduce | ⭐⭐⭐⭐ |
| L3 | 流处理 | Kafka, Flink | ⭐⭐⭐⭐ |
| L4 | 工作流 | Airflow, Dagster | ⭐⭐⭐⭐⭐ |
| L5 | 数据质量 | Great Expectations | ⭐⭐⭐ |
import time, random
from collections import defaultdict
def benchmark(sizes, fn):
results = {}
for size in sizes:
data = list(range(size)); random.shuffle(data)
start = time.perf_counter()
result = fn(data)
elapsed = time.perf_counter() - start
results[size] = {'ms': round(elapsed*1000,2), 'tps': round(size/elapsed,0)}
return results
sizes = [1000, 5000, 10000, 50000, 100000]
print("排序性能:")
for size, m in benchmark(sizes, sorted).items():
print(f" {size:>8,}条: {m['ms']:>8.2f}ms, {m['tps']:>10,.0f}条/秒")
def chunked_sort(data, chunk_size=10000):
chunks = [sorted(data[i:i+chunk_size]) for i in range(0,len(data),chunk_size)]
result = []
for c in chunks: result.extend(c)
return result
print("分块排序:")
for size, m in benchmark(sizes, chunked_sort).items():
print(f" {size:>8,}条: {m['ms']:>8.2f}ms, {m['tps']:>10,.0f}条/秒")
print("✅ 验证通过 - 性能分析完成")
| 年代 | 里程碑 | 影响 |
|---|---|---|
| 1990s | 数据仓库诞生 | Bill Inmon提出数仓理论 |
| 2004 | MapReduce论文 | Google开启大数据时代 |
| 2010 | Spark诞生 | 内存计算,比MR快100倍 |
| 2018 | dbt诞生 | ELT替代ETL |
| 2020 | 湖仓一体 | Lakehouse融合数仓与数据湖 |
🎁 下一课预告:深入ETL管道设计!
| 优化项 | 方法 | 预期效果 |
|---|---|---|
| I/O优化 | 批量读写、列式存储、压缩 | 3-10x |
| 计算优化 | 向量化执行、谓词下推、列裁剪 | 2-5x |
| 并行化 | 分区并行、流水线并行 | 线性扩展 |
| 缓存 | 维度表缓存、结果缓存 | 2-10x |
| 增量处理 | 只处理变更数据 | 10-100x |