公司动态
顺丰科技大数据平台开发笔试复盘:从Java到Spark核心考点拆解
说实话现在回头复盘顺丰科技2019秋招大数据平台开发这套客观题你会发现它不只是“一套题”更像是一张当时大数据平台工程师能力模型的体检表。距离笔试过去挺久了但这套题涉及的知识点依然在面试和日常开发里反复出现拿它来做系统复习的提纲比盲目刷LeetCode性价比高得多。这篇文章不打算只给你罗列题目和答案我会以这套题为线索拆解每个考点背后到底在考什么、为什么这么考、以及你在准备同类岗位时应该怎么复习。无论你是正在准备校招还是刚转行做大数据开发这份拆解应该都能让你少走一些弯路。1. 招聘与考题逻辑分析1.1 顺丰科技当时到底在招什么人顺丰科技的大数据平台团队本质上是在为顺丰整个物流体系做数据底座。快递行业的数据有几个鲜明特征数据量级大、实时性要求高、链路长。一个包裹从下单到签收背后要经过揽收、中转、运输、派送多个环节每个环节都在源源不断产生轨迹数据。这些数据既要支撑实时大屏监控也要回流到离线数仓做分析挖掘。这个业务背景直接决定了岗位的能力要求。候选人不能只会写SQL或者调API得理解整个数据管道是怎么运转的。我对这套题的整体印象是它考的不是某个框架的API熟练度而是对大数据生态组件原理的理解是否成体系。当时团队需要的是那种丢到项目里能自己排查数据倾斜、能定位任务失败原因、能对集群性能问题给出合理判断的人。1.2 客观题的知识版图是怎么布局的整套客观题覆盖面相当广不是那种只考Hadoop生态的题目。从记忆来看大致可以分成四个知识板块Java编程基础与JVM占比相当高。这符合当时主流大数据平台团队的面试风格因为大数据框架基本都是Java/Scala写的看不懂JVM看源码、调优基本都是空谈。大数据组件原理Hadoop核心、Spark、Kafka、Hive、Flume这些当时的主流组件都要考重点不在API而在运行机制和设计思路。数据库与数据仓库SQL能力、索引原理、事务ACID、维度建模等都有涉及。分布式基础理论CAP理论、一致性协议、负载均衡等这部分很能拉开差距。值得注意的一点是这套题几乎没有涉及Flink。这并不意外2019年的时候Flink虽然已经进入大众视野但很多公司的生产环境还是Spark Streaming和Storm的天下。顺丰科技的题库主要围绕它们生产环境真正在用的技术栈来出题这也是给大家一个提示准备笔试时别只看“热门框架排行榜”多研究目标公司实际的技术选型复习才有针对性。2. Java基础与JVM考点拆解2.1 集合类与并发编程的高频陷阱大数据平台开发和纯后端开发在Java考点上有些区别更偏向并发、集合在分布式场景下的行为表现。我印象里有一道题是这样的HashMap在并发put时会发生什么是抛出异常、丢失数据还是形成环形链表导致死循环。很多候选人选对了“环形链表”但问了原因却说不清楚。这里要说明一点JDK版本不同行为表现其实不一样。如果是JDK 7及之前并发put在扩容时会形成环形链表get时可能触发死循环。JDK 8之后resize的逻辑重写了虽然不会再有环形链表但并发环境下依然会有数据覆盖丢失的问题。所以答题的时候先确认版本上下文再答具体表现这样的答案才算严谨。ConcurrentHashMap也是必考项。2019年校招特别爱考JDK 7版本分段锁和JDK 8版本CASsynchronized的对比。底层逻辑是JDK 8对锁粒度做了更细的拆分写操作只锁住单个桶的头节点读操作几乎完全无锁。你如果只说“ConcurrentHashMap是线程安全的”这道题基本就废了考官想听的是“它为什么在高并发场景下比HashTable性能好比Collections.synchronizedMap扩展性强”。再说线程池核心线程数、最大线程数、阻塞队列的关系几乎是必考题。比如题目问线程池核心线程数是5最大线程数是10队列容量是100此时提交第6个任务会怎么处理答案是直接进入队列等待只有队列满了才会创建新线程到最大线程数。很多人把RejectedExecutionException触发时机搞混以为超过最大线程数才拒绝。实际上拒绝策略是在队列满且线程数达到最大时才触发。注意这里有个容易被忽略的点ThreadPoolExecutor的execute方法提交任务时先判断核心线程是否已满没满则创建线程执行满了才入队队列满了才判断是否达到最大线程数最大线程数也满了才走拒绝策略。这个顺序在源码里的判断逻辑是固定的别颠倒。2.2 JVM内存模型与排查思路JVM相关题目集中在内存区域划分和GC策略上。有一道经典题是元空间Metaspace在JDK 8中属于堆内还是堆外会不会抛出OutOfMemoryError。很多人知道答案是“堆外内存会OOM”但不太理解为什么会OOM。元空间存的是类的元数据默认情况下受系统物理内存限制如果加载的类无限多或者被动态生成代理类刷爆依然会OOM。这里可以在JVM参数里设置MaxMetaspaceSize来限制。还有一道题考察GC Roots类型问下面哪些对象可以作为GC Roots虚拟机栈中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象。这题的陷阱在于考试特别喜欢写“方法区中的常量池对象可以作为GC Roots”这其实是个错误理解。可以作为GC Roots的是“方法区中常量引用的对象”比如字符串常量池里的对象引用了堆上的实例。更准确地说被常量引用的对象是Roots常量本身不属于根对象。JVM调优题里顺丰科技很偏爱问“CPU飙升怎么排查”。当年这道题作为客观题出现会简单一点但它的扩展价值很大。典型的排查步骤是先用top找到CPU过高的Java进程PID再用top -H -p PID找到具体线程然后用jstack导出线程栈用printf把线程ID转成十六进制在线程栈里搜索对应的nid。这套操作在线上排查时几乎每周都会用到建议收藏。2.3 类加载机制与双亲委派类加载机制在客观题里出现的频率也不低。题目通常是这样一个类文件里同时引用了第三方库和JDK自带的同名类最终会加载哪一个。答案就是父加载器优先的类。双亲委派模型的意思是一个类加载器收到类加载请求后先让父加载器尝试加载父加载器加载不了自己才动手。好处很明显保证Java核心类库的安全比如你自定义一个java.lang.String也不会被加载执行因为最终还是由Bootstrap ClassLoader加载了JDK自带的String类。这个知识点也不只是笔试常客在排查实际问题时也很有用。比如用Spark提交任务时遇到NoSuchMethodError往往就是依赖冲突导致类加载到了错误的版本这个时候需要结合依赖树分析和类加载排查。所以在准备这部分时别死记硬背双亲委派的流程多想想一旦破坏了这个模型用在什么场景——比如Tomcat为什么要打破双亲委派SPI机制为什么需要线程上下文类加载器想明白了就不怕万变不离其宗的考法。3. 大数据组件原理深度解析3.1 Hadoop生态从HDFS到MapReduce的底层逻辑HDFS题目最常考数据副本机制和读写流程。有一道题当Client写入一个文件时默认副本数是3NameNode会怎么选择DataNode。标准答案是第一个副本放在Client所在节点如果Client不在集群内则随机选一个节点第二个副本放在与第一个副本不同机架的节点上第三个副本放在与第二个副本相同机架的另一个节点上。这个策略叫“机架感知”目的是在可靠性和写带宽之间取平衡。副本放置策略值得深挖一下。1、2副本跨机架保证了容灾2、3副本同机架又减少了跨机架的写流量整体写一个block只需要跨一次机架。如果全放在不同机架每次写入都要跨机架传输带宽开销很大如果全放同机架一个机架断电数据就全没了。这个设计思路你理解了之后遇到类似题目基本能推出来而不是背答案。MapReduce的容错机制也是高频考点。题目会问当某个TaskTracker节点上的Map Task执行失败JobTracker会怎么处理。答案不是直接让整个Job失败而是把这个Task调度到另一个节点重新执行。如果同一个Task失败超过4次默认才会导致整个Job失败。这个机制叫“Task级别的容错”。这里就要提一个大数据的经典困惑为什么MapReduce这么慢当年大家还用得津津有味因为它在设计之初就不是为低延迟设计的而是为“在商用机器集群上可靠处理海量数据”设计的。每次作业都要经过Map端落盘、Shuffle排序、Reduce端拉取每一步IO开销都不小。能理解这个背景你对Spark为什么会成为主流、Flink为什么能后来居上就会有更立体的认知。3.2 Spark的运行机制与调优思路Spark是这套客观题的大头。当时爱考RDD的依赖关系窄依赖和宽依赖的区别。窄依赖是指父RDD的每个分区最多被子RDD的一个分区使用宽依赖是指多个子分区依赖同一个父分区。这个区别直接决定了两个重要机制一是流水线执行窄依赖和Shuffle宽依赖二是故障恢复时窄依赖重算效率高、宽依赖需要引入Checkpoint。有一道题问得很典型reduceByKey和groupByKey有什么区别哪个性能更好。答案就是reduceByKey在Map端会做一次本地combine减少了Shuffle的数据量groupByKey不会直接把所有原始数据原样Shuffle。虽然逻辑上两者结果可能一致但性能差距在数据量大时可以高达数倍甚至一个数量级。这题考察的其实是你有没有真正写出高性能Spark作业的经验而不是只会背API。Spark作业提交流程也会考一段简单的WordCount代码从提交到最终执行经过了哪些阶段。标准链路是spark-submit提交 - 创建SparkContext - DAGScheduler根据宽依赖划分Stage - 每个Stage内部生成TaskSet - TaskScheduler把Task分发到Executor执行。这道题的变体是问Stage划分的依据答案就是宽依赖Shuffle依赖处切断。内存管理这块当时考了统一内存模型。Spark 1.6之后引入了统一内存Unified Memory分为Storage内存和Execution内存两者可以互相借用。Execution内存优先当Storage要抢占被Execution占用的内存时Storage会被强制驱逐反过来Storage占用的内存不会被Execution强制驱逐只能等待Storage主动释放。记住这个“Execution优先”原则很多内存相关题目都能答对。在调优思路上客观题通常只考概念但真正做项目时你需要能回答这几个问题什么时候调大spark.sql.shuffle.partitions、什么时候用Kryo序列化、什么时候开启spark.sql.autoBroadcastJoinThreshold。我的经验是Hive表Join时小表低于10MB就自动Broadcast避免Shuffle如果某张维表已经上了100MB就别指望自动广播了手动处理会有奇效。3.3 Kafka的消息模型与一致性机制Kafka在物流行业的地位非常高。顺丰的轨迹数据、订单状态变更、实时监控告警基本都走Kafka。这套题考Kafka也不是停留在Producer/Consumer这种表面API上而是把重点放在消息可靠性和消费者组机制上。有一道题是Kafka的Consumer从Partition中消费消息之后什么时候提交Offset才保证不丢消息且不重复消费。答案是“先处理后提交”会丢数据“先提交后处理”会重复消费没有两全其美。想要at-least-once就处理后提交想要at-most-once就提前提交想要精确一次需要开启幂等性加事务API。这道题考察的是你对Kafka设计哲学的认知它能做到什么级别做不到什么级别选型时要怎么取舍。ISR机制也考了。Leader Partition维护了一个ISR集合只有这个集合里的Follower才能被选为新的Leader。判断Follower是否正常的标准是与Leader的日志落后程度不超过replica.lag.time.max.ms默认30秒。只有ISR中的副本挂了才会等min.insync.replicas参数判断是否接受写入。这里有个生产环境的坑如果把min.insync.replicas设为2但只有一个副本存活Producer还在发数据就会持续报NotEnoughReplicasException。这种情况下宁可让Producer失败也不要写丢数据。存储机制也有一道记忆深刻的题为什么Kafka可以做到高吞吐写入而不是每次刷盘都落磁盘。答案是它利用了操作系统Page CacheProducer写数据先写Page Cache由系统异步刷盘。而且Kafka的消息是顺序追加写文件不是随机写。顺序写磁盘的速度比随机写快好几个数量级这才是Kafka高吞吐的核心秘密而不是很多人以为的什么“内存队列”。3.4 Hive与离线数仓Hive在大数据平台开发里是查询的入口客观题占比也不小。有一道题目问Hive的SQL底层执行引擎默认是什么简单题。但延伸的问题就难一些一个简单的SELECT COUNT(*) FROM table底层会跑几个MapReduce任务在Hive 3.x之后使用Tez引擎会优化成更少的任务但理解MapReduce的执行过程仍能帮助排查性能问题。Hive的分区表和分桶表也是高频考点。分区表是按目录切分数据分桶表是按文件切分数据。分区字段是伪列不在数据文件里分桶字段是真实字段用Hash取模写入对应桶文件。用分区表减少扫描数据量用分桶表做Bucket Map Join优化这是两道题的答案也是两类优化手段。Hive的谓词下推Predicate Pushdown考点更有意思。当你写WHERE条件时Hive会尽量在Map端做过滤减少传输到Reduce端的数据量。比如join时如果先在ON条件里写了过滤条件可能会在Reduce端才过滤而在WHERE里写直接过滤Map端输出。这是个优化细节后来在Spark SQL里也有类似逻辑值得多练一下。4. 数据库与数据仓库重点回顾4.1 SQL基础从索引到事务的考察客观题里数据库基础占比大概有两成重点集中在索引、事务、锁机制三块。有一道题几乎年年出现InnoDB的索引结构是B树为什么不用B树也不用红黑树或Hash索引。答案是B树只有叶子节点存数据内部节点可以存更多索引树高更低IO次数更少。而且叶子节点之间通过双向链表相连非常适合范围查询。Hash索引适合等值查询但hash冲突和无法范围查询是硬伤。事务隔离级别这道题也是必考尤其是问“不可重复读”和“幻读”的区别。不可重复读是同一行两次读值不同侧重点在行的更新幻读是同一范围两次查询返回的行数不同侧重点在新插入的行。InnoDB通过MVCC解决不可重复读通过Next-Key Lock间隙锁记录锁解决幻读。能把这个链路讲清楚事务题基本不会失分。我对大家的建议是复习SQL时别满足于会写要会看执行计划。EXPLAIN后看到type是ALL表示全表扫描看到possible_keys和key不一致说明优化器没选对索引看到Extra里出现Using filesort说明排序没用到索引。这些细节笔试不一定考但面试聊项目时一说立马显得有实战经验。4.2 维度建模与数据仓库设计数据仓库题偏重理论考star schema星型模型和snowflake schema雪花模型的区别。星型模型是事实表在中间维度表直接连一圈查询路径短雪花模型是维度表继续规范化拆分减少了数据冗余但查询时join层级多性能受限。生产环境常见做法是底层用雪花模型建模节约存储上层用星型模型做宽表和汇总兼顾灵活性和查询性能。缓慢变化维SCD也是经典考点。典型的场景用户的收货地址变更后历史订单里的地址怎么处理。这题没有标准答案看你业务怎么选SCD1直接覆盖、SCD2保留历史记录并加生效时间、SCD3只保留最近两次值。我的建议是对于物流行业这种强审计、强追踪的业务订单快照是必须的不能用SCD1覆盖否则历史账就对不上了。拉链表是个容易出主观题的考点但客观题偶尔也会考。拉链表里面每行数据有start_date和end_date当前有效记录的end_date是9999-12-31。这种设计特别适合订单状态、会员等级这种经常变化的维度属性既能查当前状态也能回溯历史状态。如果你在面试中提到自己做过拉链表面试官的眼睛基本会亮一下。4.3 CAP理论与分布式事务权衡分布式基础题的数量不多但区分度极高。CAP理论几乎是必考内容一致性Consistency、可用性Availability、分区容错性Partition tolerance在分布式系统里三者不可兼得。深度一点的题目会问在分区发生时ZooKeeper选择的是CP还是AP答案是CP为了保持一致性它会拒绝写入导致短暂不可用。而Eureka选择的是AP各节点数据可能不一致但保证可用。分布式事务在大数据平台的笔试题里出现的概率也在上升。常见的方案包括两阶段提交2PC、三阶段提交3PC、本地消息表、事务消息。客观题通常问的是“哪种方案会产生阻塞”答案是2PC。2PC在协调者宕机后会阻塞参与者的事务直到超时这是它最大的痛点。3PC引入了超时机制和准备阶段就是为了解决这个问题。提示现代大数据架构中强一致的分布式事务场景越来越少最终一致补偿机制成了主流。Kafka事务和Flink的端到端Exactly-Once配合让流式数据管道也能做到精确一次。笔试答题时别只背概念能举出实际组件中的应用分数会高不少。5. 笔试题型复盘与解题策略5.1 高频题目类型与分值分布从整体做题感受来讲整套客观题大致包含三类题型单选、多选和判断。多选是最容易丢分的因为多选漏选少选都算错。我当年在这上面吃了不少亏后来总结出两个判断原则如果一道题选项里出现“绝对化描述”比如“一定不会”、“必须全部”这个选项大概率是错误的大数据场景里很少有绝对情况。如果选项里出现“可能”、“在某种条件下”那这个选项通常就是正确选项。分布式系统里到处是“视情况而定”。多选题里有个典型例子下面哪些因素会导致Spark作业运行缓慢选项包括数据倾斜、Executor内存不足导致频繁Full GC、Shuffle分区数设置得太少、使用groupByKey替代reduceByKey。四个选项其实都选但很多人会漏掉“使用groupByKey”因为它在多数情况下不会Job失败只是慢。这种题考的是你对性能问题的敏感度而不是基础知识点本身。5.2 时间分配与做题顺序顺丰科技的这套客观题题量不小我当时估算了一下平均每题只有不到1分半的时间。如果被一道复杂的JVM多选题卡住后面的Hive SQL题可能就来不及看了。我后来的策略是先做判断和单选这类题信息量小答得快。再做数据库和SQL相关题因为这部分自己最有把握。最后做大组件原理和多选题因为这类题往往需要推敲且容易在某两个选项之间纠结。做完第一遍之后把不确定的题目标记出来最后统一检查。检查时优先改那些“你第一眼觉得对但后来发现好像不对”的题第一直觉通常是长时间积累的直觉比临时推理更可靠。5.3 从错题中挖掘知识盲区笔试之后复盘比考试本身更重要。我当时把自己做错的题按知识点归类发现一个规律错得最多的不是某个具体框架的问题而是“框架之间横向对比”的题。比如“Spark和MapReduce在Shuffle机制上的区别”、“Kafka和RabbitMQ在可靠性设计上的差异”这类题目需要你对多个组件有横向理解而不是孤立地记每个组件的特性。解决横向对比题的方法很简单做一张自己的技术对比表。左边是组件名称右边是存储模型、计算模型、容错机制、适用场景、性能瓶颈。不一定要写成大而全的长文只要把你自己容易混的几个点写清楚。比如我之前老分不清Spark的窄依赖和Kafka的Partition之间的关系写下来之后就清晰了Spark的依赖描述的是RDD之间的血缘关系Kafka的Partition描述的是消息的组织形式两者不是一个层级的抽象应用场景完全不同。6. 常见问题与实战避坑经验6.1 复习资料怎么选才高效我当时走过不少弯路一开始买了好几本大数据原理的大部头想着从头啃到位结果看了两周还停在第一章。后来发现效率最高的复习方式是“以题带点”——先把历年笔试题过一次把不会的题对应的知识点标记出来再针对这些知识点去查资料。这样做的好处是你带着问题去学习印象最深而且不会淹没在无关的细节里。选择题库时也要有筛选标准。最理想的是带解析且解析里讲原理的不是只丢个ABCD的。有些资料只说了答案不解释为什么遇到这种题我建议直接跳过因为它对你构建知识体系没有帮助。我当时把顺丰这套题里涉及的知识点全部手写过一轮不是为了抄题而是为了确认自己真的能从头推导出答案。6.2 时间紧张时的复习优先级如果你现在离笔试只剩两周我的建议是这样的复习次序第一优先SQL和Hive。这个短期提分最快而且笔试出现概率极高性价比最高。不管是必考的聚合函数、窗口函数、表关联还是Hive的语法套路两周时间足够练出肌肉记忆。第二优先Java集合和JVM基础。这部分考点相对固定翻来覆去就那些问题集中刷三天能覆盖七八成而且很多选择题靠常识加推理能排除掉一半选项。第三优先Spark加Kafka的核心机制。重点复习宽窄依赖、Shuffle过程、Stage划分、消息可靠性和Offset管理不用贪多每类掌握两三道母题就能应对多数变体。最后如果有余力再去看CAP、分布式事务、数据仓库模型这些理论题。顺序的逻辑很简单先保分再冲高分。6.3 笔试现场和准备期的心态管理笔试和面试不同面试你还有机会和对方互动笔试只能自己看题、自己判断、自己承受压力。我遇到过一种情况一道题明明很眼熟但选项里有个很细的差别让我拿不准这时候如果陷入纠结后面的时间就全被吃掉了。我的做法是给自己定一个硬性deadline判断题每题最多2分钟单选3分钟多选4分钟。超时立刻标记跳过因为时间的边际收益在递减与其耗在一道题上不如把后面能稳拿的分先攥到手。整体做完之后再回来处理标记的题那时候心态已经恢复了往往能看出之前忽略的线索。7. 从笔试到Offer大数据平台开发的长期积累7.1 技术栈之外的能力要求如果你目标是顺丰科技这种级别的公司还要意识到一点笔试只是第一道关卡后面还有一轮轮技术面和HR面。客观题反映的是你的技术基础但面试官更关注的是你有没有真正的工程感觉。我面试时被问到一个项目问题“假如线上有一个Hive SQL任务从30分钟跑到了5小时你会怎么排查”这问题没有标准答案但面试官想听的是排查路径先看是否数据量暴增再看是否有数据倾斜、是否有小文件过多、是哪个Stage变慢、Executor日志里有没有报错。能思路清晰地把排查流程说出来比背模板答案要有说服力得多。7.2 动手实践是唯一捷径准备笔试阶段我强烈建议你搭一套单机的大数据环境也不用多复杂装一个Hadoop伪分布式加Spark再加一个MySQL和一个Kafka就够了。关键是亲自动手跑几个作业体会一下提交任务、查看日志、调整参数的过程。你亲手提交过Spark作业就知道Driver和Executor的关系到底是怎么回事你亲手消费过Kafka消息就明白Offset提交时机到底影响什么。这套题再怎么考最终都是为工作服务的。笔试的终点不是拿到Offer而是真正具备解决问题的能力。7.3 保持对新技术的敏感度2019年的这套题还没有Flink但放到今天Flink已经成了实时计算领域的标配。所以如果你正在准备当前年度的秋招光刷旧题是不够的一定要在旧题的基础上把Flink的状态管理、Checkpoint机制、Watermark原理这些补上。技术栈会变但招聘的逻辑不会变永远在找能解决实际问题的人。旧题是“复习大纲”新技术是“加分项”两者结合才是完整准备。我自己在准备过程中踩过最大的坑就是过度依赖原题。后来发现如果把重心放在原理理解上即使遇到全新的题型也能用已有的知识结构推断出合理答案。这就是所谓的“以不变应万变”。写到这里突然有点怀念当年刷题的日子。那段时间虽然辛苦但每天都能感觉到知识体系在一点点变完整。现在回头再看这套题它早已不是一份普通的笔试题目而是一个指引我走进大数据平台开发这行的路标。如果你也在准备类似的岗位笔试希望这篇拆解能帮你看清题海背后的知识点脉络少走一些我当年走过的弯路。