平臺研發(fā)筆試復(fù)盤:C++/OS/網(wǎng)絡(luò)考點與準備清單)
聊到阿里巴巴2015基礎(chǔ)平臺研發(fā)工程師實習生筆試很多人的第一反應(yīng)是當年這些題放到現(xiàn)在依然能勸退一大片。那時候阿里正在大規(guī)模建設(shè)自研基礎(chǔ)設(shè)施基礎(chǔ)平臺研發(fā)崗做的就是存儲、消息、調(diào)度、網(wǎng)絡(luò)這類底層組件所以筆試幾乎不考花哨的項目經(jīng)歷上來全是計算機基礎(chǔ)硬功夫。這篇文章不打算復(fù)述具體的原始試卷而是把這類筆試背后真正想考察的東西拆開來講考了哪些方向、每類題目背后的原理是什么、如果現(xiàn)在讓我重新準備我會怎么復(fù)習。這篇內(nèi)容適合幾類人看正在準備大廠基礎(chǔ)架構(gòu)方向?qū)嵙暶嬖嚨耐瑢W、剛接觸后端但想往底層走的研發(fā)以及純粹想知道“阿里基礎(chǔ)平臺筆試到底有多硬核”的吃瓜群眾。無論你是哪一類看完至少能少走很多彎路。1. 基礎(chǔ)平臺研發(fā)實習生筆試到底在考什么1.1 崗位畫像基礎(chǔ)平臺研發(fā)是做什么的先說清楚崗位定位?;A(chǔ)平臺研發(fā)工程師在阿里內(nèi)部不是一個寫業(yè)務(wù)接口的崗位而是負責支撐所有上層業(yè)務(wù)的基礎(chǔ)設(shè)施比如分布式文件系統(tǒng)、KV存儲、消息中間件、服務(wù)注冊中心、容器調(diào)度平臺、網(wǎng)絡(luò)接入層。這些組件的特點是并發(fā)量大、延遲要求極致、故障影響面廣一旦出問題就是全站級別的故障。所以這個崗位對候選人的要求非?!暗讓印薄P枰愣瓹/C或Java底層運行機制懂操作系統(tǒng)如何管理內(nèi)存和調(diào)度線程懂TCP/IP協(xié)議棧的細節(jié)懂Linux環(huán)境下如何排查問題。2015年前后阿里還在大力投入自研中間件和存儲系統(tǒng)實習生筆試自然也更偏向這些底層能力而不是框架使用經(jīng)驗。1.2 筆試整體定位基本功篩選器實習生筆試不像社招面試那樣有大量項目深度追問它更像一個篩子先把不具備基礎(chǔ)能力的候選人過濾掉。2015年那會兒的筆試題型大致包括選擇題、簡答題、手寫代碼題內(nèi)容覆蓋C語言內(nèi)存、操作系統(tǒng)并發(fā)、網(wǎng)絡(luò)協(xié)議、數(shù)據(jù)結(jié)構(gòu)算法、Linux命令。整體難度不算變態(tài)但覆蓋面廣任何一塊有短板都會丟分。為什么這么設(shè)計因為基礎(chǔ)平臺研發(fā)的日常工作就是和這些底層概念打交道。如果你連“進程和線程的區(qū)別”都說不清楚寫生產(chǎn)者消費者模型時不知道該用鎖還是信號量那后面培養(yǎng)成本會非常高。筆試就是在用最短的時間驗證候選人是否具備底層系統(tǒng)的“常識感”。2. 一塊一塊拆解筆試核心考點2.1 C/C指針、內(nèi)存和不過時的底層功C/C幾乎是每年必考原因很簡單存儲、網(wǎng)絡(luò)這類基礎(chǔ)組件大量使用C/C編寫對性能的要求決定了你沒法完全避開裸指針和手動內(nèi)存管理。常見考題有這樣幾類sizeof的運算結(jié)果比如sizeof(指針)、sizeof(數(shù)組名)的區(qū)別內(nèi)存對齊規(guī)則以及結(jié)構(gòu)體大小的計算指針數(shù)組、數(shù)組指針、函數(shù)指針的聲明和用法內(nèi)存泄漏、懸空指針、野指針的成因手寫memcpy、strcpy這類基礎(chǔ)函數(shù)我記得很多人在sizeof上翻車。比如一個int *psizeof(p)在64位系統(tǒng)上是8字節(jié)但sizeof(*p)是4字節(jié)。這個如果不理解sizeof是編譯期運算符只看表象就容易記混。內(nèi)存對齊更是典型考點struct { char a; int b; }在常見32位編譯器下大小不是5而是8因為int要對齊到4字節(jié)邊界char a后面會填充3個字節(jié)。這類題目的關(guān)鍵不是背答案而是理解“變量在內(nèi)存里到底怎么排布”。建議準備時動手寫幾個結(jié)構(gòu)體用offsetof和printf打印地址偏移徹底搞懂對齊規(guī)則比死記硬背強得多。2.2 操作系統(tǒng)進程線程與并發(fā)原語基礎(chǔ)平臺研發(fā)每天要處理高并發(fā)所以操作系統(tǒng)里的進程線程模型、同步互斥、死鎖、調(diào)度這些都可能是考點。常見題目進程和線程的區(qū)別從資源分配和調(diào)度的角度講死鎖產(chǎn)生的四個必要條件怎樣破壞死鎖生產(chǎn)者消費者模型用信號量和互斥鎖分別怎么實現(xiàn)什么是競爭條件什么場景下需要原子操作用戶態(tài)和內(nèi)核態(tài)的區(qū)別系統(tǒng)調(diào)用的開銷來自哪里這類題靠背概念也能拿一部分分但要拿高分必須會舉例。比如解釋進程線程區(qū)別時可以說進程是資源分配的基本單位線程是CPU調(diào)度的基本單位同一個進程內(nèi)的線程共享地址空間和文件描述符但也因此需要同步機制保護共享數(shù)據(jù)。我建議把生產(chǎn)者消費者模型親手寫一遍用C語言pthread實現(xiàn)或者用Java的wait/notify實現(xiàn)理解“緩沖區(qū)滿時生產(chǎn)者等待緩沖區(qū)空時消費者等待”這兩個條件。筆試時如果讓你寫偽代碼至少不會卡殼。2.3 網(wǎng)絡(luò)TCP/IP是平臺工程師的母語網(wǎng)絡(luò)知識在基礎(chǔ)平臺研發(fā)面試里權(quán)重很高尤其是TCP/IP協(xié)議。2015年時的筆試題出現(xiàn)過不少關(guān)于TCP狀態(tài)、連接管理、阻塞與非阻塞IO的問題例如TCP三次握手、四次揮手的過程為什么需要TIME_WAITTCP和UDP的區(qū)別什么場景選哪個粘包與拆包的原因和解決辦法什么是阻塞IO、非阻塞IO、IO多路復(fù)用select、poll、epoll的區(qū)別和適用場景很多人能背出三次握手的序列但不一定理解為什么是三次而不是兩次。本質(zhì)是防止已經(jīng)失效的連接請求突然又傳到服務(wù)器導致服務(wù)器建立無用連接。如果要答好這道題最好從“雙方都需要確認對方的收發(fā)能力”這個角度來解釋。TIME_WAIT也是一個高頻考點。主動關(guān)閉連接的一方會進入TIME_WAIT狀態(tài)等待2MSL原因一是保證最后一個ACK能到達對方二是讓舊連接的報文在網(wǎng)絡(luò)中自然消失避免影響新連接?;A(chǔ)平臺研發(fā)經(jīng)常要處理高并發(fā)短連接TIME_WAIT過多就是一個經(jīng)典問題面試官很樂意從一個知識點延伸到線上排查。2.4 數(shù)據(jù)結(jié)構(gòu)與算法不刷題真的不行基礎(chǔ)平臺研發(fā)的筆試算法題不會特別偏但很看代碼基本功。常見類型包括鏈表反轉(zhuǎn)、鏈表判環(huán)、合并兩個有序鏈表數(shù)組去重、Top K、K個一組翻轉(zhuǎn)鏈表二叉樹前中后序遍歷、層序遍歷、最近公共祖先手寫快速排序、歸并排序并分析復(fù)雜度哈希表的實現(xiàn)原理哈希沖突有哪些解決辦法這些題猛一看都是LeetCode基礎(chǔ)題但筆試要求手寫沒有IDE提示還要注意變量名和邊界條件難度就上來了。比如鏈表反轉(zhuǎn)遞歸和迭代兩種寫法都要會如果面試官讓你寫“每K個節(jié)點一組反轉(zhuǎn)”就非??简炴湵聿僮鞯那逦取T?015年那個時間點更強調(diào)對經(jīng)典算法的理解。準備時不要只刷題要把每種排序的時間復(fù)雜度、穩(wěn)定性、適用場景寫下來能講清楚為什么快排平均是O(n log n)最壞為什么退化成O(n^2)。2.5 Linux與調(diào)試平臺研發(fā)的日常工具作為基礎(chǔ)平臺研發(fā)Linux是主要工作環(huán)境。筆試里可能出現(xiàn)一些Linux命令和系統(tǒng)調(diào)試方法難度不高但很實用。比如如何查看進程的CPU和內(nèi)存占用top、ps、free如何查看端口監(jiān)聽狀態(tài)netstat、ss如何查看文件被哪個進程占用lsof如何使用gdb查看堆棧strace跟蹤系統(tǒng)調(diào)用如何查看網(wǎng)絡(luò)連接狀態(tài)定位TCP連接數(shù)過高的問題這些題不會讓你寫很長命令而是通過場景題來考察比如“一臺機器CPU使用率飆升你如何定位是哪個進程、哪段代碼引起的”。如果你只是機械地背過命令不理解top輸出里的%CPU、load average含義很容易答偏。我當年遇到過一道比較有區(qū)分度的題線上服務(wù)出現(xiàn)大量TIME_WAIT連接給出排查思路。正確的路徑是先用ss -s或netstat統(tǒng)計連接狀態(tài)再用ss -tan state time-wait查看具體地址然后根據(jù)業(yè)務(wù)是短連接還是長連接決定調(diào)整tcp_tw_reuse參數(shù)還是優(yōu)化服務(wù)端連接池。這個排查鏈路現(xiàn)在看依然經(jīng)典。2.6 分布式基礎(chǔ)面試中體現(xiàn)加分項2015年基礎(chǔ)平臺筆試對分布式的要求還不算深入但已經(jīng)有概念性題目比如CAP理論怎么理解分布式系統(tǒng)為什么不能同時滿足三者一致性哈希的原理和應(yīng)用場景負載均衡有哪些策略什么是主從復(fù)制、哨兵機制一致性協(xié)議Paxos/Raft的基本思想這些題目不為難實習生主要是看有沒有接觸過分布式系統(tǒng)的基本概念?;卮餋AP時可以舉具體例子在分布式存儲里如果網(wǎng)絡(luò)分區(qū)發(fā)生你選擇保證可用性就會返回舊數(shù)據(jù)犧牲一致性選擇保證一致性就得拒絕請求犧牲可用性。2015年阿里很多中間件都在解決這類問題所以筆試具備這種思維會很加分。準備這部分不需要多深但要把一致性哈希的“加入節(jié)點后只有少量key需要遷移”講明白最好還能說明為什么用虛擬節(jié)點解決數(shù)據(jù)傾斜問題。這體現(xiàn)了你對真實工程問題的理解而不是背教科書。3. 幾類高頻筆試真題的解題思路復(fù)盤3.1 手寫內(nèi)存拷貝函數(shù)認真審題手寫memcpy是C語言筆試的經(jīng)典題看起來簡單但有兩個坑一是要處理內(nèi)存重疊二是要返回目標地址。很多人只實現(xiàn)了最簡單的字節(jié)復(fù)制沒考慮dest和src地址有重疊時可能覆蓋數(shù)據(jù)。一個相對完整的實現(xiàn)思路是這樣的如果dest在src后面且重疊區(qū)域會導致正向拷貝覆蓋源數(shù)據(jù)需要從尾部開始拷貝否則從頭部開始拷貝。具體可以用指針位置比較而不需要額外分配內(nèi)存。如果面試官允許使用標準庫函數(shù)也可以借助memmove但自己實現(xiàn)時要明白memmove的處理邏輯。這類題得分的關(guān)鍵是邊界條件??罩羔樑袛?、長度為0的情況、地址重疊的情況一個都不能漏。寫完之后自己舉兩個例子驗證比如memcpy(p3, p, 10)和memcpy(p, p3, 10)看看是否能正確復(fù)制。3.2 判定大小端一個union就搞定大小端問題也常出現(xiàn)在基礎(chǔ)題或簡答題里。題目通常是“寫程序判斷當前機器是大端還是小端”。最簡單的做法是定義一個聯(lián)合體包含一個int和一個char數(shù)組然后給int賦一個已知值比如1再檢查低地址字節(jié)的值。如果低地址字節(jié)是1說明低字節(jié)存在低地址就是小端否則是大端。#include stdio.h union endian_test { int value; char bytes[4]; }; int main() { union endian_test test; test.value 0x01; if (test.bytes[0] 0x01) { printf(little endian\n); } else { printf(big endian\n); } return 0; }為什么要掌握這個因為網(wǎng)絡(luò)字節(jié)序固定是大端而x86機器是小端做網(wǎng)絡(luò)通信時如果直接強轉(zhuǎn)指針去解析整數(shù)字段很容易踩坑?;A(chǔ)平臺研發(fā)涉及協(xié)議棧、序列化這種細節(jié)是基本功。3.3 多線程交替打印同步原語怎么選多線程題常見的問法有兩種一是寫代碼實現(xiàn)兩個線程交替打印奇偶數(shù)二是實現(xiàn)生產(chǎn)者消費者。很多同學一上來就用sleep加忙等這在筆試里會被扣分因為缺少對同步原語的理解。正確的思路是用條件變量或者信號量控制線程執(zhí)行順序。比如用C11的std::condition_variable加上一個共享變量表示當前該誰打印每次打印完喚醒另一個線程。用信號量也行兩個信號量初始值一個為1一個為0分別控制奇偶線程的執(zhí)行權(quán)。關(guān)鍵點在于共享變量要被互斥鎖保護防止多個線程同時讀寫條件等待要放在循環(huán)里因為可能出現(xiàn)偽喚醒。答題時把這兩個點寫出來面試官立刻知道你是真正寫過并發(fā)代碼而不是背模板。3.4 找出數(shù)組中出現(xiàn)次數(shù)超過一半的數(shù)字這是一道非常經(jīng)典的算法題。最樸素的做法是排序后取中間值復(fù)雜度O(n log n)更好的做法是摩爾投票法時間復(fù)雜度O(n)空間復(fù)雜度O(1)。思路是維護一個候選值和一個計數(shù)器遇到相同數(shù)字加一不同數(shù)字減一計數(shù)器歸零就更換候選值。因為目標數(shù)字出現(xiàn)次數(shù)超過一半所以最后留下的候選值就是答案。int majorityElement(int* nums, int numsSize) { int candidate nums[0]; int count 1; for (int i 1; i numsSize; i) { if (count 0) { candidate nums[i]; count 1; } else if (nums[i] candidate) { count; } else { count--; } } return candidate; }這個題在當年筆試里出現(xiàn)率很高不僅考算法思想更考寫代碼時對數(shù)組越界和空數(shù)組的處理。如果numsSize為0這段代碼會越界所以答題時一定要先判斷邊界。筆試環(huán)境里沒有測試用例邊界條件全靠自己敏感。4. 答題過程中的踩坑記錄與排查思路4.1 邊界條件代碼題失分的頭號原因我見過太多筆試代碼主體思路很正確但一運行就崩潰原因幾乎都出在邊界條件上。比如寫鏈表反轉(zhuǎn)時沒有處理空鏈表和單節(jié)點鏈表寫字符串拷貝時沒有考慮源串和目標串重疊寫二分查找時用(left right) / 2可能導致整數(shù)溢出。怎么避免寫完代碼之后養(yǎng)成在草稿紙上跑一個最小例子的習慣。手動模擬幾個邊界輸入空輸入、只有一個元素、所有元素相同、目標值在首尾。每個邊界都走一遍能發(fā)現(xiàn)大部分隱藏bug。這個習慣不是筆試練出來的是線上排查問題練出來的。4.2 對底層機制理解不深容易盲目套模板很多候選人準備筆試時背了很多“標準答案”比如“進程和線程的區(qū)別是什么”“TCP四次揮手是什么”但遇到變體題就懵了。原因在于沒有理解底層機制只記住了結(jié)論。舉個常見例子問“select為什么最多支持1024個文件描述符”。如果你只是背FD_SETSIZE是1024面試官再多問一句“能不能改”就卡住了。真正理解這個限制的話應(yīng)該知道select用固定大小的位圖管理fd集合位圖大小在編譯期確定而epoll沒有這個限制因為內(nèi)核維護的是事件表。懂這個層面才算真正掌握。所以復(fù)習時不要只看答案要追著答案問“為什么”。每復(fù)習一個知識點順手在紙上畫出相關(guān)的數(shù)據(jù)結(jié)構(gòu)或狀態(tài)流轉(zhuǎn)比單純過知識點有效得多。4.3 時間分配別讓一道題毀掉整張卷子筆試題量大時間緊張最容易犯的錯誤是在一道難題上死磕導致后面簡單的題沒時間寫。2015年的筆試卷子結(jié)構(gòu)通常是前面選擇題和簡答題后面幾道代碼題。我的經(jīng)驗是先把能拿分的題全部做完再回頭啃難題。如果一道代碼題想了幾分鐘還沒有清晰思路至少寫下思路比如“這題可以先用哈希表統(tǒng)計再遍歷找結(jié)果”并寫出關(guān)鍵數(shù)據(jù)結(jié)構(gòu)定義。閱卷的時候老師會看解題思路哪怕代碼不完整也能拿部分分。另外手寫代碼時不要追求一次全對先把核心邏輯寫對再去補邊界和異常。很多同學一上來就想著處理各種異常結(jié)果主流程都沒寫完非??上?。4.4 面試官想從筆試看到什么筆試不只是判斷對錯更是觀察候選人的思維習慣。我后來參與過一些校招題目討論發(fā)現(xiàn)評卷重點往往落在幾個維度代碼是否規(guī)范變量命名是否清晰有沒有必要的注釋有沒有考慮異常分支復(fù)雜度是否達到最優(yōu)解。這個過程中字跡工整、格式清晰也很重要。在線筆試還好如果是紙質(zhì)筆試代碼堆成一團很難抓到重點。建議按函數(shù)拆分分步寫每個步驟空一行。哪怕時間緊張也要讓閱讀者能輕松看出你的思路。5. 給當年的自己一份準備清單5.1 基礎(chǔ)要覆蓋到什么程度如果只想著通過筆試核心是把C/C基礎(chǔ)、操作系統(tǒng)、網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)和算法這四塊吃透。C/C要能自己實現(xiàn)動態(tài)數(shù)組、鏈表、哈希表不是會用STL就行而是理解底層內(nèi)存分配和擴容策略。操作系統(tǒng)要熟悉進程線程模型、同步機制、死鎖最好能畫狀態(tài)轉(zhuǎn)換圖。網(wǎng)絡(luò)要能講清楚TCP狀態(tài)機和epoll的觸發(fā)模式。數(shù)據(jù)結(jié)構(gòu)與算法要保證常見題能寫出來并且能分析復(fù)雜度?;A(chǔ)平臺研發(fā)和普通后端開發(fā)的一個區(qū)別是它更加關(guān)注“一個請求從網(wǎng)卡到業(yè)務(wù)代碼再到返回整個鏈路上發(fā)生了什么”。準備筆試時可以刻意用這個思路去串聯(lián)知識點網(wǎng)卡收到數(shù)據(jù)包后如何觸發(fā)中斷、數(shù)據(jù)如何從內(nèi)核緩沖區(qū)拷貝到用戶態(tài)、線程如何被喚醒、鎖如何保護共享狀態(tài)、數(shù)據(jù)如何序列化后發(fā)送出去。當你把孤立的知識點連成一條線很多題目就不再難了。5.2 刷題之外的積累閱讀源碼、動手實驗筆試考的是基礎(chǔ)但想拿到更好的評級光刷題不夠。建議在準備周期里實際編譯運行一些小實驗比如用C語言寫一個簡單的線程池處理任務(wù)隊列用tcpdump抓一次HTTP請求的包分析三次握手過程自己實現(xiàn)一個LRU緩存用哈希表加雙向鏈表用strace跟蹤ls命令執(zhí)行了哪些系統(tǒng)調(diào)用這些實驗?zāi)芗由顚Φ讓釉淼母行哉J識。像LRU緩存看著很簡單但當你真正實現(xiàn)“哈希表映射到雙向鏈表節(jié)點get和put都是O(1)”的時候才會理解為什么雙向鏈表和哈希表能配合使用。2015年時很多資料還不像現(xiàn)在這么豐富我主要是靠讀開源代碼和寫博客來沉淀理解?,F(xiàn)在網(wǎng)上課程和面經(jīng)很多反而容易讓人只看不練。記住一句話動手做過一次比看十篇面經(jīng)都管用。5.3 從2015年到現(xiàn)在的變化距離2015年已經(jīng)過去很久基礎(chǔ)平臺研發(fā)的內(nèi)容也變化很大。那時候云計算還沒有完全普及阿里很多基礎(chǔ)組件是自研的筆試題也更偏傳統(tǒng)底層開發(fā)現(xiàn)在的基礎(chǔ)平臺研發(fā)則更多涉及Kubernetes、容器網(wǎng)絡(luò)、云原生存儲、Service Mesh等新方向面試內(nèi)容也相應(yīng)擴展了。但有一個東西沒變對計算機基礎(chǔ)知識的考察依然很重要。哪怕現(xiàn)在很多開發(fā)用Go、Java不需要手動管理內(nèi)存但操作系統(tǒng)、網(wǎng)絡(luò)、并發(fā)的底層原理仍然是排查線上問題的依據(jù)。一個不懂TCP狀態(tài)的人很難理解服務(wù)端為什么出現(xiàn)大量CLOSE_WAIT一個不懂內(nèi)存模型的人很難調(diào)優(yōu)高并發(fā)緩存。所以這篇復(fù)盤雖然基于2015年的題目但核心知識點在今天的基礎(chǔ)平臺面試里依然適用。建議你在準備當前面試時不要只追熱點先把經(jīng)典基礎(chǔ)補扎實再去了解云原生相關(guān)的新技術(shù)。5.4 推薦的復(fù)習路徑如果讓我列一個四周準備計劃大致這樣安排第一周C/C與內(nèi)存。重點復(fù)習指針、數(shù)組、結(jié)構(gòu)體對齊、動態(tài)內(nèi)存管理手寫常用字符串和內(nèi)存函數(shù)。第二周操作系統(tǒng)與并發(fā)。重點復(fù)習進程線程模型、死鎖、信號量、條件變量手寫生產(chǎn)者消費者和線程池。第三周網(wǎng)絡(luò)與Linux。重點復(fù)習TCP狀態(tài)機、IO模型、epoll用抓包工具配合驗證同時練習常用排查命令。第四周數(shù)據(jù)結(jié)構(gòu)與算法。重點刷鏈表、數(shù)組、二叉樹相關(guān)題目每天至少手寫一道整理常見復(fù)雜度的推導。這四周的安排不是死規(guī)矩可以按自己基礎(chǔ)調(diào)整。但原則只有一個基礎(chǔ)不牢后面都是空中樓閣?;A(chǔ)平臺研發(fā)實習生筆試的題目再變考察的內(nèi)核還是這些。我個人在這些年里的體會是筆試更像是一個“照妖鏡”平時基礎(chǔ)扎不扎實一張卷子就能看出來。當年我也有過準備方向跑偏的時候花了大把時間看各種框架結(jié)果筆試里一道框架題都沒有反而栽在了一道簡單的鏈表反轉(zhuǎn)上。那之后我就調(diào)整了策略所有面試準備先從教科書級別的知識點開始再逐步往外擴展?;A(chǔ)平臺研發(fā)尤其如此把底層的“為什么”弄明白后面學什么都快。如果你正在準備這個方向的實習筆試希望這篇復(fù)盤能幫你少走點彎路。