據(jù)筆試復(fù)盤:題型解析與備考策略)
看到“觸寶科技2017秋季校招筆試后端大數(shù)據(jù)第三批”這個(gè)標(biāo)題估計(jì)會(huì)有人覺得奇怪2017年觸寶的校招筆試現(xiàn)在翻出來講還有什么意義我當(dāng)時(shí)的感受可以說明白因?yàn)槟且慌P試的題目結(jié)構(gòu)幾乎就是市面上“后端大數(shù)據(jù)”崗位筆試的標(biāo)準(zhǔn)樣本。如果你現(xiàn)在正在準(zhǔn)備這個(gè)方向的校招把這份卷子的考察邏輯吃透再去看別家公司的筆試題思路會(huì)清晰很多很多考點(diǎn)其實(shí)是換湯不換藥。我是怎么拿到這份題的這得從當(dāng)年的秋招時(shí)間線說起。2017年9月底我投遞簡(jiǎn)歷的動(dòng)作比同寢室同學(xué)慢了一拍大部分公司的提前批已經(jīng)結(jié)束。觸寶這家的筆試通知來得也比較晚郵件標(biāo)題就是“觸寶科技2017秋季校招筆試后端大數(shù)據(jù)第三批”??吹健暗谌比齻€(gè)字我還愣了一下——原來筆試還要排隊(duì)。實(shí)際秋招高峰期互聯(lián)網(wǎng)公司筆試名額必須分批消化一是簡(jiǎn)歷量太大在線筆試平臺(tái)一下子扛不住那么多并發(fā)二來也方便面試官分批發(fā)面試邀請(qǐng)。觸寶那年秋招大致安排了四五批筆試第三批大約在10月中下旬在牛客網(wǎng)在線答題時(shí)長(zhǎng)兩小時(shí)客觀題、簡(jiǎn)答題和編程題混合在一張卷子里。我考完當(dāng)天趁著記憶還熱乎把題目和答案整理了一遍這些內(nèi)容就是下面復(fù)盤的基礎(chǔ)。1. 還原“第三批”試卷題型結(jié)構(gòu)與崗位畫像1.1 為什么校招筆試要分批次第三批意味著什么先聊一個(gè)常被忽略的問題為什么校招筆試要分批次。很多同學(xué)誤以為分批次是“補(bǔ)錄”或者“沒招滿”其實(shí)不完全是。觸寶這種體量的公司在秋招季面對(duì)的簡(jiǎn)歷量是幾萬份筆試平臺(tái)同一時(shí)間承載幾千人同時(shí)在線做題雖然技術(shù)上可行但后續(xù)的判卷、簡(jiǎn)歷篩選、面試邀約節(jié)奏都會(huì)被拖垮。分批次的真正目的是讓招聘節(jié)奏可控第一批考完面試官同步篩簡(jiǎn)歷和卷子邊面邊等后面幾批的卷子等第三批考完前面通過的人可能已經(jīng)進(jìn)了終面這時(shí)企業(yè)手上會(huì)有一個(gè)完整的候選人池綜合對(duì)比后再發(fā)offer。第三批意味著什么時(shí)間上已經(jīng)過了國(guó)慶部分同學(xué)的秋招心態(tài)開始浮躁周圍有人已經(jīng)拿到意向書正在準(zhǔn)備筆試的人多少會(huì)焦慮。但企業(yè)視角下第三批的卷子和第一批的難度并不會(huì)差很多考察重點(diǎn)一般也不會(huì)變——筆試考的是通用能力不會(huì)因?yàn)榕慰亢缶凸室夥潘N矣∠罄镉|寶的筆試邀請(qǐng)郵件里還附了一句“如時(shí)間沖突可申請(qǐng)調(diào)整批次日程”說明批次安排主要是為了保證考試秩序不存在第三批更簡(jiǎn)單這種事。1.2 試卷整體結(jié)構(gòu)與崗位技術(shù)棧信號(hào)先說題型分布。我用一張表還原當(dāng)時(shí)卷面的結(jié)構(gòu)具體分值順序可能有偏差但題型構(gòu)成和核心題源我記憶比較深題型大致題量考察方向單選多選約20題Java基礎(chǔ)、并發(fā)、網(wǎng)絡(luò)、操作系統(tǒng)、大數(shù)據(jù)基礎(chǔ)簡(jiǎn)答與問答4-5題HDFS原理、MapReduce、數(shù)據(jù)傾斜、Spark與MR對(duì)比在線編程3題數(shù)組、字符串、哈希相關(guān)的算法題場(chǎng)景設(shè)計(jì)1題從埋點(diǎn)到報(bào)表的離線數(shù)據(jù)鏈路設(shè)計(jì)從這個(gè)結(jié)構(gòu)能明顯看出這個(gè)崗位的畫像它不是純粹的后端開發(fā)也不是純粹的數(shù)據(jù)平臺(tái)開發(fā)而是“后端基礎(chǔ)大數(shù)據(jù)處理”的混合體。觸寶的產(chǎn)品線當(dāng)年以輸入法、通訊工具類應(yīng)用為主海外用戶占比高日活數(shù)據(jù)量可觀用戶行為日志、詞庫糾錯(cuò)日志、推送轉(zhuǎn)化日志都需要穩(wěn)定的大數(shù)據(jù)鏈路去處理。因此后端大數(shù)據(jù)崗要的人既要有Java服務(wù)的功底也要理解Hadoop生態(tài)的離線計(jì)算方式。整個(gè)卷面透露出的技術(shù)棧信號(hào)很清晰語言以Java為主觸寶后端當(dāng)時(shí)大量使用Java大數(shù)據(jù)側(cè)圍繞HDFS、Hive、Spark這套體系SQL能力是隱含考察點(diǎn)算法題不考特別偏的動(dòng)態(tài)規(guī)劃側(cè)重基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)和邊界處理。我當(dāng)時(shí)翻完卷子心里就有了數(shù)這家的筆試比較務(wù)實(shí)不是“我考倒你”的姿態(tài)而是“我把日常工作濃縮成題目看看你合不合適”。2. 基礎(chǔ)題復(fù)盤Java并發(fā)與網(wǎng)絡(luò)知識(shí)里的“送分題”和“陷阱題”2.1 HashMap原理為什么年年考考的核心是什么第一大題里幾乎必有HashMap這次也不例外。當(dāng)時(shí)的題目大致是“簡(jiǎn)述HashMap的put流程JDK8中引入紅黑樹解決了什么問題HashMap為什么線程不安全”這類題放在今天的筆試?yán)镆廊皇浅?秃诵目疾禳c(diǎn)分三層第一層是能不能說清put的完整流程。先計(jì)算key的hash值經(jīng)過擾動(dòng)函數(shù)高16位與低16位異或降低碰撞概率然后定位到數(shù)組下標(biāo)如果該位置為空就直接放入不為空就遍歷鏈表存在相同key則覆蓋否則尾插。JDK8里鏈表長(zhǎng)度超過閾值8且數(shù)組長(zhǎng)度達(dá)到64時(shí)鏈表轉(zhuǎn)紅黑樹目的是把最壞情況下的查找時(shí)間從O(n)降到O(logn)。第二層是要理解為什么線程不安全。我當(dāng)時(shí)的回答分了三個(gè)角度并發(fā)put可能導(dǎo)致數(shù)據(jù)覆蓋因?yàn)槎鄠€(gè)線程同時(shí)判斷相同位置為空時(shí)都會(huì)執(zhí)行插入后寫覆蓋先寫擴(kuò)容時(shí)在高并發(fā)場(chǎng)景下JDK7可能出現(xiàn)鏈表環(huán)雖然JDK8改了插入方式不再有環(huán)但數(shù)據(jù)丟失和覆蓋問題依然存在size字段也不是線程安全的。第三層是引申問法HashMap和ConcurrentHashMap的區(qū)別。這個(gè)話題后面面試也被追問過答法不能只是“一個(gè)是線程安全的一個(gè)不是”而是要說清楚ConcurrentHashMap用CASsynchronized鎖粒度更細(xì)鎖的是桶位而不是整個(gè)數(shù)組從而提升并發(fā)度。當(dāng)時(shí)筆試選擇題里也有一道類似的判斷我在這里拿了分但同考場(chǎng)有人因?yàn)闆]寫JDK8的紅黑樹閾值被扣了分。這張卷子對(duì)HashMap的追問深度說明它不是按“面試背誦題”來出而是按“你真的寫過Java代碼并處理過并發(fā)問題”來出。2.2 線程池參數(shù)問答看似基礎(chǔ)實(shí)處見功底另一道讓我印象深的題是一道選擇題加一道簡(jiǎn)答的組合選擇項(xiàng)是“Java線程池中corePoolSize、maximumPoolSize、workQueue三者如何配合當(dāng)任務(wù)數(shù)超過核心線程數(shù)時(shí)新任務(wù)先入隊(duì)列還是先創(chuàng)建新線程”正確答案是當(dāng)提交任務(wù)時(shí)如果運(yùn)行線程數(shù)小于corePoolSize創(chuàng)建新線程否則嘗試把任務(wù)放入workQueue如果隊(duì)列滿了且運(yùn)行線程數(shù)小于maximumPoolSize創(chuàng)建新線程如果隊(duì)列滿了且線程數(shù)已經(jīng)達(dá)到maximumPoolSize走拒絕策略。很多人這里記反了以為是先創(chuàng)建線程到最大值再入隊(duì)順序不對(duì)面對(duì)突發(fā)流量時(shí)的資源走勢(shì)就完全不一樣。簡(jiǎn)答部分問的是“shutdown()和shutdownNow()的區(qū)別”。shutdown會(huì)停止接收新任務(wù)隊(duì)列里的任務(wù)繼續(xù)執(zhí)行完shutdownNow會(huì)嘗試中斷正在執(zhí)行的任務(wù)并且返回尚未執(zhí)行的任務(wù)列表。我當(dāng)時(shí)還補(bǔ)充了一句調(diào)用shutdown之后配合awaitTermination可以在業(yè)務(wù)線程里等待線程池完全終止避免JVM主線程提前退出導(dǎo)致任務(wù)沒跑完。這個(gè)補(bǔ)充屬于實(shí)操經(jīng)驗(yàn)普通教科書不太會(huì)寫據(jù)我后來和同學(xué)交流這個(gè)點(diǎn)讓卷面加分不少。2.3 網(wǎng)絡(luò)與操作系統(tǒng)數(shù)據(jù)鏈路視角的考察網(wǎng)絡(luò)部分的題目也比較典型。單選題里出現(xiàn)了“TCP建立連接為什么需要三次握手而不是兩次”這類題的本質(zhì)是確認(rèn)雙方的收發(fā)能力。第一次握手服務(wù)端確認(rèn)了客戶端的發(fā)送能力第二次握手客戶端確認(rèn)了服務(wù)端的接收和發(fā)送能力第三次握手服務(wù)端確認(rèn)了客戶端的接收能力。只握兩次服務(wù)端無法確認(rèn)客戶端是否具備接收能力這會(huì)導(dǎo)致半連接狀態(tài)下資源白白占用。還有一道操作系統(tǒng)的題問的是進(jìn)程與線程的差別以及線程共享哪些資源。常規(guī)答法進(jìn)程是資源分配的基本單位線程是CPU調(diào)度的基本單位同一進(jìn)程內(nèi)的線程共享地址空間、打開的文件表、全局變量各自獨(dú)立的包括棧、寄存器狀態(tài)、程序計(jì)數(shù)器。這道題沒什么難點(diǎn)但它出現(xiàn)在“后端大數(shù)據(jù)”的卷子里有另一層含義大數(shù)據(jù)框架里進(jìn)程模型和線程模型是兩套體系比如HDFS的NameNode是進(jìn)程級(jí)服務(wù)而Spark Task是在Executor進(jìn)程內(nèi)以線程方式調(diào)度搞清楚這個(gè)關(guān)系對(duì)理解資源隔離有直接幫助。2.4 我的丟分點(diǎn)一個(gè)不常見的HTTP狀態(tài)碼這里必須分享一個(gè)丟分教訓(xùn)。有一道多選題給了好幾個(gè)HTTP狀態(tài)碼問哪些屬于重定向狀態(tài)碼。我選了302和307漏了303和304結(jié)果丟分。這不是“不會(huì)”而是對(duì)狀態(tài)碼的邊界不熟。304 Not Modified本質(zhì)上是客戶端緩存命中的響應(yīng)歸類上屬于重定向類因?yàn)榉?wù)器并不返回響應(yīng)體而是告訴客戶端“用你緩存里的版本”。這個(gè)坑這幾年在很多技術(shù)社區(qū)里也被反復(fù)討論所以放在這里重點(diǎn)提醒刷題不要只盯大熱點(diǎn)冷門狀態(tài)碼、端口號(hào)、默認(rèn)配置這類容易被輕視的知識(shí)點(diǎn)在校招筆試?yán)锬芷鸬胶艽蟮暮Y人作用。觸寶這批卷子的選擇題本身不算難但覆蓋面廣任何一個(gè)領(lǐng)域的“半瓶水”都會(huì)在這里現(xiàn)形。3. 大數(shù)據(jù)專題HDFS、Spark與數(shù)據(jù)傾斜的標(biāo)準(zhǔn)化答案3.1 HDFS寫入全流程與副本放置策略大數(shù)據(jù)專題的簡(jiǎn)答題第一道就是經(jīng)典的“描述HDFS文件的寫入過程”。這道題今天依然是絕大多數(shù)后端大數(shù)據(jù)校招的必考題我把當(dāng)時(shí)答題的框架寫在這里客戶端向NameNode發(fā)起寫請(qǐng)求NameNode檢查權(quán)限、文件是否已存在以及目錄結(jié)構(gòu)是否合法。通過檢查后NameNode在內(nèi)存中創(chuàng)建文件元數(shù)據(jù)記錄并返回可以寫入的DataNode列表??蛻舳税盐募凑諌K大小默認(rèn)128MB切分第一個(gè)塊的寫入過程是這樣的客戶端從返回的DataNode列表中選出第一個(gè)節(jié)點(diǎn)建立TCP連接將數(shù)據(jù)包以流水線方式依次傳給下一節(jié)點(diǎn)。第一個(gè)DataNode把數(shù)據(jù)落盤并傳給第二個(gè)第二個(gè)落盤再傳給第三個(gè)。每寫入一個(gè)chunk默認(rèn)512字節(jié)客戶端會(huì)計(jì)算校驗(yàn)和隨數(shù)據(jù)一起傳輸DataNode校驗(yàn)后存儲(chǔ)最終所有副本寫完后DataNode向客戶端發(fā)送確認(rèn)客戶端再通知NameNode更新元數(shù)據(jù)。答完流程之后我還補(bǔ)充了副本放置策略第一個(gè)副本放在客戶端所在節(jié)點(diǎn)的本地磁盤如果客戶端不在集群內(nèi)則隨機(jī)挑選一個(gè)負(fù)載不高的節(jié)點(diǎn)第二個(gè)副本放在不同機(jī)架的節(jié)點(diǎn)第三個(gè)副本與第二個(gè)放在同機(jī)架的不同機(jī)器上其余副本繼續(xù)隨機(jī)選擇。這樣做的平衡點(diǎn)是既能容忍機(jī)架級(jí)故障又不會(huì)因?yàn)榭鐧C(jī)架寫太多數(shù)據(jù)導(dǎo)致網(wǎng)絡(luò)帶寬消耗過大。這道題的考察意圖不只是讓學(xué)生背流程而是觀察答題者有沒有工程視角。我當(dāng)時(shí)加了“如果最后一個(gè)Block小于128MB不會(huì)額外占用一個(gè)完整塊大小只需實(shí)際大小”這個(gè)細(xì)節(jié)就比泛泛而談的人多了一層可信度。3.2 計(jì)算引擎之辯MapReduce與Spark的思維差異簡(jiǎn)答題的第二題是“MapReduce和Spark在計(jì)算模型上的主要區(qū)別各自適用于什么場(chǎng)景”。這個(gè)問題的答題質(zhì)量直接反映一個(gè)人是不是真的用離線計(jì)算處理過數(shù)據(jù)而不僅僅是看過概念。我的答題思路從三方面展開。執(zhí)行模型上MapReduce每個(gè)任務(wù)都有固定的Map-Shuffle-Reduce階段中間結(jié)果必須落盤到HDFS容錯(cuò)依賴落盤恢復(fù)Spark把計(jì)算過程構(gòu)造成DAG中間結(jié)果優(yōu)先存內(nèi)存內(nèi)存不足才溢寫磁盤迭代類作業(yè)不需要每次都刷盤所以小迭代任務(wù)的速度優(yōu)勢(shì)明顯。調(diào)度粒度方面MapReduce以進(jìn)程為單位每次啟動(dòng)JVM開銷很大Spark以線程為單位在Executor內(nèi)部調(diào)度Task減少了資源啟動(dòng)的損耗。數(shù)據(jù)處理模型上MapReduce是典型的兩階段批處理而Spark除了批處理還通過RDD/DataFrame統(tǒng)一了批流兩類計(jì)算語義Spark Streaming用微批的方式模擬實(shí)時(shí)計(jì)算。我還特意提了一個(gè)“適合場(chǎng)景”的觀點(diǎn)如果集群主要跑T1的定時(shí)報(bào)表任務(wù)量穩(wěn)定MapReduce的穩(wěn)定性和容易排查的特點(diǎn)足夠用但如果需要反復(fù)迭代、機(jī)器學(xué)習(xí)的特征計(jì)算、或者需要更快地響應(yīng)臨時(shí)分析需求Spark更合適。這個(gè)觀點(diǎn)也許沒有標(biāo)準(zhǔn)答案那么“正確”但能體現(xiàn)你真的思考過選型問題。據(jù)說后來我拿到的面試機(jī)會(huì)里面試官對(duì)這段回答印象比較深問了好幾句基于場(chǎng)景的討論。3.3 數(shù)據(jù)傾斜一道看似簡(jiǎn)單卻勸退很多人的場(chǎng)景題簡(jiǎn)答題里還有一道題干大概是“一個(gè)Spark任務(wù)在groupBy某個(gè)字段時(shí)某個(gè)key的量明顯多于其他key導(dǎo)致整個(gè)任務(wù)卡在最后一個(gè)stage如何定位和處理”這是大數(shù)據(jù)面試的經(jīng)典話題當(dāng)時(shí)在線上筆試?yán)镆院?jiǎn)答題形式出現(xiàn)我是從定位和處理兩個(gè)角度分開寫的。定位方面我寫了三個(gè)步驟先看Spark UI里各個(gè)Task的Shuffle Read大小傾斜的任務(wù)通常表現(xiàn)為某幾個(gè)Task讀到的數(shù)據(jù)量是其他Task的幾十倍再看Stage的劃分確認(rèn)傾斜發(fā)生在Shuffle之后最后確認(rèn)傾斜的key是什么可以通過在代碼里對(duì)key做count或直接在Spark UI日志里看到記錄數(shù)異常的Task編號(hào)對(duì)應(yīng)的數(shù)據(jù)特征。處理方面我提供了三種策略。第一是兩階段聚合對(duì)傾斜的key加一個(gè)隨機(jī)前綴先做局部聚合再去掉前綴做全局聚合第二是廣播優(yōu)化當(dāng)小表數(shù)據(jù)量不太大時(shí)比如幾十MB把維度表用broadcast join廣播出去避免Shuffle帶來的大key問題第三是過濾異常key如果傾斜的數(shù)據(jù)是無效數(shù)據(jù)比如空值、默認(rèn)值、爬蟲標(biāo)識(shí)可以在不影響業(yè)務(wù)口徑的前提下先過濾掉。我還補(bǔ)充了“如果傾斜是因?yàn)閿?shù)據(jù)本身業(yè)務(wù)特性導(dǎo)致的加鹽法最穩(wěn)如果只是小表join大表優(yōu)先考慮廣播”。這道題不要求寫出完整代碼但能用工程語言把定位路徑和處理手段講清楚的人不多。很多人的答案只有“加隨機(jī)數(shù)”四個(gè)字缺少定位思路和適用邊界也就是所謂“知其然不知其所以然”。這種區(qū)分度就是筆試高分的分水嶺。4. 三道在線編程題邊界條件比算法思想更考驗(yàn)人4.1 編程題一合并兩個(gè)有序數(shù)組題目大意給定兩個(gè)有序整數(shù)數(shù)組nums1和nums2nums1的長(zhǎng)度為mn前m個(gè)元素是有效內(nèi)容nums2的長(zhǎng)度為n把nums2合并進(jìn)nums1結(jié)果仍有序要求空間復(fù)雜度O(1)。這是一個(gè)雙指針從后往前走的經(jīng)典題。很多人的第一反應(yīng)是新建一個(gè)數(shù)組再sort或者用額外O(mn)的空間去合并但題目明確限制了空間。當(dāng)時(shí)的實(shí)現(xiàn)思路是從nums1的m-1位置和nums2的n-1位置開始往前遍歷誰大誰就放到nums1的末尾從mn-1位置往前填直到nums2全部被放完。這樣不會(huì)覆蓋nums1還沒處理到的有效數(shù)據(jù)。public void merge(int[] nums1, int m, int[] nums2, int n) { int p1 m - 1, p2 n - 1, p m n - 1; while (p2 0) { if (p1 0 nums1[p1] nums2[p2]) { nums1[p--] nums1[p1--]; } else { nums1[p--] nums2[p2--]; } } }這道題考察的核心不是雙指針本身而是“剩余數(shù)據(jù)處理”的邊界條件。while循環(huán)退出條件是p2 0代表nums2已經(jīng)被完全合并nums1剩下的那些前半段元素天然有序不需要額外處理。很多人寫while(p1 0 p2 0)然后漏掉單獨(dú)處理p2剩余的情況就會(huì)在部分測(cè)試用例上報(bào)錯(cuò)。我當(dāng)時(shí)提交后自己又檢查了一遍邊界并寫了兩個(gè)用例去驗(yàn)證一個(gè)是nums1為空一個(gè)是nums2為空。這種測(cè)試習(xí)慣比代碼本身更能提升得分。4.2 編程題二找出數(shù)組中缺失的最小正整數(shù)題目大意給定一個(gè)未排序的整數(shù)數(shù)組找出其中沒有出現(xiàn)的最小的正整數(shù)。要求時(shí)間復(fù)雜度O(n)空間復(fù)雜度O(1)。這道題在今天已經(jīng)算高頻題但2017年出現(xiàn)在校招卷子里區(qū)分度比較明顯。正確思路是原地哈希數(shù)值為i的元素應(yīng)該放到下標(biāo)i-1的位置。遍歷數(shù)組當(dāng)當(dāng)前元素在[1, n]范圍內(nèi)且不在正確位置上時(shí)與目標(biāo)位置交換。交換完成后再遍歷一次第一個(gè)不滿足nums[i] i 1的位置就是缺失的最小正整數(shù)如果全部滿足返回n 1。public int firstMissingPositive(int[] nums) { int n nums.length; for (int i 0; i n; i) { while (nums[i] 0 nums[i] n nums[nums[i] - 1] ! nums[i]) { int tmp nums[nums[i] - 1]; nums[nums[i] - 1] nums[i]; nums[i] tmp; } } for (int i 0; i n; i) { if (nums[i] ! i 1) return i 1; } return n 1; }這里最關(guān)鍵的一段是while里的三個(gè)條件只處理[1,n]范圍內(nèi)的數(shù)需要等待當(dāng)前數(shù)還沒放到正確位置時(shí)持續(xù)交換如果目標(biāo)位置已經(jīng)有相同值說明重復(fù)不進(jìn)入交換避免死循環(huán)。這道題我見過很多同學(xué)在“重復(fù)元素導(dǎo)致死循環(huán)”上卡住筆試現(xiàn)場(chǎng)時(shí)間緊迫更容易忽略。建議所有備戰(zhàn)校招的人遇到數(shù)組原地交換類的題第一件事就是問自己兩個(gè)問題會(huì)不會(huì)越界會(huì)不會(huì)因?yàn)橹貜?fù)值死循環(huán)4.3 編程題三字符串形式的IP地址合法性校驗(yàn)題目大意給定一個(gè)字符串判斷它是否是合法的IPv4地址。要求考慮前導(dǎo)零、數(shù)字范圍、非法字符等情況。這類字符串處理的題不算難但邊界條件極其細(xì)致特別適合筆試篩選。核心判斷邏輯是按點(diǎn)分割后必須正好4段每段不能為空每段只能包含數(shù)字每段長(zhǎng)度不超過3轉(zhuǎn)換成int后在0到255之間如果長(zhǎng)度大于1不能以0開頭也就是不能有前導(dǎo)零。我當(dāng)時(shí)先講清楚思路再寫循環(huán)判斷。當(dāng)時(shí)我忽略了一個(gè)點(diǎn)字符串里的空白字符。如果輸入是192.168.1.1 末尾多了一個(gè)空格很多語言里split之后最后一段是1 直接轉(zhuǎn)int會(huì)報(bào)異常。雖然題目未必包含這個(gè)測(cè)試用例但如果能主動(dòng)處理并寫出“先trim再分割”的細(xì)節(jié)會(huì)顯得經(jīng)驗(yàn)更足。我在做題時(shí)加了一句注意雖然浪費(fèi)了三十秒但為后面說明邊界處理思路做好了鋪墊。4.4 做題順序與時(shí)間分配的實(shí)操體會(huì)三題編程題我用了大約50分鐘前兩題各15分鐘IP校驗(yàn)20分鐘。整體耗時(shí)比預(yù)想多原因是在IP校驗(yàn)上反復(fù)檢查了邊界。這里分享一個(gè)實(shí)戰(zhàn)經(jīng)驗(yàn)在線筆試的編程題千萬不要在一道題上死磕超過30分鐘。如果30分鐘還沒通過全部用例先提交部分分回頭再調(diào)。觸寶這套卷子總分里編程題占比較高但拿到部分分依然比零分強(qiáng)很多。我當(dāng)時(shí)遇到一道排序題因?yàn)闀r(shí)間不夠只寫了思路靠簡(jiǎn)答題的完整答案把總分拉了上來。另一條心得是編譯器通常沒有自動(dòng)構(gòu)造函數(shù)提示很多考題平臺(tái)也不允許import額外的包。平時(shí)練習(xí)時(shí)一定要適應(yīng)“裸寫代碼”的節(jié)奏不要依賴IDE的智能提示。Map、List這些常用類的API必須手寫熟練否則在線筆試會(huì)很吃虧。5. 場(chǎng)景設(shè)計(jì)題從埋點(diǎn)到報(bào)表的離線數(shù)據(jù)鏈路設(shè)計(jì)5.1 題干還原與隱含的考察點(diǎn)最后的大題是純場(chǎng)景設(shè)計(jì)題干大概是這樣的“觸寶輸入法App會(huì)記錄用戶的行為日志、詞庫糾錯(cuò)日志、崩潰日志等多種數(shù)據(jù)目前日均產(chǎn)生約10億條日志需要支撐運(yùn)營(yíng)后臺(tái)每天查看熱詞榜、用戶留存、地域分布等報(bào)表同時(shí)支持小時(shí)級(jí)的趨勢(shì)變化。請(qǐng)?jiān)O(shè)計(jì)一套數(shù)據(jù)采集、存儲(chǔ)、計(jì)算、展示的完整鏈路并說明關(guān)鍵環(huán)節(jié)的選型理由?!边@道題是全卷最好的一道題因?yàn)闆]有任何標(biāo)準(zhǔn)答案。它的考察點(diǎn)不只是一個(gè)技術(shù)棧而是你能不能把從客戶端到報(bào)表端的全鏈路串起來并且在關(guān)鍵環(huán)節(jié)給出合理取舍。而且題干特意給了“小時(shí)級(jí)趨勢(shì)變化”這個(gè)約束這意味著不能只設(shè)計(jì)一個(gè)純T1的離線數(shù)倉(cāng)還需要考慮小時(shí)級(jí)任務(wù)如何調(diào)度、結(jié)果如何加速。5.2 我當(dāng)時(shí)的答題框架我的答案分五層采集層、緩存與傳輸層、存儲(chǔ)層、計(jì)算層、服務(wù)與展示層并畫了一條鏈路客戶端日志 - Nginx統(tǒng)一接入 - Kafka - Flume落HDFS - Hive數(shù)倉(cāng)分層 - Spark SQL跑計(jì)算 - 結(jié)果入MySQL/Redis - 報(bào)表平臺(tái)查詢。先寫采集層??蛻舳巳罩窘y(tǒng)一通過HTTP接口上報(bào)到Nginx集群日志格式約定為JSON方便后續(xù)解析。這里選Nginx是因?yàn)樗垢卟l(fā)、配置簡(jiǎn)單而且可以作為第一層負(fù)載入口。Nginx的access_log不能直接作為業(yè)務(wù)日志管道所以業(yè)務(wù)日志從Nginx轉(zhuǎn)發(fā)到Kafka而不是寫本地文件再采集這樣能減少一個(gè)環(huán)節(jié)。然后寫Kafka的作用。Kafka主要做削峰和異步緩沖。日志上報(bào)具有明顯的晝夜波峰直接寫HDFS的話落盤壓力會(huì)隨流量波動(dòng)而Kafka能穩(wěn)定承接讓下游消費(fèi)者按自己的速率拉取數(shù)據(jù)。分區(qū)數(shù)按Topic的數(shù)據(jù)量來估算保證每個(gè)分區(qū)消費(fèi)吞吐量合理。同時(shí)Kafka還能作為多下游復(fù)用的數(shù)據(jù)源比如實(shí)時(shí)告警的Flink任務(wù)和離線Hive任務(wù)可以消費(fèi)同一個(gè)Topic互不干擾。存儲(chǔ)層用的是HDFS按天和按小時(shí)二級(jí)分區(qū)比如/log_date20250101/hour10/這樣的目錄結(jié)構(gòu)。分區(qū)的好處是查詢時(shí)能快速裁剪數(shù)據(jù)不用掃描全量。Hive表通過外部表方式關(guān)聯(lián)HDFS目錄業(yè)務(wù)數(shù)據(jù)模型在Hive里分為三層ODS層原樣存儲(chǔ)日志原始數(shù)據(jù)DWD層做清洗和維度統(tǒng)一比如統(tǒng)一設(shè)備ID、識(shí)別用戶ID去掉無效字段ADS層匯聚應(yīng)用層指標(biāo)如每日熱詞TopN、日活、留存率。分層的好處是每層職責(zé)單一ODS不動(dòng)原始數(shù)據(jù)DWD改壞了不影響源頭ADS直接面向業(yè)務(wù)查詢。計(jì)算層主用Hive和Spark SQL。日級(jí)別的報(bào)表用Hive定時(shí)任務(wù)即可小時(shí)級(jí)別的趨勢(shì)指標(biāo)用Spark SQL因?yàn)樗膱?zhí)行速度更快能保證小時(shí)任務(wù)在每小時(shí)的05分前后產(chǎn)出不影響運(yùn)營(yíng)查看。計(jì)算時(shí)采用增量計(jì)算狀態(tài)累積的策略即只算新到一個(gè)小時(shí)的數(shù)據(jù)再與歷史累計(jì)狀態(tài)合并避免每天重復(fù)跑全量。服務(wù)與展示層是MySQL存維度表、配置表和報(bào)表結(jié)果Redis存熱詞榜、實(shí)時(shí)看板這類需要秒級(jí)返回的查詢。報(bào)表平臺(tái)的后端接口從Redis或MySQL讀取結(jié)果數(shù)據(jù)提供日環(huán)比、周同比等對(duì)比邏輯。MySQL的表結(jié)構(gòu)按指標(biāo)維度設(shè)計(jì)行數(shù)不會(huì)太大不需要分庫分表但必須加索引。5.3 這道題的隱藏加分點(diǎn)與我的補(bǔ)充我在答完主鏈路后還補(bǔ)了三個(gè)容易被忽視的環(huán)節(jié)數(shù)據(jù)質(zhì)量監(jiān)控、精確去重方案、任務(wù)失敗重跑機(jī)制。數(shù)據(jù)質(zhì)量監(jiān)控的思路是每天計(jì)算任務(wù)結(jié)束后拿當(dāng)天的總?cè)罩玖颗c昨天的同時(shí)段數(shù)據(jù)做環(huán)比如果波動(dòng)超過50%說明上游采集可能出現(xiàn)問題需要馬上告警。另外在DWD層加一張數(shù)據(jù)質(zhì)量校驗(yàn)表記錄每個(gè)分區(qū)記錄數(shù)、空值比例、異常值比例一旦指標(biāo)異常報(bào)表展示前就攔截。這個(gè)點(diǎn)許多沒做過生產(chǎn)環(huán)境數(shù)據(jù)的同學(xué)是想不到的。精確去重是我單獨(dú)強(qiáng)調(diào)的。日活的UV統(tǒng)計(jì)不能用簡(jiǎn)單的count(distinct device_id)刷全表因?yàn)閿?shù)據(jù)量大時(shí)開銷太高。可以每天把當(dāng)天的device_id字段按天去重后寫入bitmap或者用Spark的approx_count_distinct做近似去重誤差控制在1%以內(nèi)。如果業(yè)務(wù)口徑要求嚴(yán)格的精確UV再用bitmap方案如果只關(guān)心量級(jí)趨勢(shì)用HyperLogLog近似算法性價(jià)比更高。這個(gè)取舍本身就是大數(shù)據(jù)工程里常見的“精確性換性能”的權(quán)衡。任務(wù)失敗重跑和調(diào)度依賴我提到用調(diào)度平臺(tái)管理事件驅(qū)動(dòng)式任務(wù)依賴ODS導(dǎo)入完成后才觸發(fā)DWD清洗DWD完成后才觸發(fā)ADS指標(biāo)計(jì)算不會(huì)出現(xiàn)上游還沒跑完下游就開跑的空數(shù)據(jù)情況。失敗后要支持指定分區(qū)重跑而不是全表重算這對(duì)資源消耗和SLA都重要。這道題最終得分取決于綜合表達(dá)鏈路完整性是第一檔選型取舍是第二檔隱藏問題意識(shí)是第三檔。大多數(shù)人能畫出“日志-Kafka-HDFS-Hive-報(bào)表”的主干但能把監(jiān)控、去重、調(diào)度依賴補(bǔ)全的人很少而這幾個(gè)點(diǎn)恰好是日常工作里每天都要面對(duì)的事情。6. 從這份試卷反推備考策略幾年后回看依然有效的判斷6.1 從試卷看“后端大數(shù)據(jù)”崗位到底要什么人復(fù)盤完所有題目可以直接總結(jié)出這個(gè)崗位的能力模型第一Java基礎(chǔ)必須扎實(shí)HashMap、并發(fā)、網(wǎng)絡(luò)這些通用后端知識(shí)不能被問倒第二要熟悉Hadoop生態(tài)的常用組件原理尤其是HDFS和Spark這類每天打交道的系統(tǒng)得清楚它們的運(yùn)行機(jī)制而不是只會(huì)調(diào)用API第三動(dòng)手編碼能力必須有保障手寫數(shù)組、字符串、哈希相關(guān)的算法題不能發(fā)怵第四要有真實(shí)的數(shù)據(jù)工程體感知道日志從產(chǎn)生到展示會(huì)經(jīng)過哪些環(huán)節(jié)哪些環(huán)節(jié)容易出問題。這套能力模型不只是觸寶一家如此。我后來投過不少做用戶增長(zhǎng)、內(nèi)容分發(fā)、智能硬件數(shù)據(jù)的公司筆試題的框架基本都是“后端基礎(chǔ)大數(shù)據(jù)原理算法題場(chǎng)景設(shè)計(jì)”四件套差別只在深度和選型偏好上。所以備考重點(diǎn)不在于猜測(cè)某家公司會(huì)出什么原題而在于把這份能力模型里的短板補(bǔ)齊。6.2 給正在備考校招的同學(xué)幾條具體建議第一Java基礎(chǔ)用“高頻問答清單”來查漏。HashMap、JVM內(nèi)存區(qū)域、類加載、線程池參數(shù)、synchronized與ReentrantLock的區(qū)別這些是后端筆試的骨干內(nèi)容每天花半小時(shí)針對(duì)每個(gè)主題試著不看資料講一遍能發(fā)現(xiàn)自己記憶里含糊的地方。第二Hive SQL一定要寫得熟。實(shí)際筆試?yán)锊粫?huì)直接出“請(qǐng)寫出一條SQL”但場(chǎng)景題里大量涉及指標(biāo)的口徑拆解如果你能順手寫出“近7日每日活躍用戶數(shù)”這類SQL在面試?yán)镆彩羌臃猪?xiàng)。第三手寫代碼練習(xí)時(shí)刻意練習(xí)“先寫邊界條件再寫主邏輯”的習(xí)慣很多筆試題因?yàn)檫壿嬚_但遺忘空數(shù)組、空字符串、首尾空格、負(fù)數(shù)等邊界情況導(dǎo)致不能全部通過非??上?。第四準(zhǔn)備一份“場(chǎng)景設(shè)計(jì)答題模板”不光是背下來而是真正理解每一層的作用然后針對(duì)不同業(yè)務(wù)去套用和變化。6.3 我的個(gè)人體會(huì)這批筆試過去好幾年了但每次有師弟師妹問我校招怎么準(zhǔn)備后端大數(shù)據(jù)方向我都會(huì)翻出這份復(fù)盤給他們看。不是因?yàn)樗卸嚯y而是它代表了一類典型的、務(wù)實(shí)的校招筆試風(fēng)格不考偏題怪題不搞腦筋急轉(zhuǎn)彎每一道題都在考察“你平時(shí)寫代碼、搭系統(tǒng)、跑數(shù)據(jù)時(shí)有沒有認(rèn)真思考過為什么”。我至今還記得在答場(chǎng)景設(shè)計(jì)題時(shí)我腦中回放的是自己課程設(shè)計(jì)里搭數(shù)倉(cāng)時(shí)踩過的坑忘了對(duì)ODS數(shù)據(jù)做校驗(yàn)結(jié)果報(bào)表里出現(xiàn)了翻倍的日活數(shù)據(jù)排查了整整一天。那次踩坑最終成了筆試答案里“數(shù)據(jù)質(zhì)量監(jiān)控”那個(gè)分點(diǎn)的來源。很多經(jīng)驗(yàn)?zāi)阋詾闆]用會(huì)在將來某個(gè)時(shí)刻突然變得重要。校招筆試在短期內(nèi)看是一場(chǎng)競(jìng)爭(zhēng)長(zhǎng)期看更像是對(duì)過去積累的一次檢驗(yàn)。真正讓你拿到offer的不是考前的突擊而是你平時(shí)寫每一行代碼、處理每一份數(shù)據(jù)時(shí)留下的思考深度。