☰
爱奇艺大数据开发秋招笔试题全解析:核心考点与答题策略
2026/11/1 6:51:46 网站建设 项目流程

上个月整理硬盘,翻出了一份2019年爱奇艺大数据开发方向秋招笔试题(A卷)的回忆版,是我考完当天凭记忆整理的,后来几个朋友也帮忙补过几题。坦率讲,这类真题回忆版不完全等同于官方原卷,但题型结构、考点覆盖和出题风格基本保留了下来。

现在回头看,这套题虽然是几年前的,但它的考察框架依然非常典型:Java并发与集合、Hadoop生态、Spark原理、Kafka消息链路、SQL和算法编程,几乎横跨大数据开发岗笔试的所有核心板块。对正在准备秋招的人来说,这套题的参考价值不只是"题目+答案",而是能帮你理解出题人到底在筛什么人、用什么样的题来筛。

我打算按板块逐题拆解,把每道题背后的知识点、容易踩的坑、以及答题时应该写到什么深度都说清楚。如果你正在准备大数据开发岗,建议静下心来读完整篇,遇到不懂的地方按着方向去补,比盲目刷几百道题有用得多。

1. 这场笔试的底层逻辑:出题人在筛什么样的人

1.1 技术栈分布与题型结构

爱奇艺做视频业务,每天产生海量的用户行为日志、播放记录、推荐特征数据,大数据团队的技术栈基本是业内主流的那一套:HDFS、MapReduce、YARN、Hive、Spark、Kafka,早期实时计算用Spark Streaming,后来逐渐向Flink迁移。

笔试A卷的题型结构大体如下:

题型题量占比考察方向建议用时
基础选择题约30%Java、JVM、并发、Linux20分钟
简答题约20%Hadoop、Spark原理25分钟
SQL与编程题约30%算法、SQL窗口函数、手写代码35分钟
综合设计题约20%Kafka、实时链路架构设计20分钟

可以看出,这不是一套纯考背诵的卷子。选择题考的是你有没有认真读过源码和官方文档,简答题考的是你能不能把原理讲清楚,编程和SQL题则直接检验编码基本功,综合题则是为了筛掉那些只写过Demo、没真正理解分布式系统的人。

1.2 筛选标准背后的业务场景

为什么出题人这么出?可以结合爱奇艺的典型业务场景来看。

比如视频播放日志,一天就有几十亿条,这些日志要从业务服务器采集上来,经过Kafka削峰,再落地到HDFS或者进入实时计算引擎。这个链路里,任何一环出问题,都会直接影响推荐效果和运营报表。所以笔试中必须考察你对HDFS读写机制、MapReduce数据处理流程、Kafka可靠性、Spark调优这些核心环节的理解。

换句话说,这套题筛的不是"会不会调API",而是"把你扔进一个真实的分布式集群环境里,你能不能判断问题出在哪、能不能设计出合理的方案"。后面的所有题目,都是围绕这个目的展开的。

2. Java并发与集合:最容易被低估的送分题

2.1 HashMap从链表到红黑树:一道题考出你的底层功底

笔试中有一道很经典的选择题,改了几个选项,核心考的是同一个点:JDK1.8中HashMap底层用什么结构解决Hash冲突?链表长度达到什么条件会转红黑树?

标准答案都知道,底层是数组加链表加红黑树,链表长度达到8时会尝试转红黑树。但这里有个关键限制条件:链表长度达到8,且当前数组长度不小于64,才会真正转红黑树。如果数组长度还不到64,HashMap会优先选择扩容,而不是转树。

我当时认识一个同学,这道题就栽了:他只记住了阈值是8,题目里把条件改成"只要链表长度达到8就转红黑树",他直接选了正确。实际上这是个错误选项,因为数组长度不足64时,扩容才是首选。

顺带说一下为什么是8这个数字。源码注释里有解释,基于泊松分布,在负载因子0.75、随机哈希函数的前提下,链表长度达到8的概率大约是千万分之六。也就是说,正常业务数据几乎不可能触发这个长度,真到了8,基本可以断定有人在恶意构造哈希碰撞。红黑树的存在主要是为了防这种攻击,而不是为了优化正常场景。

答题时如果能写出"链表长度达到8且数组长度不小于64"这个完整条件,再补充一句"这是为了在哈希碰撞极端情况下保证查询复杂度从O(n)降为O(logn)",这一题的深度立刻就不一样了。

2.2 线程池执行流程:从笔试答案到生产配置

线程池的考察几乎从不缺席,而且出题方式很固定:给出corePoolSize、maximumPoolSize、workQueue这几个参数,让你描述一个任务提交之后,线程池内部到底干了什么。

完整的执行流程是下面这样的:

  1. 当前线程数小于corePoolSize时,新建核心线程执行任务。
  2. 线程数达到corePoolSize后,新任务被放入工作队列排队。
  3. 工作队列满了,且线程数小于maximumPoolSize,创建非核心线程执行任务。
  4. 队列满了且线程数已经达到maximumPoolSize,触发拒绝策略。

这里最容易搞混的是第2步和第3步的顺序。很多人以为队列满了才会创建线程,这没错,但更准确的说法是:核心线程满了之后,先尝试往队列里塞,塞不进去了才创建新线程。这个顺序决定了线程池的"先排队、后加人"逻辑,和很多人的直觉相反。

关于拒绝策略,常见的四种:AbortPolicy直接抛异常、CallerRunsPolicy让提交任务的线程自己跑、DiscardPolicy静默丢弃、DiscardOldestPolicy丢弃队列里最老的任务。笔试如果问生产环境选哪个,我一般推荐CallerRunsPolicy,它能让生产者感知到压力,起到天然的背压效果。

生产环境配线程池时,参数怎么定?CPU密集型任务,核心线程数建议设为CPU核数加1,因为CPU密集型线程基本不阻塞,多了反而频繁切换。IO密集型任务,核心线程数可以设为CPU核数的两倍左右,因为线程大部分时间在等IO,需要更多线程来占满CPU。如果你拿不准,可以先用压测工具测,再根据耗时和吞吐量调整。

2.3 JVM与GC的小题不小

选择题里通常还会有一道JVM相关的,最常见的是Java 8中哪个区域被移除了。答案是永久代PermGen,被元空间Metaspace取代。

这道题本身不难,但很多人只记住了答案,没理解背后的原因。永久代的大小是有限的,由-XX:MaxPermSize控制,而且它属于JVM堆内存的一部分。字符串常量池和类元数据都放在这里,很容易因为类加载过多导致OOM。Java 8把元空间挪到了本地内存,默认情况下只受物理内存限制,不再那么容易撑爆。

如果笔试里出到GC算法相关题,记住几个关键结论就够了:新生代用复制算法,因为"朝生夕死"的对象多,复制成本低;老年代用标记清理或标记整理,因为对象存活率高,复制不划算;CMS和G1的区别在于,CMS主打低停顿但会产生内存碎片,G1则把堆划分成Region,可预测停顿时间。回答时能带上"为什么",就比死记硬背高一个层次。

3. Hadoop生态三件套:MapReduce、HDFS与YARN的隐藏考点

3.1 WordCount不是白写的:Shuffle全过程拆解

MapReduce的简答题里,经典提问方式是:介绍WordCount的完整执行流程,或者直接问Shuffle过程中Map端和Reduce端各自做了什么。

如果只答"Map输出后经过Shuffle传给Reduce",基本拿不到分。要答得完整,需要拆成Map端和Reduce端两条线。

Map端的Shuffle流程是这样的:map函数输出结果先写入环形缓冲区,默认大小100MB,达到80%阈值时触发溢写。溢写过程中会做分区、排序,如果设置了Combiner,还会在溢写前做一次本地聚合,减少写入磁盘的数据量。溢写会产生多个小文件,最后会合并成一个大的溢出文件,同时做一次归并排序。

Reduce端的流程是:先从Map端拉取属于自己分区的数据,拉取方式有HTTP请求,拉到本地后做合并和归并排序,然后按照Key分组,把同一Key的所有Value交给reduce函数处理。

很多同学容易漏掉几个细节:环形缓冲区默认100MB、溢写阈值80%、Map端有Combiner可选、Reduce端拉取数据是走HTTP。这些细节才是考官判断你是否真正写过MapReduce作业、是否读过源码的依据。

顺带说一下MapReduce为什么慢:整个Shuffle过程中,数据要多次落盘、多次序列化和反序列化,还要进行排序。磁盘IO和网络传输是最大瓶颈。所以后来Spark才提出"基于内存的计算"这个卖点,把中间结果尽量留在内存里。

3.2 HDFS读写链路与副本放置策略

HDFS的题目通常是这样的:描述HDFS读文件的过程,或者问副本放置策略是什么。

读流程回答要点是:客户端调用FileSystem.open方法,向NameNode发起RPC请求,获取文件的数据块信息。NameNode返回每个块对应的DataNode地址列表,客户端根据网络拓扑距离选择最近的DataNode读取数据,边读边校验,防止读到损坏的块。

写流程稍微复杂一点。客户端向NameNode发起创建文件请求,NameNode检查权限和路径后创建文件元数据。客户端开始写第一个块,先写入本地临时文件,然后从NameNode获取一个DataNode列表,构建pipeline。数据按"客户端->第一个DataNode->第二个DataNode->第三个DataNode"的管道方式依次传递,每个DataNode写完会向上游返回ack,最后一个DataNode写完会通知客户端。

副本放置策略也是高频考点:第一个副本放在客户端所在节点,第二个副本放在同机架的不同节点,第三个副本放在不同机架。这样兼顾了写入性能(第一个副本本地写)、容错性(跨机架)和读取性能(同机架内有两个副本)。

答题时如果能补充一句"NameNode只存元数据、不存实际数据,所有数据读写都走DataNode",会让考官觉得你对架构有整体认知。

3.3 YARN资源调度题怎么答才不丢分

YARN的考察通常和作业提交流程绑定,比如:一个MapReduce作业提交到YARN后,完整执行流程是什么?

标准的回答链路是:

  1. 客户端向ResourceManager提交作业。
  2. RM为作业分配一个容器Container,并在该容器中启动ApplicationMaster。
  3. AM启动后,向RM注册自己,并向RM申请运行Map和Reduce任务的资源。
  4. RM返回可用节点列表,AM与对应的NodeManager协商,在Container中启动Task。
  5. 所有任务执行完成后,AM向RM注销并释放资源。

这里有个容易忽略的点:ApplicationMaster是作业级别的,MapReduce、Spark on YARN都是先启动AM,再由AM去申请资源。AM本身就是一个Container,它负责这个作业的生命周期管理。

如果题目进一步问调度器区别,答三点:FIFO先来先服务,简单但容易队头阻塞;Capacity Scheduler按队列分配资源,适合多租户;Fair Scheduler按公平策略动态分配资源,适合资源利用率要求高的场景。爱奇艺这种大集群一般用Capacity Scheduler,因为它能保证不同业务线之间的资源隔离。

4. Spark核心:选择题考概念,大题考调优

4.1 宽窄依赖判断题:别把union记成宽依赖

Spark选择题里必有一道宽窄依赖判断,常见选项包括map、filter、groupByKey、union。

宽依赖和窄依赖的划分标准是:子RDD的每个分区依赖父RDD的哪些分区。如果只依赖父RDD的固定一个分区,就是窄依赖,比如map、filter、flatMap、union;如果依赖父RDD的多个分区,就是宽依赖,比如groupByKey、reduceByKey、join。

最容易错的是union。union只是把多个RDD拼接在一起,子RDD的每个分区仍然只依赖父RDD中对应的一个分区,所以它是窄依赖,不是宽依赖。很多人看到union觉得是"多个合并成一个",想当然就选了宽依赖,这是典型的凭感觉答题。

为什么Spark要区分宽窄依赖?两个原因。第一个是Stage划分:宽依赖是划分Stage的边界,遇到宽依赖就必须Shuffle,所以要把Stage切在这里。第二个是故障恢复:窄依赖的某个分区丢失,只需要重新计算对应的父分区;宽依赖的某个分区丢失,可能要重新计算父RDD的多个分区,恢复成本高得多。

4.2 Spark作业提交到执行的完整链路

Spark作业执行流程是简答题里的常客。答题时按下面的链路展开,基本不会漏点:

  1. 通过spark-submit提交应用,启动Driver进程。
  2. Driver中构建SparkContext,SparkContext向ClusterManager申请资源。在YARN模式下,会先在集群中启动ApplicationMaster。
  3. AM向ResourceManager申请Container,用来启动Executor进程。
  4. Executor启动后反向注册到SparkContext。
  5. SparkContext根据业务代码构建RDD依赖关系,生成DAG。
  6. DAGScheduler按照宽依赖把DAG切分成多个Stage,每个Stage包含一组可以并行执行的Task。
  7. TaskScheduler把Task下发到Executor上执行。
  8. Executor执行Task,结果返回给Driver,或者直接写入外部存储系统。

这里有两个容易漏的点:一是DAGScheduler切分Stage的依据是宽依赖,二是Executor是在Driver注册之后才执行具体任务的。如果你能补充一句"Stage内部可以pipeline执行,多个窄依赖算子可以在一个Task里连续计算",会显得你对执行模型有更深的理解。

另外还常问cache和checkpoint的区别。一句话总结:cache只是把数据留在内存或磁盘,不切断血缘;checkpoint会切断血缘,把数据写到可靠存储如HDFS。所以用checkpoint时要注意,它会把RDD的血缘彻底断掉,后面如果缓存被清了,只能从checkpoint目录重新加载。

4.3 数据倾斜:各大厂大数据笔试的常青树

数据倾斜几乎是大数据开发笔试简答题里出现频率最高的问题,没有之一。爱奇艺这道题的问法是:Spark作业中遇到数据倾斜,你会怎么排查和解决。

答题时先讲排查手段:在Spark UI里看各个Task的耗时和读写数据量,正常情况下Task之间应该比较均衡,如果某个Task处理的数据量远超其他Task,基本可以确定是数据倾斜。

然后是定位原因:最常见的三种,一是Key本身分布不均匀,比如某个热点Key的数据量特别大;二是分组聚合类操作,groupByKey或reduceByKey时大量数据落到同一个Key上;三是Join操作中有一张表的Key大量重复。

解决方案按场景给:

  1. 如果倾斜Key可以过滤:直接把异常Key过滤掉再计算。
  2. 如果是大表Join小表:用Broadcast Join,把小表广播到每个Executor,避免Shuffle。
  3. 如果是聚合类倾斜:用两阶段聚合。先给每个Key加一个随机前缀,让它拆分成多个子Key,先做一次局部聚合,再去掉前缀做第二次全局聚合。相当于把压力分散到多个Task。
  4. 提高Shuffle并行度:调整spark.sql.shuffle.partitions或spark.default.parallelism,让每个Task处理的数据量变小。这个方法最简单,但不一定能根治问题。

答题时最好把两阶段聚合的逻辑说清楚,因为这是最能体现实战经验的点。考官一听你是真的处理过倾斜,不是只会背方案,分数自然不一样。

5. 消息队列与实时链路:区分"会用"和"懂原理"

5.1 Kafka可靠性三件套:acks、重试与最小副本

Kafka的题目通常围绕消息可靠性展开,常见的问法是:如何保证Kafka消息不丢失、不重复?

要答好这道题,需要从生产端、消费端、Broker端三个层面分别说明。

生产端保证不丢:设置acks=all,表示所有ISR副本都写入成功才返回成功;设置retries大于0,并在网络抖动时允许重试;开启幂等性enable.idempotence=true,防止重试导致消息重复写入。

Broker端保证不丢:设置min.insync.replicas=2,表示至少两个副本同步成功才认为写入成功;关闭unclean.leader.election.enable,避免非同步副本被选为Leader导致数据丢失。

消费端保证不丢:核心是关闭自动提交enable.auto.commit=false,改成手动提交offset,并且一定要在业务逻辑处理完成之后再提交。如果先提交offset再处理业务,消费者挂了就会丢数据。

至于不重复消费,坦白说Kafka的语义是at least once,也就是至少一次,生产端重试和消费端重平衡都可能导致同一批消息被消费多次。所以真正的解法是在业务侧做幂等,比如写入数据库时用唯一索引去重,或者用Redis记录已处理的消息ID。这部分答出来,说明你理解消息系统的边界,而不是幻想一个配置就能同时解决不丢和不重。

5.2 实时推荐链路设计题的答题套路

综合题常给一个场景,比如:设计一个实时统计视频播放量的系统,要求每天处理几十亿条日志,延迟在分钟级,你会怎么设计架构。

这种题没有标准答案,但答题框架是固定的,按数据流向逐层说就行。

第一层是日志采集:视频播放日志通过Nginx或业务服务打印,由Flume或Logstash采集。注意采集端要有缓冲机制,防止业务高峰把采集进程打爆。

第二层是消息队列:日志进入Kafka,主题按业务线划分,比如播放主题、点击主题。Kafka在这里的核心作用是削峰填谷和解耦,下游计算引擎挂掉时,Kafka可以帮忙把数据暂存起来,不会丢。

第三层是实时计算:用Spark Streaming或Flink消费Kafka数据,按分钟窗口聚合播放量、完播率等指标。如果用Flink,要开启Checkpoint,保证故障恢复时不丢数据。

第四层是存储和展示:聚合结果写入Redis供实时查询,或者写入ES供多维分析,再通过报表平台展示。

答题时如果能结合选型理由会更好,比如为什么用Flink不用Spark Streaming:因为Flink是真正的流式计算,延迟更低,且支持精确一次语义。这种"每选一个组件都能说出为什么"的能力,才是综合设计题真正考察的东西。

6. 编程与SQL题:送分题和压轴题并存

6.1 单例模式:双重检查锁为什么必须加volatile

手写代码题里常有一道实现单例模式,而且要求线程安全、懒加载。最经典的写法是双重检查锁,代码如下:

public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }

这道题的关键不是能不能写出来,而是能不能解释清楚volatile的作用。

new Singleton()这行代码在JVM层面不是原子操作,它分三步:在堆上分配内存、执行构造函数初始化对象、把引用指向这块内存。在没有volatile的情况下,第三步可能先于第二步执行,也就是引用指向了一个还没有初始化完成的内存区域。此时另一个线程进来判断instance不为null,直接返回,拿到的是一个半初始化的对象,再用就会出问题。

volatile的作用是禁止这两行指令重排序,保证引用赋值一定发生在对象初始化完成之后。外层判空是为了避免不必要的加锁开销,内层判空是为了防止多个线程同时进入同步块时重复创建实例。把这几点写在答案旁边,这一题满分。

当然也可以用静态内部类实现,逻辑更简单:利用类加载机制保证线程安全,但笔试时DCL是更主流的考点,建议还是先把这个写法吃透。

6.2 Top K问题:堆与快速选择的取舍

另一道经典算法题是从10亿个整数中找出最大的100个。这题一眼看上去数据量大,其实关键就在"100"这个K值很小。

最优解是维护一个大小为100的最小堆:遍历整个数组,如果堆没满就直接放入,如果当前元素比堆顶大,就弹出堆顶再放入当前元素。遍历结束后,堆里就是最大的100个数。时间复杂度是O(nlogK),这里K是100,所以logK是常数级别,相当于O(n)。空间复杂度是O(K),非常小。

PriorityQueue<Integer> heap = new PriorityQueue<>(100); for (int num : nums) { if (heap.size() < 100) { heap.offer(num); } else if (num > heap.peek()) { heap.poll(); heap.offer(num); } }

顺便提一下另一种解法:快速选择算法,平均时间复杂度O(n),但它有一个问题:会修改原数组顺序,而且如果要输出有序的Top K,还要再排一次序。更重要的是,如果数据是流式不断进来的,快速选择就没法用了,而堆天然支持增量计算。所以在大数据场景下,堆是更实用的方案。

答题时能说出"10亿个数不需要全部加载到内存,一次只处理一个数,空间只需要维护K个元素",这道题就算答到位了。如果面试官追问"进程内存不够怎么办",可以补充外部排序或MapReduce的分布式思路。

6.3 连续登录SQL:窗口函数的标准解法

SQL题里频率最高的一类是连续性问题,比如:给定用户登录表user_login(user_id, login_date),找出连续登录3天及以上的用户。

标准解法是先用窗口函数给每个用户按登录日期编号,再用登录日期减去编号,得到一个新的日期字段。同一用户连续登录时,这个新字段是相同的。

SELECT user_id, grp_date, COUNT(*) AS cnt FROM ( SELECT user_id, login_date, DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp_date FROM user_login ) t GROUP BY user_id, grp_date HAVING COUNT(*) >= 3;

核心逻辑是:如果用户连续登录,比如3月1日、3月2日、3月3日,分别对应row_number的1、2、3,日期减去编号得到2月28日、2月28日、2月28日,完全一样。一旦中间断了,这个差值就会变化,从而被分到不同的组。

写这道题有几个坑需要注意:login_date要去重,因为同一天可能有多条登录记录,窗口函数会重复计数,导致连续天数被算多。处理方式是对(user_id, login_date)先distinct,或者用dense_rank代替row_number,但最稳妥的还是先做去重。另外DATE_SUB函数在不同数据库里写法可能不同,MySQL里是DATE_SUB,Hive里可以直接用date_sub,笔试时注意看题目指定的环境。

7. 丢分点复盘与笔试实战建议

7.1 这几个坑我当年亲眼见过别人踩

整理这套题的时候,我把自己和身边人考完后的复盘汇总了一下,列出出现频率最高的丢分点,大家可以对照检查:

考点常见错误正确理解
HashMap转红黑树只记得长度达到8需要长度达到8且数组长度不小于64
线程池执行顺序以为队列满才创建核心线程核心线程满后先入队,队满再创建非核心线程
宽窄依赖把union当成宽依赖union的每个分区只依赖父RDD对应分区,属于窄依赖
MapReduce Shuffle漏掉溢写和排序步骤Map端有环形缓冲区溢写,Reduce端有拉取和归并排序
Kafka可靠性只会说"设置acks=all"需要分生产端、Broker端、消费端三层说明
连续登录SQL忘记去重,导致连续天数虚高先对登录日期去重,再做窗口计算

另外还有一个非技术层面的坑:简答题只写关键词不写过程。比如问Shuffle流程,有人只写"排序、合并、拉取"几个词,甚至不分Map端和Reduce端。笔试是按点给分的,你不展开写,考官就算想给分也找不到依据。

7.2 时间分配与答题顺序

时间分配是很多人忽略的隐形丢分点。这套题总分100分,SQL和编程题占了30分以上,但它也是最耗时的部分。我的建议是拿到卷子先花两分钟通读一遍,然后按"SQL编程题优先、简答题其次、选择题最后"的顺序来做。

为什么先做SQL和编程?因为这类题分值大、区分度高,而且思路一旦断了很难再捡起来,放在后面容易因为时间不够而被迫放弃。选择题反而不是那么着急,很多基础选项一眼就能判断,拿不准的先跳过,做完大题再回来推敲,往往能靠排除法选出正确答案。

简答题答题时也要控制篇幅。每道简答题写5到8行即可,把核心步骤列清楚,用"第一步、第二步"的方式组织,比写一大段流水账强得多。阅卷的人最怕看到满篇文字但要自己从中找得分点。

我自己当年考试有一个习惯:遇到不会的大题,先把脑子里能想到的关键词和公式写上去,再逐步组织语言。因为笔试阅卷是抓要点给分,写了不一定得分,但不写一定是零分。这个习惯帮我救回过不少题。

最后聊几句

整理这篇拆解的时候,我自己也重新过了一遍当年的知识点。说实话,这类笔试真正考的不是你背了多少八股,而是你能否把一个知识点放到真实场景里讲清楚。HashMap为什么转红黑树、数据倾斜怎么解决、Kafka怎么保证可靠性,这些问题背后全是真实业务里会遇到的麻烦事。

如果你正在准备大数据开发的秋招,我的建议是别只刷题,而是把每一个考点往底层原理和业务场景追问一层。看到HashMap就想想并发场景下它为什么会丢数据,看到Shuffle就想想数据倾斜时它会放大什么问题,看到Kafka就想想如果你的下游挂了它到底靠什么兜底。把知识点串成一条线,笔试和面试都会顺手很多。

这套题里还有几个方向没有完全展开,比如Linux常用命令、HiveQL优化,如果大家需要,我可以再单独写一篇。祝各位秋招顺利。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询