字节跳动大数据笔试复盘:Hadoop、Spark与数仓核心考点解析

发布时间:2026/8/29 13:30:19
字节跳动大数据笔试复盘:Hadoop、Spark与数仓核心考点解析 2018年秋招字节跳动的大数据岗位笔试在牛客上讨论度一直很高。那会儿今日头条还没全面改名但招人力度已经非常猛了尤其是大数据方向据说要支撑推荐、广告、内容审核等一堆业务线。我当年也参加了第四批笔试说实话考完第一感受是这套题不像是一份笔试题更像是一张大数据知识地图——从Hadoop生态到Spark原理、从SQL到算法、从架构设计到业务落地全部串起来考了一遍。这篇文章不是官方题解而是我以一个过来人的身份把第四批大数据方向笔试涉及的考点、答题思路、以及我后来复盘时整理的经验完整拆解一遍。不管你是正在准备大数据校招、社招还是想系统梳理大数据核心知识这份梳理都有参考价值。尤其是那些经常被一笔带过的为什么——比如为什么Spark比MapReduce快、为什么数仓分层要这么分、数据倾斜到底怎么定位我都会结合当年的题目和自己的实操经验详细说。1. 这是一场什么样的笔试先摸清出题逻辑1.1 笔试整体结构与考察思路2018年字节跳动大数据方向第四批笔试在线答题题型大致分为选择题、编程题和大题。选择题覆盖Java基础、数据结构、计算机网络、概率统计这部分看上去像是在考通用计算机基础但实际上很多题都偷偷往分布式方向靠。编程题一般是2到3道其中必有一道SQL或数据处理题另外一到两道是算法题。最后还有一到两道系统设计或场景分析题考察你对大数据链路的整体理解。我当时拿到试卷的第一反应是题目跨度真大。从Java的HashMap原理到HDFS的写入流程从概率题里的贝叶斯公式到Spark的宽窄依赖几乎每个主流大数据组件都被扫了一遍。后来复盘才琢磨明白头条的面试官和出题人想找的并不是背了很多面试题的人而是真正在项目里摸过数据的人。因为单纯背知识点的人遇到你设计的数仓为什么这么分层这个任务数据倾斜了你怎么办这种问题很难编出有说服力的答案。1.2 大数据岗位的选人标准字节跳动当年的大数据岗位官方名称叫大数据开发工程师但实际工作内容非常宽泛——有人做数据仓库有人做实时计算有人做数据平台还有人做数据挖掘的基础支持。所以笔试环节考察的是综合能力更准确地说是从数据源到数据消费这条完整链路的掌握程度。换句话说他们不期待你每个组件都精通但希望你有一个完整的知识框架。比如HDFS、MapReduce、Hive、Spark、Kafka、HBase、Flume、ZooKeeper这些组件你至少都听过、知道各自解决什么问题、有哪些优缺点、在一条链路里怎么配合。这种考察思路其实比单纯考深度更难准备因为你需要理解组件之间的关系而不只是单个组件的API。我在复盘时给自己画了一条知识链路数据采集Flume、Kafka→ 数据存储HDFS、HBase→ 数据处理MapReduce、Spark、Flink→ 数据查询Hive、Presto→ 数据应用BI报表、推荐系统、算法特征。笔试里的大部分题目几乎都能在这条链路上找到位置。所以准备这场笔试的第一步不是刷题而是先把这个链路搭起来。2. 大数据核心组件考点拆解Hadoop生态、Spark、数仓与消息队列2.1 Hadoop生态从HDFS写入流程到MapReduce的容错机制第四批笔试里Hadoop生态的题目占比不小。选择题里有一道是问HDFS客户端写入一个文件时数据流是如何流动的选项里涉及DataNode之间的复制管道、ack应答机制。这道题的完整流程是客户端先向NameNode发起写请求NameNode检查权限和配额后返回可以写入的DataNode列表然后客户端将数据分块默认128MB按顺序写入第一个DataNode第一个DataNode再复制给第二个、第二个复制给第三个同时逐级返回ack。整个过程是流水线式的不是等一个块完全写完了再复制下一个。当时很多同学只背了三个副本这个结论但没搞明白副本是怎么分布的。这里有个很容易忽略的细节副本放置策略是第一个副本在客户端所在节点如果客户端不在集群内则随机选一个第二个副本在另一个机架的随机节点第三个副本和第二个在同一个机架的不同节点。这么设计的目的是同时兼顾容错和网络开销——两个副本在一个机架可以减少部分跨机架流量同时三个副本分布在至少两个机架上能扛住整个机架故障。MapReduce的容错机制也是常考点特别是当某个Task失败后ApplicationMaster或者早期版本里的JobTracker会怎么做。这里要分清两个层级单个Task失败会自动重试默认4次超过重试次数则整个Job失败如果某个NodeManager节点上有多个任务连续失败会被加入黑名单后续任务不再调度到该节点。还有一个容易被问到的点是Shuffle的过程——Map端输出先写入环形缓冲区默认100MB达到80%时开始溢写溢写前会做分区和排序默认按Key排序如果有Combiner会在溢写时执行一次局部合并减少传输数据量。2.2 Spark原理为什么它比MapReduce快宽窄依赖怎么区分Spark相关题目在当年的笔试里几乎是必考。最经典的问题就是Spark为什么比MapReduce快很多人的答案是Spark基于内存计算但这个回答其实不够完整。第四批笔试里有一道选择题就是围绕这个问题的多个选项让你选正确的其中有个选项是Spark的DAG计算引擎能够减少Shuffle次数这个才是核心。MapReduce对每个MapReduce任务都要落盘中间结果写入HDFS下一阶段再从HDFS读取导致磁盘I/O开销很大。而Spark的RDD在内存中存储Stage内部的算子如map、filter、flatMap是流水线式的数据在内存里直接传递不需要落盘。只有在宽依赖触发Shuffle时结果才会写磁盘。此外Spark的DAG调度器会把连续的窄依赖算子合并成一个Stage进一步减少调度和计算开销。所以答案是内存计算只是一方面DAG优化和惰性求值机制才是关键。Spark另一个高频考点是宽窄依赖。题目会给你几种算子让你判断是窄依赖还是宽依赖。窄依赖指父RDD的每个分区最多被一个子RDD分区使用比如map、filter、union针对窄依赖的情况宽依赖指父RDD的一个分区会被多个子RDD分区使用必然产生Shuffle比如groupByKey、reduceByKey、join非广播场景。我当年的记忆技巧是看到减少数据量的算子如filter、map基本是窄依赖看到重新聚合的算子如groupBy、reduceBy基本是宽依赖但reduceByKey在map端还有一次Combine这点要额外注意。2.3 Hive与数仓分层SQL题之外的隐藏考点Hive的考点一般集中在SQL如何转换为MapReduce以及数仓分层设计。笔试里有一道SQL题是统计用户的连续登录天数这种题型现在依然是面试高频题。核心思路是使用开窗函数row_number()按用户分组、按日期排序然后计算日期减去行号的值同一个日期差对应的记录就是连续登录区间。我当时用的是另一个更通用、更不容易出错的写法先把每个用户的登录日期去重再用lag或lead函数判断日期是否连续。说实话遇到连续登录问题我更推荐用差集法思维连续日期的天数可以用一个固定的等差序列来映射。具体SQL你可以参考我下面给的这个模式select user_id, min(login_date) as start_date, max(login_date) as end_date, count(*) as continuous_days from ( select user_id, login_date, date_sub(login_date, row_number() over (partition by user_id order by login_date)) as date_group from ( select distinct user_id, login_date from user_login_log ) t1 ) t2 group by user_id, date_group这套SQL的核心逻辑是如果登录日期是连续的那日期减去行号后会得到同一个值。举个例子用户A在1月1日、1月2日、1月3日登录那么行号分别是1、2、3date_sub后分别是12月31日、12月31日、12月31日得到同一个分组。这个方法应对连续类题目非常顺手建议直接背下来。数仓分层也是第四批笔试的大题方向之一。典型题目是设计一个电商平台的数据仓库说明每层的作用。数仓一般分五层ODS层原始数据层、DWD层明细数据层、DWS层汇总数据层、ADS层应用数据层、DIM层公共维度层。ODS层就是原封不动地存业务库同步过来的数据DWD层做清洗、去重、标准化比如将不同格式的时间统一成timestamp将性别字段的男/女/1/0统一成标准值DWS层按主题做轻度汇总比如每天的订单量、GMV、UV、PV都在这层汇总ADS层则面向具体业务需求比如大屏展示、运营报表DIM层存放用户维表、商品维表等核心维度数据。当年有很多人搞不清楚DWD和DWS的区别其实站在使用方角度想就很清楚DWD是干净但不聚合的明细数据可以回答昨天每个用户的每一笔订单是什么DWS是聚合后的指标数据可以回答昨天的总订单量是几单。如果业务需要的是后者直接查DWS层就可以不需要每次跑一遍明细。2.4 Kafka与实时链路从消息队列到实时计算第四批笔试里Kafka的考点主要集中在消息队列的基本模型和使用场景上。选择题会问Kafka中一个Partition的消息是如何保证有序的消费者组与Partition的分配关系这类问题。Kafka的消息在同一个Partition内是有序的但由于同一Key的消息会路由到同一个Partition所以如果业务上强依赖全局有序就只能用单个Partition——这会牺牲并行度所以实际中大多数场景接受分区有序。还有一个容易混淆的点Kafka的消费者组。同一个消费者组内的多个消费者共同消费一个Topic时每个Partition只会被组内一个消费者消费这样才能保证一条消息只被处理一次。如果消费者数大于Partition数多出来的消费者会空闲。这个机制在实时链路设计里非常关键很多业务初期只建了3个Partition结果业务量涨上来后消费者加到10个发现只有3个在工作其他全部空闲这就是没理解Partition与消费者的绑定关系。实时计算方面当年Flink还不像现在这么普及笔试里涉及的Spark Streaming更多一些。考点通常是Spark Streaming的窗口计算有哪几种如何保证实时计算的精确一次语义。这里想提醒大家的是Kafka配合实时计算框架时最重要的不是多快而是不丢不重。因为数据源系统比如埋点日志的写入是永不停止的一旦出现反压背压或者任务重启处理位置offset管理不好就会丢数据或重复消费后续的报表数据就可能出现对不上号的情况。3. 经典大题实战从算法题到系统设计题3.1 海量数据去重从HashSet到BitMap再到Bloom Filter编程题里有一道我印象很深的给定一个包含上亿个整数的文件内存只有几百MB要求统计不重复的整数个数。这道题当时把很多人问懵了因为如果直接加载到一个HashSet里上亿个Integer的内存占用轻轻松松超过几个GB。正确思路是分治或使用位图。如果整数范围不太大比如都在0到2的32次方之间但出现次数有限可以用BitMap——每个整数用1个bit表示是否存在2的32次方个bit只需要512MB如果再用两个bit还可以分别表示0次/1次/多次但内存要求还是偏高。更实用的方案是Bloom Filter一个m位的位数组加k个哈希函数判重时如果所有哈希位置都是1则认为该元素可能存在如果任一位置是0则一定不存在。Bloom Filter的代价是可能出现误判把不存在的判为存在所以它更适合允许小概率误判的去重场景比如爬虫URL去重、推荐系统已读内容过滤但对需要精确计数的业务要慎用。还有一种更灵活的方案基于分治的Hash分桶。读取文件中的每个整数按hash值模N取余比如N100分别写入100个小文件这样相同整数一定落在同一个文件里然后每个小文件用HashSet统计去重后数量最后汇总。这个方案看起来简单粗暴但确实是面试官想听到的答案之一——它体现了大文件拆小文件的分布式思维。而且这个思路和MapReduce的分区思想完全一致放在大数据岗位的笔试里非常应景。3.2 系统设计题设计一个日活千万的日志采集系统第四批笔试最后一道大题是系统设计题场景大致是业务方每天产生海量的客户端日志需要采集、传输、存储并支持后续分析请设计一套完整的架构。这道题没有标准答案但有几个得分点是固定的数据采集端要支持埋点SDK和日志聚合上报传输层要引入消息队列削峰填谷存储层要区分原始日志和清洗后的结构化数据查询分析层要支持离线跑批和实时看板。我当时的答题框架是这样的客户端通过SDK批量上报日志 → Nginx/LVS接入 → 写入Kafka按业务线建Topic → Flume或Logstash消费Kafka并做初步清洗 → 一份落HDFS供Hive/Spark离线分析一份写入HBase或ClickHouse供实时查询和报表展示。同时加一个配置中心控制各端的采样率、上报频率和日志级别监控报警系统监控各环节的积压量和延迟。这个架构看起来中规中矩其实就是面试官想看到的完整链路。要拿到高分你需要在此基础上补充细节比如为什么用Kafka而不用直接写HDFS因为Kafka能解耦生产和消费写入峰值时消费者可以按能力慢慢消费不会压垮存储层。为什么要用Flume做清洗而不是在Kafka里直接改数据因为Kafka的消息一旦写入不可变避免改数据带来的一致性问题。这些细节才是区分度和加分项。3.3 大数据场景下的Java与算法题HashMap、TopN与合并排序笔试的编程题不可能全是SQLJava与算法题是绕不开的。第四批里有一道Java题是让手写一个简化版的HashMap考察对hash计算、哈希冲突链地址法、扩容机制负载因子0.75、扩容时重新分配的理解。这道题放在大数据岗位也有特殊含义——因为在分布式计算里map阶段的数据分发本质就是哈希分桶和HashMap的原理一脉相承。所以如果你能在答题时主动提到这和MapReduce的Partitioner类似会是一个很好的加分点。还有一道TopN题比如一个文件里每行是URL统计出现次数最多的前100个URL。标准做法是先用HashMap计数再维护一个大小为100的最小堆遍历所有计数结果比堆顶大的就替换并调整堆。时间复杂度是O(n log100)也就是O(n)。如果数据量再大就引入分治每个文件各统计一份Top100最后再归并。这个思路在Spark里对应的是reduceByKey加orderBy或takeOrdered在MapReduce里则对应自定义Partitioner加单Reducer或二次排序。关于排序还有一个容易被考到的点如何在内存只有512MB的情况下对2GB的文件排序。答案是外部排序——分块读入内存排序后写回磁盘生成多个有序小文件然后做多路归并。这个知识点在Hadoop的MapReduce中对应的是环形缓冲区溢写后的小文件归并merge所以我始终强调大数据面试里的算法题考察的不只是算法本身而是你能否把通用算法映射到分布式系统的大规模数据处理场景里。理解这层映射关系比刷一百道LeetCode更有用。4. 数据倾斜与任务调优大数据开发真正的工作日常4.1 数据倾斜的定位与常见解决方案数据倾斜是笔试中经常出现、也是实际工作中最让人头疼的问题之一。第四批的讨论区里很多人都在问数据倾斜到底怎么定位我刚工作那会儿也真的被这个问题折腾过。数据倾斜的表现是一个Spark或MapReduce任务里大部分Task很快跑完了但有一两个Task要跑几倍甚至几十倍的时间整个Job卡在99%。定位倾斜的第一步是看Log在Spark Web UI里找到运行时间最长的Stage查看该Stage的Task列表——如果某个Task的输入数据量是其他Task的几十倍基本可以确定发生了倾斜。定位到具体的Key之后再想对应的解决方案。常见的处理办法有几种优化Join如果是一个大表和一个小表Join可以用Broadcast Join把小的维度表广播到每个Executor内存中避免Shuffle如果是大表和大表Join可以给倾斜的Key加上随机前后缀先打散再聚合如果是groupByKey/reduceByKey造成的倾斜可以考虑两阶段聚合先给Key加随机前缀做一次局部聚合再去除前缀做全局聚合。还有一些细节技巧如果只是为了去重可以用repartition(rand())来强制打散数据如果倾斜特别严重且业务允许也可以单独把倾斜Key的数据拎出来走单独的任务再和主任务结果合并。实际工作中我遇到过最离谱的一次是某个业务的设备ID字段有空值几千万条Null数据全挤到同一个Key上Task直接OOM。当时简单的处理是在SQL里先将空值的设备ID随机赋一个值再参与聚合问题立刻解决。这类经验笔试里很难考到但面试官会当作后续追问来考察你的项目真实性。4.2 任务调优的核心思路从资源参数到执行计划大数据笔试偶尔也会涉及调优题比如给你一个运行很慢的Spark任务你会怎么优化。很多人一上来就加Executor内存或并行度但更成熟的做法是先从执行计划入手。第一查看是否有不必要的Shuffle比如两个表都做了filter但过滤条件没下推、groupBy后又groupBy等可以通过修改SQL逻辑减少Shuffle第二检查数据是否有不必要的复制或序列化开销比如Kryo序列化通常比Java默认序列化快很多第三再考虑并行度、内存和Core设置。资源参数的设置也有套路。比如Spark Executor的core数一般建议设为2到5个因为单个Executor上Core太多时线程切换开销会变大而且HDFS写入吞吐量也可能成为瓶颈。Executor内存则要预留一部分给Overhead避免出现OOM时把YARN上的其他任务拖垮。至于那个经典问题spark.sql.shuffle.partitions设多少合适我的经验是如果Shuffle的数据量在几GB级别设200到500比较合适如果单分区数据量超过几百MB就该考虑提高分区数。分区数不是越大越好每个分区启动和调度都有开销你把分区数从2000提到5000可能不仅没变快反而更慢。这些经验听起来琐碎但都是实际工作里踩过坑才总结出来的。4.3 SQL性能优化从执行计划到过滤下推作为数据开发SQL优化是每天都要做的事情。回到笔试场景有一道SQL题问如何优化一条运行很慢的Hive/Spark SQL。大部分人都知道要加过滤条件、避免select *但有几个考点容易被忽略。其一谓词下推。如果你join之前先做了过滤优化器如Spark Catalyst会把过滤条件下推到表扫描阶段优先减少数据读取量但如果你在子查询里写得不够清楚优化器可能无法下推这时候需要手动改写SQL把过滤条件尽量放在内层子查询中。其二避免笛卡尔积。这个错误我见过很多次新手写SQL时一不小心就会造成join条件不完整比如left join两边各有一份重复的数据结果量级瞬间爆炸。定位办法很简单跑之前先看explain计划里的Join类型如果是CartesianProduct赶紧检查关联字段的唯一性。其三列裁剪和分区裁剪。对分区表查询时where里一定要带上分区字段否则全表扫描数据量差一个数量级以上。其实SQL优化有一个底层原则尽量在数据量还没膨胀之前把数据量减下来。这句话是我当年刚做数仓时前辈告诉我的如今我每次排查慢SQL、优化Spark任务都会先默念一遍。它在大数据领域的普适性几乎等同于先压缩、再传输。5. 常见问题与踩坑实录5.1 笔试环节的典型问题与应对方式我在牛客和知乎上看到很多参加第四批笔试的同学反映最大的问题不是不会做而是时间不够用。原因很简单选择题里有些题涉及非常底层的细节比如HashMap在JDK1.8中链表转红黑树的阈值是多少ZooKeeper的ZAB协议存在几个阶段这类题一旦没复习到就要靠猜一猜就浪费时间。我的建议是考前用一天时间把底层细节快速过一遍HashMap的树化阈值是8、红黑树退化为链表的阈值是6ZAB协议包括发现、同步、广播三个阶段HDFS默认副本数是3、块大小是128MBSpark默认的Shuffle分区数在早期版本是200。这些知识点不深但都是背了就能拿分的部分性价比极高。编程题方面最常见的坑是环境不熟悉。在线编程环境和本地IDE不太一样比如不支持某些库、输入输出格式有要求、单元测试是黑盒的。很多人能把逻辑写对但卡在如何读取多行输入这种细节上。我的经验是考前一定要去牛客或LeetCode的在线环境练几道题特别是那些需要自己处理输入输出的。而且注意笔试平台的判题一般不会打印标准答案你可以先写一个暴力解法保证通过率然后再慢慢优化别一上来就死磕最优解。5.2 复盘总结答得不好的题目是成长的突破口第四批笔试有一道关于数据仓库缓慢变化维SCD的题我记得当时答得很一般。题目大概是业务中用户信息会变比如手机号、收货地址你如何设计一个既能保留历史、又能查询最新的维表。这道题考的是SCD的几种策略SCD1直接覆盖不保留历史SCD2增加新行并给起止时间标记保留完整历史SCD3增加新列保存当前值和前值只保留有限历史。我当时只答了SCD2没有把SCD1和SCD3的使用场景说清楚现在想想如果能结合一个具体业务例子比如订单报表需要按下单时的收货地址统计这就要求订单表关联的是当时的历史地址而不是最新地址来答效果会好很多。这道题带给我的教训是准备大数据笔试不能只背知识点的名字一定要能讲出每个方案背后的取舍。后来我在带实习生的时候也发现能讲清楚为什么的人写出来的代码和设计通常更靠谱。这也算是当年那场笔试给我留下的最大收获——它逼着我把每个知识点都往实际业务里怎么用这个方向去思考而不是停留在这个概念我见过的层面。5.3 面试环节的大数据深挖方向笔试通过了还有面试这里顺便说一下面试官经常顺着笔试题目往深里挖。比如笔试题里出现了数据倾斜面试官就会追问你实际处理过数据倾斜吗最后的方案是什么有没有对比过优化前后的运行时间如果你只是背了网上的答案没有真实的数据和案例支撑很容易在追问环节露馅。所以我的建议是在准备这类岗位时一定要自己动手搭一个小的实验环境。不用特别大几台虚拟机或者用Docker起一个Hadoop和Spark集群就行自己写个简单的ETL脚本跑一跑WordCount、统计一下用户访问量、模拟一次数据倾斜然后尝试解决。这些实操经验才是你在笔试和面试中与其他人拉开差距的地方也是我在这篇文章里反复强调实战的原因——大数据这个方向绝不只是靠刷题就能学好的。写在最后从2018年到现在大数据技术栈迭代很快Flink已经比当年普及得多、Spark也有了很多变化但第四批这套笔试的考察内核并没有过时它始终在考察你对数据链路的理解、对分布式系统核心概念的把握、以及在具体业务场景中做取舍的能力。如果有机会重新参加一次这样的笔试我想我一定会把更多时间花在理解为什么上——为什么HDFS写三个副本而不是两个为什么Spark DAG可以优化Shuffle为什么数仓要分层为什么数据倾斜要这样处理。如果你正在准备大数据岗位的校招或跳槽不妨拿着这份复盘把每一个组件、每一个问题都延展开来问自己一句这背后的原理是什么业务上该怎么用出了问题时怎么排查。把这三个问题都答清楚你收获的就不只是一份笔试题的答案而是一个能扛住真实业务的大数据工程师的思维框架。