Netflix 数据工程师面试实录 2026:Spark Streaming + Kafka + 实时推荐数据管道
Netflix Data Engineer 面试真实经历:Spark Streaming、Kafka、实时数据处理、System Design 完整复盘。第一人称真实面经,含面试官对话与解题思路。
公司:Netflix 岗位:Data Engineer II 面试形式:Phone Screen + Virtual Onsite (4 轮) 结果:Pass → Offer
2026 年 8 月,我参加了 Netflix 的 Data Engineer 面试。Netflix 的面试风格非常独特——高度关注实时数据处理、大规模数据管道和推荐系统。面试官都是 Netflix 数据平台团队的核心成员,对 Spark Streaming、Kafka 和实时推荐系统的理解要求极高。
Netflix 的文化是 “Freedom & Responsibility”,面试过程中也能感受到这种文化——面试官给你充分的自由去设计解决方案,同时也期望你展现出高度的主人翁意识。
Phone Screen:SQL + Python
电话面由一位 Senior DE 进行,45 分钟。
SQL 题目:用户观看行为分析
给定以下表结构:
view_events: event_id, user_id, title_id, event_type, event_time, watch_durationtitles: title_id, title_name, genre, release_year请完成以下查询:
- 计算每个用户的每日观看时长
- 找出最受欢迎的 Top 10 标题(按总观看时长)
- 计算用户留存率(第 1 天观看,第 7 天是否回访)
我的解答:
-- 1. 每个用户的每日观看时长
SELECT
user_id,
DATE(event_time) AS watch_date,
SUM(watch_duration) AS total_watch_seconds,
COUNT(DISTINCT title_id) AS unique_titles_watched
FROM view_events
WHERE event_type = 'watch'
GROUP BY user_id, DATE(event_time)
ORDER BY watch_date DESC, total_watch_seconds DESC;
-- 2. 最受欢迎的 Top 10 标题
SELECT
t.title_name,
t.genre,
t.release_year,
COUNT(DISTINCT ve.user_id) AS unique_viewers,
SUM(ve.watch_duration) AS total_watch_seconds,
AVG(ve.watch_duration) AS avg_watch_duration
FROM view_events ve
JOIN titles t ON ve.title_id = t.title_id
WHERE ve.event_type = 'watch'
GROUP BY t.title_id, t.title_name, t.genre, t.release_year
ORDER BY total_watch_seconds DESC
LIMIT 10;
-- 3. 用户留存率(第 7 天回访率)
WITH first_day AS (
SELECT
user_id,
MIN(DATE(event_time)) AS first_watch_date
FROM view_events
WHERE event_type = 'watch'
GROUP BY user_id
),
retained AS (
SELECT DISTINCT
f.user_id,
f.first_watch_date
FROM first_day f
INNER JOIN view_events ve
ON f.user_id = ve.user_id
AND DATE(ve.event_time) = DATE_ADD(f.first_watch_date, INTERVAL 7 DAY)
AND ve.event_type = 'watch'
)
SELECT
COUNT(DISTINCT f.user_id) AS total_users,
COUNT(DISTINCT r.user_id) AS retained_users,
ROUND(COUNT(DISTINCT r.user_id) * 100.0 / COUNT(DISTINCT f.user_id), 2) AS retention_rate_7d
FROM first_day f
LEFT JOIN retained r ON f.user_id = r.user_id;
Python 题目:实时事件处理
设计一个实时事件处理器,支持:
- 从 Kafka 消费观看事件
- 实时更新用户观看计数
- 支持滑动窗口聚合
我的解答:
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.types import *
from pyspark.sql.window import Window
# 构建 Spark Session
spark = SparkSession.builder \
.appName("RealTimeWatchProcessor") \
.config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
.config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
.getOrCreate()
# Kafka 配置
kafka_bootstrap_servers = "kafka1:9092,kafka2:9092,kafka3:9092"
kafka_topic = "watch_events"
kafka_group_id = "watch-processor-group"
# 定义 Schema
event_schema = StructType([
StructField("event_id", StringType()),
StructField("user_id", StringType()),
StructField("title_id", StringType()),
StructField("event_type", StringType()),
StructField("event_time", StringType()),
StructField("watch_duration", IntegerType())
])
# 从 Kafka 读取流数据
kafka_df = (spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", kafka_bootstrap_servers)
.option("subscribe", kafka_topic)
.option("startingOffsets", "latest")
.option("checkpointLocation", "/data/checkpoints/watch_events/")
.load())
# 解析事件
events_df = (kafka_df
.select(from_col("value").cast("string").alias("value"))
.select(from_json(col("value"), event_schema).alias("data"))
.select("data.*")
.withColumn("event_timestamp", to_timestamp(col("event_time")))
.filter(col("event_type") == "watch"))
# 滑动窗口聚合(5 分钟窗口,每 1 分钟触发)
windowed_agg = (events_df
.withWatermark("event_timestamp", "10 minutes") # 允许 10 分钟延迟
.groupBy(
window(col("event_timestamp"), "5 minutes", "1 minute"),
col("user_id")
)
.agg(
count("*").alias("watch_count"),
sum(col("watch_duration")).alias("total_watch_seconds"),
count_distinct(col("title_id")).alias("unique_titles")
))
# 写入 Delta 表
query = (windowed_agg.writeStream
.format("delta")
.outputMode("update")
.option("checkpointLocation", "/data/checkpoints/watch_agg/")
.trigger(processingTime="1 minute")
.start("/data/delta/watch_aggregates/"))
# 监控查询状态
query.awaitTermination()
VO Round 1:Spark Streaming 深度
这一轮由一位 Streaming Platform 团队的 DE 进行,60 分钟。
题目:设计实时推荐特征管道
设计一个实时特征管道,支持:
- 从 Kafka 消费用户行为事件
- 实时更新用户特征
- 支持推荐模型实时推理
- 端到端延迟 < 100ms
我的架构设计:
┌─────────────────────────────────────────────────────────────────┐
│ Real-time Feature Pipeline │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Kafka │ │ Spark │ │ Feature Store │ │
│ │ (Events) │→ │ Streaming │→ │ (Redis/Bigtable) │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ Event Types: │ │ Features: │ │ Model Serving: │ │
│ │ - watch │ │ - recent │ │ - Real-time │ │
│ │ - like │ │ watches │ │ inference │ │
│ │ - dislike │ │ - genre │ │ - < 100ms latency │ │
│ │ - search │ │ preferences│ │ - High throughput │ │
│ │ - rating │ │ - session │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
特征计算代码:
from pyspark.sql import SparkSession
from pyspark.sql.functions import *
from pyspark.sql.window import Window
class RealTimeFeatureEngine:
"""实时特征引擎"""
def __init__(self, spark: SparkSession):
self.spark = spark
self.redis_client = RedisClient(host="redis-cluster", port=6379)
def compute_user_features(self, events_df):
"""计算用户实时特征"""
# 1. 最近观看的标题 ID 列表
recent_watches = (events_df
.filter(col("event_type") == "watch")
.withWatermark("event_timestamp", "1 hour")
.groupBy(
window(col("event_timestamp"), "30 minutes"),
col("user_id")
)
.agg(
collect_list(col("title_id")).alias("recent_titles"),
count("*").alias("recent_watch_count")
))
# 2. 类型偏好
genre_preferences = (events_df
.filter(col("event_type") == "watch")
.join(titles_df, on="title_id")
.withWatermark("event_timestamp", "1 hour")
.groupBy(
window(col("event_timestamp"), "1 hour"),
col("user_id"),
col("genre")
)
.agg(
sum(col("watch_duration")).alias("genre_watch_time")
))
# 3. 会话特征
session_features = (events_df
.withWatermark("event_timestamp", "30 minutes")
.withColumn("session_id",
F.session_window(col("event_timestamp"), "30 minutes"))
.groupBy(
col("user_id"),
col("session_id")
)
.agg(
count("*").alias("session_events"),
sum(col("watch_duration")).alias("session_duration"),
count_distinct(col("title_id")).alias("session_titles")
))
return recent_watches, genre_preferences, session_features
def write_to_feature_store(self, features_df):
"""写入特征存储(Redis)"""
def redis_write(row):
"""写入 Redis"""
user_id = row["user_id"]
features = {
"recent_titles": row["recent_titles"],
"watch_count": row["recent_watch_count"],
"updated_at": row["window_end"].isoformat()
}
self.redis_client.hset(
f"user:{user_id}:features",
mapping=features,
ex=3600 # 1 小时过期
)
# 使用 mapInPandas 或 foreachBatch 写入
(features_df.writeStream
.foreachBatch(self._batch_write_redis)
.outputMode("update")
.start())
def _batch_write_redis(self, df, batch_id):
"""批量写入 Redis"""
if df.count() > 0:
for row in df.collect():
self.redis_write(row)
# 使用示例
spark = SparkSession.builder.appName("FeatureEngine").getOrCreate()
engine = RealTimeFeatureEngine(spark)
events_df = (spark.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "kafka1:9092")
.option("subscribe", "user_events")
.load())
features = engine.compute_user_features(events_df)
engine.write_to_feature_store(features[0])
面试官追问:
“如何保证特征的一致性和低延迟?”
我回答:
- Redis 集群:使用 Redis Cluster 分片,支持水平扩展
- Lua 脚本:原子性更新特征,避免竞态条件
- 异步写入:使用 Redis Pipeline 批量写入,减少网络往返
- 缓存策略:设置合理的 TTL,定期清理过期特征
# Redis Lua 脚本 - 原子性更新
UPDATE_FEATURES_LUA = """
local user_key = KEYS[1]
local feature_key = ARGV[1]
local feature_value = ARGV[2]
local ttl = tonumber(ARGV[3])
redis.call('HSET', user_key, feature_key, feature_value)
redis.call('EXPIRE', user_key, ttl)
return 1
"""
# 使用 Pipeline 批量写入
pipe = redis_client.pipeline()
for user_id, features in batch.items():
pipe.execute_command(
'EVAL', UPDATE_FEATURES_LUA, 1,
f"user:{user_id}:features",
"recent_titles", json.dumps(features["recent_titles"]),
3600
)
pipe.execute()
VO Round 2:Kafka + 数据管道
这一轮由一位 Streaming Infrastructure 团队的 DE 进行,60 分钟。
题目:设计 Kafka 消息管道
设计一个 Kafka 管道,支持:
- 每秒 100 万事件
- Exactly-once 语义
- 支持数据回溯和重放
- 多消费者组
我的设计:
from confluent_kafka import Producer, Consumer, KafkaException
import json
import time
from typing import Dict, List
class NetflixEventProducer:
"""Netflix 事件生产者"""
def __init__(self, bootstrap_servers: str):
self.producer = Producer({
'bootstrap.servers': bootstrap_servers,
'acks': 'all', # 所有副本确认
'retries': 10, # 重试次数
'enable.idempotence': True, # 幂等性
'max.in.flight.requests.per.connection': 5,
'compression.type': 'lz4', # 压缩
'batch.size': 65536, # 64KB
'linger.ms': 10 # 等待 10ms 批量发送
})
self.delivery_report = {}
def deliver_report(self, err, msg):
"""发送报告回调"""
if err:
self.delivery_report[msg.key()] = f"Error: {err.str()}"
else:
self.delivery_report[msg.key()] = f"Sent to {msg.topic()} [{msg.partition()}] @ {msg.offset()}"
def send_event(self, topic: str, key: str, value: Dict):
"""发送事件"""
try:
self.producer.poll(0)
self.producer.produce(
topic=topic,
key=key.encode('utf-8'),
value=json.dumps(value).encode('utf-8'),
callback=self.deliver_report
)
except KafkaException as e:
raise e
def flush(self, timeout=10):
"""刷新缓冲区"""
self.producer.flush(timeout)
class NetflixEventConsumer:
"""Netflix 事件消费者"""
def __init__(self, bootstrap_servers: str, group_id: str, topics: List[str]):
self.consumer = Consumer({
'bootstrap.servers': bootstrap_servers,
'group.id': group_id,
'auto.offset.reset': 'earliest', # 从最早开始
'enable.auto.commit': False, # 手动提交
'max.poll.interval.ms': 300000,
'session.timeout.ms': 30000,
'heartbeat.interval.ms': 10000
})
self.consumer.subscribe(topics)
def poll_messages(self, timeout=1.0, max_messages=1000):
"""拉取消息"""
messages = []
msg = self.consumer.poll(timeout)
if msg is None:
return messages
if msg.error():
raise KafkaException(msg.error())
messages.append(msg)
# 拉取更多消息
while len(messages) < max_messages:
msg = self.consumer.poll(0.1)
if msg is None:
break
if msg.error():
raise KafkaException(msg.error())
messages.append(msg)
return messages
def commit(self):
"""提交偏移量"""
self.consumer.commit()
def close(self):
"""关闭消费者"""
self.consumer.close()
# 使用示例
producer = NetflixEventProducer("kafka1:9092,kafka2:9092,kafka3:9092")
consumer = NetflixEventConsumer(
bootstrap_servers="kafka1:9092,kafka2:9092,kafka3:9092",
group_id="feature-computation-group",
topics=["watch_events", "like_events", "search_events"]
)
# 发送事件
producer.send_event(
topic="watch_events",
key="user_123",
value={
"event_id": "evt_001",
"user_id": "user_123",
"title_id": "title_456",
"event_type": "watch",
"event_time": "2026-09-09T10:00:00Z",
"watch_duration": 300
}
)
# 消费事件
messages = consumer.poll_messages()
for msg in messages:
event = json.loads(msg.value().decode('utf-8'))
print(f"Received: {event}")
consumer.commit()
Kafka 架构:
┌─────────────────────────────────────────────────────────────────┐
│ Kafka Architecture │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Topics │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ watch_events │ │ like_events │ │ search_ │ │ │
│ │ │ (50 partitions)│ │ (30 partitions)│ │ events │ │ │
│ │ └──────────────┘ └──────────────┘ │ (20 partitions)│ │ │
│ │ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Consumer Groups │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Feature │ │ Analytics │ │ ML Training │ │ │
│ │ │ Computation │ │ Pipeline │ │ Pipeline │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
VO Round 3:System Design — 推荐数据管道
这一轮由一位 Principal Engineer 进行,60 分钟。
题目:设计推荐系统数据管道
设计一个推荐系统数据管道,支持:
- 实时特征更新
- 离线模型训练
- 实时推理
- A/B 测试
我的架构设计:
┌─────────────────────────────────────────────────────────────────┐
│ Netflix Recommendation Pipeline │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Data Collection │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Implicit │ │ Explicit │ │ Context │ │ │
│ │ │ Feedback │ │ Feedback │ │ Data │ │ │
│ │ │ (watch, skip)│ │ (like, rate) │ │ (time, device)│ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Feature Engineering │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ User Features│ │ Item Features│ │ Interaction │ │ │
│ │ │ - watch hist │ │ - genre │ │ Features │ │ │
│ │ │ - preferences│ │ - cast │ │ - co-watch │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Model Training │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Offline │ │ Online │ │ Model │ │ │
│ │ │ Training │ │ Learning │ │ Registry │ │ │
│ │ │ (daily) │ │ (real-time) │ │ (MLflow) │ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ Model Serving │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Real-time │ │ Batch │ │ A/B Testing │ │ │
│ │ │ Inference │ │ Inference │ │ Framework │ │ │
│ │ │ (< 100ms) │ │ (hourly) │ │ (LaunchDarkly)│ │ │
│ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
关键技术决策:
| 组件 | 选择 | 原因 |
|---|---|---|
| 流处理 | Spark Streaming | 高吞吐,与批处理统一 API |
| 消息队列 | Kafka | Exactly-once,支持回溯 |
| 特征存储 | Redis | 低延迟,支持复杂数据结构 |
| 模型服务 | TensorFlow Serving | 高吞吐,支持版本管理 |
| 模型注册 | MLflow | 实验追踪,模型版本管理 |
| A/B 测试 | LaunchDarkly | 灵活的分流,实时监控 |
VO Round 4:Behavioral / Culture Fit
最后一轮由 Hiring Manager 进行,60 分钟。
典型问题
Q1: Why Netflix?
我回答:
- 数据驱动文化:Netflix 是数据驱动决策的典范
- 技术挑战:处理 PB 级数据,亿级用户
- Freedom & Responsibility:高度自主的工作环境
Q2: Describe a time you had to make a trade-off between speed and quality.
我分享了一个实际案例:
- Situation: 需要快速上线一个推荐功能,但数据质量有问题
- Task: 在 1 周内上线,同时保证数据质量
- Action:
- 先上线 MVP 版本,使用简单规则推荐
- 同时优化数据管道,提升数据质量
- 逐步切换到 ML 推荐模型
- Result: 按时上线,后续迭代提升推荐准确率 30%
面试总结
成功经验
- 实时数据处理:Spark Streaming、Kafka 是核心技能
- 大规模系统:理解 Netflix 规模的数据处理挑战
- 推荐系统:特征工程、模型训练、A/B 测试
- 文化契合:Freedom & Responsibility 的文化价值观
面试注意事项
技术深度:Netflix 对实时数据处理的深度要求很高。
系统设计:考察的是完整的推荐系统数据管道设计能力。
文化契合:准备体现自主性和主人翁意识的故事。
推荐阅读
- Spark Streaming 最佳实践 — Watermark、Windowing、Checkpoint
- Kafka 架构设计 — 分区、副本、消费者组
- 推荐系统数据管道 — 特征工程、模型训练、A/B 测试
💡 需要面试辅导?
如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。
👉 联系我们 获取专属面试准备方案