掌阅大数据开发岗笔试复盘:从SQL窗口函数到数仓实战
2026/9/7 11:41:56 网站建设 项目流程

2023年秋招季,我投了掌阅科技的大数据开发岗,笔试做完之后最大的感受是:这家公司考的东西不算偏,但非常务实,几乎每一道题都能在真实的数仓和用户增长业务里找到对应场景。如果你正在准备类似岗位,这份复盘应该能帮你少走不少弯路。

先说下整体情况。掌阅的笔试时间是90分钟,题量不算大,大约30道选择题加3道主观题,但内容覆盖面比较广,从SQL窗口函数、Hadoop生态组件原理,到Java集合源码、机器学习基础概念都有涉及。没有纯考背诵的八股,更多是给你一个业务场景,让你用技术方案去解决。这点和很多纯互联网大厂的出题风格不太一样,更偏向“你是不是真的拿数据干过活”。

整份卷子做下来,我的体感是:SQL和Hive占了大头,Spark和Flink也有涉及,Java基础考得比预期多,算法题反而比较朴素——没有hard级别的动态规划,重点在思路上。

1. 笔试全景与岗位定位

1.1 掌阅大数据岗到底在招什么样的人

先说岗位定位。掌阅做数字阅读起家,核心产品是掌阅App和iReader阅读器,业务数据主要来自用户阅读行为、付费转化、内容推荐效果、广告投放等场景。所以大数据岗的日常工作大概率围绕这几块:用户行为日志的清洗与建模、内容推荐的特征工程、阅读时长的多维分析、会员付费漏斗的转化洞察。

从这个业务形态反推笔试内容,就能理解为什么SQL和数仓占据了那么多篇幅。阅读类产品的数据链路非常典型:客户端埋点 -> 日志服务 -> 消息队列 -> 数仓分层 -> 报表和推荐特征。笔试里的题目,本质上就是在模拟这条链路上的实际问题。

如果你准备投这家公司,建议先把用户行为分析、漏斗转化、留存分析这几类SQL场景刷熟,比盲目刷leetcode性价比高得多。我在考场上看到好几道题,第一反应就是“这不就是我们数仓日常取数的活儿”。

1.2 题量与时间分配策略

90分钟做30道选择加3道主观题,时间看上去宽裕,但实际上主观题非常耗时间,尤其是让你写SQL和画架构图的那种,一不小心就容易超时。我的建议是选择题控制在40到45分钟内,剩下45分钟全部留给主观题。

选择题的分布大概是这样的:SQL和数据仓库相关约8到10道,Hadoop生态组件(HDFS、MapReduce、YARN、Hive)约6到8道,Spark和Flink约4到5道,Java基础约4到5道,机器学习基础约2到3道。这个比例说明掌阅对数仓和离线处理能力的要求很高,实时计算也有涉及,但对算法深度要求不算夸张。

做题顺序上,我个人的习惯是先扫一遍所有题目,把会做的快速搞定,拿不准的标记下来,最后再回头思考。这样能避免在一道卡壳的题上耽误太久,导致后面的简单题没时间做。

2. 核心考点拆解:SQL和数仓题

2.1 窗口函数是绝对重点

SQL题里,窗口函数几乎必考,而且不是简单的row_number() over这种入门用法,而是会组合排序、聚合、偏移函数一起考。我记得有一道题大概是这样的:

给定用户阅读记录表reading_log(user_id, book_id, chapter_id, read_time, device_type),要求统计每个用户连续阅读天数大于等于3天的用户名单。

这道题有两个关键点。第一,同一天可能有多条阅读记录,所以要先对user_id和日期去重。第二,要判断连续天数,经典解法是用日期减去row_number()的分组偏移:连续日期的差值相同,形成一个新的分组标识,然后按这个分组标识统计天数。

我当时在考场上写的大致思路是这样的:

with user_daily as ( select distinct user_id, to_date(read_time) as read_date from reading_log ), user_seq as ( select user_id, read_date, date_sub(read_date, row_number() over (partition by user_id order by read_date)) as grp from user_daily ), user_group as ( select user_id, grp, min(read_date) as start_date, max(read_date) as end_date, count(*) as cnt from user_seq group by user_id, grp ) select distinct user_id from user_group where cnt >= 3;

这里date_sub(read_date, row_number())是判断连续日期的核心技巧,原理是:如果日期是连续的,那么每个日期减去它在该用户日期序列中的排序编号,结果一定是同一个值。一旦中断,这个差值就会变化。理解这个原理比背代码重要,因为题目换个花样,比如求最长连续登录天数、连续付费天数,思路是一样的。

2.2 留存分析和漏斗转化

留存率和漏斗转化是运营和产品最常看的两类指标,笔试里自然不会少。掌阅的题目考了次日留存的计算,给了用户活跃表和阅读行为表,要求算不同注册渠道的新用户次日留存率。

核心逻辑是:先找到某天新增的用户集合,然后判断这些用户在次日是否有活跃行为,最后按渠道分组计算比例。这里有个容易踩坑的地方——次日留存的“活跃”定义,题目里可能同时包含启动App和阅读行为两种口径,一定要看清楚题目要求的是哪种。我看题时就差点把“阅读行为”和“启动行为”混在一起算。

漏斗转化题也很有代表性,模拟的是从App启动到完成首次阅读的转化链路:启动 -> 浏览书籍详情 -> 加入书架 -> 开始阅读。要求计算每一步的转化率,并找出转化率最低的环节。这类题除了写SQL,还可能让你给出优化建议,这时候如果你能结合阅读类产品的特点,比如“加入书架到开始阅读的转化低,可能是因为阅读器打开速度慢”或者“书籍详情页缺少试读章节导致跳出率高”,会比只写SQL的人高一个段位。

2.3 数仓建模理论题

主观题里有一道数仓设计题,背景是掌阅要做一个“用户阅读行为分析”主题的数仓,要求设计分层架构并说明各层职责。这道题其实考的就是你对数仓分层的理解程度。

我当时的回答思路是这样的:ODS层存放原始日志,保持和埋点数据一致,不做过多加工;DWD层做清洗和标准化,比如解析user_agent得到设备型号和操作系统版本,统一时间格式,过滤爬虫和异常数据;DWS层按主题汇总,比如用户维度的阅读时长、阅读天数、付费金额等指标;ADS层面向具体业务场景输出,比如推荐算法的特征表、运营看板的指标表。

这里的关键是强调“为什么这样分层”。ODS保持原样是为了回溯和排查问题,DWD清洗标准化是为了下游复用,DWS汇总是为了减少重复计算,ADS按需组装是为了快速响应需求。每一层都有自己的职责边界,不能混在一起。面试官想听的往往不是你背出的分层名称,而是你对每一层存在理由的理解。

3. 核心考点拆解:Hadoop生态与Java

3.1 HDFS写入流程与副本策略

HDFS相关题目里,写入流程是必考题。题目一般这么问:客户端往HDFS写一个文件,从调用create接口开始,到数据落盘,整个过程是怎么运作的?副本策略又是怎么选择的?

回答要点是这样的:客户端先调用DistributedFileSystem.create(),这个请求会发送到NameNode,NameNode检查权限和目录是否存在,然后在文件系统元数据里创建文件条目,返回一个FSDataOutputStream给客户端。客户端开始写数据时,会把文件切分成块(默认128MB),第一个块写完后,DataNode之间会建立管道(Pipeline),按副本因子(默认3)依次把数据复制到下一个节点。

副本放置策略在默认情况下是第一副本放在客户端所在节点(如果客户端不在集群内,则随机选一个磁盘空间充足的节点),第二副本放在与第一副本不同机架的节点,第三副本放在与第二副本相同机架的不同节点。这样设计是为了在可靠性和写入性能之间取平衡——机架内带宽充裕,跨机架复制保证容灾,如果三个副本都放同一个机架,整个机架断电数据就全没了。

笔试里还考了一个小问:如果写入过程中某个DataNode挂了会怎样?答案是管道会关闭,已经写入的块信息会重新上报给NameNode,NameNode会重新分配一个DataNode作为新的复制目标,客户端会从失败的块位置重新尝试写入。这个机制保证了写入的高可用,但也意味着写入延迟会有所增加。

3.2 Spark作业执行流程与宽窄依赖

Spark的题目明显比Hadoop更深入,毕竟现在离线计算大部分都跑在Spark上。我记得有一道题是描述Spark作业从提交到执行的完整流程,关键在于理解Driver、Executor、DAG调度器、TaskScheduler之间的协作关系。

流程是这样的:用户提交Spark作业后,Driver会创建SparkContext,SparkContext向Cluster Manager申请Executor资源。作业中的Action操作会触发DAG的构建,DAG Scheduler负责把作业拆分成多个Stage,划分依据是宽依赖——遇到shuffle操作就切分Stage。每个Stage内部包含多个可以并行执行的Task,TaskScheduler把Task分发到Executor上执行。

这里宽窄依赖是个高频考点。窄依赖是指父RDD的每个分区最多被一个子RDD分区使用,比如map、filter、union操作,可以在同一个Stage内完成,不需要shuffle。宽依赖是指父RDD的每个分区可能被多个子RDD分区使用,典型的就是groupByKey、reduceByKey、join,这类操作必然产生shuffle,也是Stage划分的临界点。理解这个区别的价值在于——一个作业写得好不好,很大程度上就是看你能不能尽量减少宽依赖。

3.3 Flink的state和checkpoint机制

实时计算部分,Flink主要考了状态管理和checkpoint机制。题目大概是这样:在Flink流式计算中,状态(State)的作用是什么?checkpoint是怎么实现的?

State是Flink实现有状态计算的核心。比如统计每个用户每分钟的阅读时长,你需要一个Map来保存中间结果,这个Map就是State。Flink的State分两种:算子状态(Operator State)和键控状态(Keyed State)。键控状态按照key进行分区,每个key有自己的状态,适合做按用户维度的统计。

Checkpoint机制是Flink容错的关键。它基于Chandy-Lamport分布式快照算法,通过Barrier(屏障)实现。Source算子周期性地向流中注入Barrier,每个算子收到Barrier后保存自己当前的状态快照,然后将Barrier传递给下游算子。当所有算子的快照都保存完成后,一次checkpoint就完成了。

这道题的考点不在于背概念,而在于理解状态和checkpoint之间的关系——状态保存在本地(可以是内存、RocksDB等),checkpoint则是把状态定期备份到外部存储(比如HDFS)来保证故障恢复能力。一旦某个Task失败,Flink可以从最近一次成功的checkpoint恢复状态,重新开始处理数据。

3.4 Java基础题

Java基础考得比较常规,主要是集合类源码和并发编程。有一道题是HashMap的put流程和扩容机制,这个是Java八股里的经典题了——计算key的hash值,通过扰动函数(^和>>>)降低碰撞概率,定位到桶位后,如果桶位是链表就尾插,如果链表长度达到8且数组长度达到64,就转成红黑树,扩容时数组长度翻倍,元素会重新分配位置。

还有一道题是问synchronized和ReentrantLock的区别,以及volatile的作用。这些题对专门准备过Java面试的人来说算送分题,但如果你的主攻方向是纯大数据开发,平时不怎么写Java代码,这些题可能会丢分。我建议准备大数据岗的时候,Java基础别完全丢掉,HashMap、ConcurrentHashMap、线程池这三大件一定要能说出来。

4. 核心考点拆解:机器学习与算法

4.1 机器学习基础概念

掌阅笔试里机器学习题目不多,但考得比较典型。有一道题是问过拟合的解决方法,选项包括:增加训练数据、正则化、Dropout、交叉验证、减少模型复杂度。这道题基本是送分题,全部选项都是对的。

还有一道题考了特征选择的方法,给出了一些选项让你判断哪些属于过滤式、包裹式、嵌入式方法。这里的关键是理解三类方法的区别:过滤式方法(Filter)独立于学习算法,根据统计指标(卡方检验、信息增益、相关系数等)筛选特征;包裹式方法(Wrapper)把学习器性能当作特征子集的评价标准,典型有递归特征消除;嵌入式方法(Embedded)在学习器训练过程中自动进行特征选择,比如L1正则化、决策树的特征重要性。

我看到这几道题的时候有点意外,因为不少大数据岗笔试完全不碰机器学习,但掌阅考了。后来想想也合理——推荐系统是阅读类产品的核心,特征工程和模型训练都要用到这些基础知识,了解基本概念可能是对岗位的基础要求。

4.2 算法题:不靠难题靠思路

算法题部分比较友好,考了一道SQL转化的题目:给定整数数组,找出和为target的两个数的下标。这题用HashMap一次遍历就能解决,时间复杂度和空间复杂度都是O(n)。

public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) { return new int[] {map.get(complement), i}; } map.put(nums[i], i); } return new int[] {-1, -1}; }

还有一道是字符串相关的题,大概是判断括号是否有效,用栈来处理就行。整体来说,算法题难度低于大厂校招常规水平,但是要注意代码的完整性和边界条件。比如twoSum不仅要考虑有解的情况,还要考虑无解时返回什么。

我的体感是掌阅的算法题更偏向验证“你能否把思路用代码落地”,而不是考察你是否有竞赛级别的算法能力。如果你的算法基础一般,把常见的哈希表、双指针、栈、队列、二叉树遍历这些高频题型刷熟,问题就不大。

5. 主观题深度拆解:场景设计题

5.1 阅读行为分析数仓设计

主观题第一道是数仓设计题,题干给了场景:掌阅App每天产生数亿条用户行为日志,需要构建一个行为分析数仓,支撑运营看板、用户画像、推荐系统三个业务方向。要求给出数仓分层架构、核心表设计以及ETL调度方案。

这道题考察的是综合设计能力。我的思路是先把三个业务方向的需求拆清楚,运营看板需要统计类指标(DAU、MAU、人均阅读时长、付费率),用户画像需要标签类数据(性别年龄、阅读偏好、消费能力),推荐系统需要特征类数据(用户实时行为序列、书籍内容特征)。不同需求对数据的时效性和粒度要求不同,数仓分层也要相应考虑。

在表设计层面,DWD层我建议按主题拆分为用户主题、书籍主题、行为主题。用户主题表包含用户注册信息、设备信息、渠道来源等;书籍主题表包含书籍基本信息、分类、标签、上架时间等;行为主题表包含阅读行为流水,字段包括user_id、book_id、action_type、from_page、to_page、duration、timestamp等。DWS层按天汇总用户维度的阅读指标,ADS层分别输出运营看板和推荐特征需要的数据。

ETL调度可以用DolphinScheduler或者Airflow来实现,核心是管理好任务依赖关系——ODS层同步完成之后才能跑DWD层清洗任务,DWD层完成之后才能跑DWS汇总任务。调度频率方面,离线任务一般按天(T+1)调度,实时看板的需求则走Flink实时计算链路。

5.2 日志采集链路设计

第二道主观题是设计一个日志采集与处理链路,场景是:App端需要上报用户行为日志,包括启动、浏览、点击、阅读等事件,要求设计端到端的数据链路,并说明各组件选型理由。

这道题其实就是考察你对大数据生态各组件的理解和选型能力。链路大概是这样的:App端通过埋点SDK采集日志,通过HTTP或者TCP长连接上报到Nginx集群,Nginx做负载均衡和接入层缓冲,然后写入Kafka消息队列。下游有两个分支,离线链路通过Flume或Canal将Kafka数据同步到HDFS,再用Spark/Hive做批处理;实时链路通过Flink消费Kafka,进行实时ETL和指标计算,结果写入ClickHouse或者Doris供实时查询。

链路设计本身不难,难的是说清楚每一步为什么选这个组件。比如为什么中间一定要加Kafka?答案是削峰填谷——晚上8点是阅读高峰期,日志量可能是白天的数倍,如果没有消息队列缓冲,直接写入HDFS或数据库很容易把下游打爆。Kafka的高吞吐和消息持久化能力,能够在上游流量突发和下游处理能力之间起到缓冲作用。

再比如实时计算为什么选Flink而不是Spark Streaming?虽然Spark Streaming的微批模式也能实现准实时,但Flink在事件时间处理、状态管理、精确一次语义方面更成熟,对于阅读行为这种需要精确统计的场景更合适。

5.3 用户留存下降的分析框架

第三道主观题比较有意思,题干是:某个月发现掌阅App的用户次日留存率下降了5个百分点,要求设计分析方案,找出可能的原因。

这是一道开放性问题,答案没有唯一标准,但很能反映一个人的数据分析思维水平。我的回答框架是这样的:首先从数据层面确认下降趋势——是整体下降还是某个渠道、某个版本、某个用户群体在下降;然后从多个维度拆解,包括版本维度(最近是否有新版本发布)、渠道维度(是否有渠道投放质量下降)、内容维度(是否有头部书籍下架或推荐策略调整)、竞品维度(是否有竞品在同期做大规模推广)、季节维度(是否受寒暑假周期影响)。

接下来要定位到具体环节,用漏斗分析对比留存下降用户与留存正常用户在关键行为上的差异,比如新用户是否完成首次阅读、是否完成书架添加、是否收到有效的推送触达。还可以做同期群分析(Cohort Analysis),把不同周新增的用户分开对比,看是新增用户质量下降还是老用户活性下降。

最后,要结合外部信息辅助判断——比如去应用商店看最近版本的评分和用户评论,去看竞品动态,去看是否有重大舆情事件。分析思路的价值在于:它体现的不是SQL写得好不好,而是遇到业务问题时能否体系化地拆解,这恰恰是大数据岗位和纯技术研发岗位最大的区别。

6. 复盘总结与备战建议

6.1 从笔试题反推掌阅的技术栈

做完这份笔试题,大致可以反推出掌阅大数据团队在用什么技术栈。HDFS、Spark、Hive是离线计算的主力,Kafka是消息队列的标准配置,Flink用于实时计算场景,数仓建设已经走上分层规范化的路线,ClickHouse或Doris在实时报表查询中应用广泛。

如果准备后续的面试,建议重点关注几个方向:一是SQL能力,特别是窗口函数、UDTF、复杂JOIN的性能优化;二是数仓建模方法论,尤其是维度建模中的星型模型和雪花模型的适用场景;三是Spark调优经验,比如数据倾斜的解决方案、小文件问题的治理、Shuffle优化的常用手段。

6.2 给准备者的实战建议

回头看这份笔试题,我给准备掌阅或者其他互联网公司大数据岗的同学几个具体的建议。

第一,SQL一定要非常熟练,达到能用窗口函数解决任何常见业务问题的程度。我推荐的练习方式是找一个真实的业务数据集,给自己设定分析场景,比如计算用户留存、转化漏斗、复购率、TOP N排行榜、连续N天活跃等,把每种场景的SQL都写一遍,写熟练。不要只刷题不思考,遇到问题时问问自己:如果数据量放大一百倍,这条SQL还能跑得动吗?

第二,Hadoop生态的组件原理不要死记硬背,要理解设计动机。面试官问“HDFS为什么不适合存小文件”,如果你能说出“小文件会导致NameNode内存占用过高、MapReduce启动Task的开销变大、数据本地性变差”这三个层次的原因,就比单纯背诵答案要让人信服得多。

第三,主观题部分一定要把思路写清楚,不要只写结论。比如数仓设计题,你要画出分层架构图,写明每一层的输入输出和职责边界,说明为什么这样设计,有什么权衡取舍。阅卷人看的不是标准答案,而是你的思维过程和工程素养。

第四,算法题虽然难度不高,但不能掉以轻心。手写代码要保证能够一次性run通过,注意边界条件、空值判断、极端输入的处理。平时练习的时候不要用IDE自动补全,尝试在白板或者纯文本编辑器里写,模拟笔试环境。

6.3 考后的一些体会

说实话,考完掌阅这套题,我最大的感受是:它更看重“数据工程师”的综合素养,而不是某一项技能的深度。SQL要熟练,大数据组件要有全局视野,Java基础不能丢,业务理解也要有——这些都是日常工作中真正常用的东西。

有一道题我印象特别深,问的是Kafka消费者组的rebalance机制。候选人如果只是背过“消费者加入或退出时会触发rebalance”,但没有真正处理过线上rebalance导致的消费延迟问题,回答的深度会有明显差别。如果你在一线工作过,就会知道rebalance过程中所有消费者会暂停消费,这会导致消费位移停滞,消息积压,严重时还会造成重复消费。

这种“实战型”考点在掌阅笔试里不是个例。整体试卷传递的信号很清楚:我们不要只会写SQL的取数工具人,也不要只会调参调的算法工程师,我们要的是能用数据手段解决业务问题的工程师。想明白这一点,笔试的备考方向也就清晰了。

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

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

立即咨询