Netflix 数据工程师面试实录 2026:Spark Streaming + Kafka + 实时推荐数据管道
netflixdata-engineerinterviewsparkkafkapythonsystem-designstreaming

Netflix 数据工程师面试实录 2026:Spark Streaming + Kafka + 实时推荐数据管道

Netflix Data Engineer 面试真实经历:Spark Streaming、Kafka、实时数据处理、System Design 完整复盘。第一人称真实面经,含面试官对话与解题思路。

Sam · · 15 分钟阅读

公司: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_duration
  • titles: title_id, title_name, genre, release_year

请完成以下查询:

  1. 计算每个用户的每日观看时长
  2. 找出最受欢迎的 Top 10 标题(按总观看时长)
  3. 计算用户留存率(第 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 题目:实时事件处理

设计一个实时事件处理器,支持:

  1. 从 Kafka 消费观看事件
  2. 实时更新用户观看计数
  3. 支持滑动窗口聚合

我的解答:

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 分钟。

题目:设计实时推荐特征管道

设计一个实时特征管道,支持:

  1. 从 Kafka 消费用户行为事件
  2. 实时更新用户特征
  3. 支持推荐模型实时推理
  4. 端到端延迟 < 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])

面试官追问:

“如何保证特征的一致性和低延迟?”

我回答:

  1. Redis 集群:使用 Redis Cluster 分片,支持水平扩展
  2. Lua 脚本:原子性更新特征,避免竞态条件
  3. 异步写入:使用 Redis Pipeline 批量写入,减少网络往返
  4. 缓存策略:设置合理的 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 管道,支持:

  1. 每秒 100 万事件
  2. Exactly-once 语义
  3. 支持数据回溯和重放
  4. 多消费者组

我的设计:

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 分钟。

题目:设计推荐系统数据管道

设计一个推荐系统数据管道,支持:

  1. 实时特征更新
  2. 离线模型训练
  3. 实时推理
  4. 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
消息队列KafkaExactly-once,支持回溯
特征存储Redis低延迟,支持复杂数据结构
模型服务TensorFlow Serving高吞吐,支持版本管理
模型注册MLflow实验追踪,模型版本管理
A/B 测试LaunchDarkly灵活的分流,实时监控

VO Round 4:Behavioral / Culture Fit

最后一轮由 Hiring Manager 进行,60 分钟。

典型问题

Q1: Why Netflix?

我回答:

  1. 数据驱动文化:Netflix 是数据驱动决策的典范
  2. 技术挑战:处理 PB 级数据,亿级用户
  3. Freedom & Responsibility:高度自主的工作环境

Q2: Describe a time you had to make a trade-off between speed and quality.

我分享了一个实际案例:

  • Situation: 需要快速上线一个推荐功能,但数据质量有问题
  • Task: 在 1 周内上线,同时保证数据质量
  • Action:
    1. 先上线 MVP 版本,使用简单规则推荐
    2. 同时优化数据管道,提升数据质量
    3. 逐步切换到 ML 推荐模型
  • Result: 按时上线,后续迭代提升推荐准确率 30%

面试总结

成功经验

  1. 实时数据处理:Spark Streaming、Kafka 是核心技能
  2. 大规模系统:理解 Netflix 规模的数据处理挑战
  3. 推荐系统:特征工程、模型训练、A/B 测试
  4. 文化契合:Freedom & Responsibility 的文化价值观

面试注意事项

技术深度:Netflix 对实时数据处理的深度要求很高。

系统设计:考察的是完整的推荐系统数据管道设计能力。

文化契合:准备体现自主性和主人翁意识的故事。


推荐阅读

  • Spark Streaming 最佳实践 — Watermark、Windowing、Checkpoint
  • Kafka 架构设计 — 分区、副本、消费者组
  • 推荐系统数据管道 — 特征工程、模型训练、A/B 测试

💡 需要面试辅导?

如果你对准备技术面试感到迷茫,或者想要个性化的面试指导和简历优化,欢迎联系 Interview Coach Pro 获取一对一辅导服务。

👉 联系我们 获取专属面试准备方案

准备好拿下下一次面试了吗?

获取针对你的目标岗位和公司的个性化辅导方案。

联系我们