易云存儲(chǔ)校招筆試復(fù)盤:從哈希索引到LSM Tree的分布式存儲(chǔ)核心)
1. 先從卷子看網(wǎng)易的考核邏輯1.1 這份卷子考了什么又為什么值得翻出來(lái)2018年網(wǎng)易校招云計(jì)算存儲(chǔ)開(kāi)發(fā)工程師的筆試卷放到今天依然很有參考價(jià)值。原因很簡(jiǎn)單存儲(chǔ)方向的核心知識(shí)點(diǎn)五年八年都不太會(huì)大變。當(dāng)年考的是分布式系統(tǒng)、存儲(chǔ)引擎、對(duì)象存儲(chǔ)、KV、緩存、IO模型這些東西今天面試還在問(wèn)這些換個(gè)問(wèn)法而已。我當(dāng)年刷過(guò)這份題也帶著不少學(xué)弟學(xué)妹復(fù)盤過(guò)。整張卷子給我最深的印象是它不考死記硬背而是在考你“遇到存儲(chǔ)問(wèn)題時(shí)的第一反應(yīng)”。比如給你一個(gè)寫(xiě)入延遲偏高的場(chǎng)景你會(huì)先想到WAL落盤、還是先想到鎖競(jìng)爭(zhēng)、還是先想到網(wǎng)絡(luò)分區(qū)這種題沒(méi)有標(biāo)準(zhǔn)答案但你能寫(xiě)到哪一層基本就代表了你的水平在哪個(gè)段位。從招聘崗位來(lái)看網(wǎng)易當(dāng)年的存儲(chǔ)團(tuán)隊(duì)主要維護(hù)對(duì)象存儲(chǔ)、分布式塊存儲(chǔ)、以及各類KV組件。所以卷子覆蓋了幾個(gè)固定模塊數(shù)據(jù)結(jié)構(gòu)與算法、操作系統(tǒng)與網(wǎng)絡(luò)、分布式系統(tǒng)理論、存儲(chǔ)引擎原理、以及最后的場(chǎng)景設(shè)計(jì)題。每個(gè)模塊都不算特別深但組合起來(lái)覆蓋面很廣想拿高分必須“既懂理論又能落地”。1.2 為什么說(shuō)這套考核思路對(duì)現(xiàn)在求職仍有參考價(jià)值我接觸到不少準(zhǔn)備云計(jì)算存儲(chǔ)方向校招的同學(xué)很容易陷入兩個(gè)極端要么只知道背面試題要么只埋頭寫(xiě)業(yè)務(wù)代碼兩者都很難應(yīng)對(duì)這類筆試卷。這份卷子的出題思路本質(zhì)上是在篩選“具備系統(tǒng)全局觀”的候選人。存儲(chǔ)系統(tǒng)是典型的下層基礎(chǔ)設(shè)施任何一個(gè)環(huán)節(jié)出問(wèn)題都會(huì)向上傳導(dǎo)所以它要求開(kāi)發(fā)工程師不僅要會(huì)調(diào)API還要理解IO路徑上每一層發(fā)生的事情。這和今天“云計(jì)算運(yùn)維”、“AI應(yīng)用開(kāi)發(fā)工程師”的崗位也很像——技術(shù)??梢該Q但底層思維是共通的。所以如果你正準(zhǔn)備存儲(chǔ)方向或云計(jì)算方向的校招這份2018年的卷子不是用來(lái)“刷”的而是用來(lái)“拆”的。把每一道題背后對(duì)應(yīng)的知識(shí)域列出來(lái)你就得到了一個(gè)非常清晰的復(fù)習(xí)大綱。接下來(lái)我就按這份卷子的模塊結(jié)構(gòu)把核心知識(shí)點(diǎn)和答題思路逐一展開(kāi)。2. 數(shù)據(jù)結(jié)構(gòu)與存儲(chǔ)模型先過(guò)筆試?yán)锏挠查T檻2.1 哈希索引與LSM Tree選擇背后的性能賬本筆試卷里有一類高頻題給你幾種數(shù)據(jù)結(jié)構(gòu)問(wèn)它們?cè)诖鎯?chǔ)場(chǎng)景下的適用性。哈希表、跳表、B樹(shù)、LSM Tree幾乎是必考組合。很多人能說(shuō)出各自的定義但講不清“為什么存儲(chǔ)系統(tǒng)要這樣選”這才是丟分點(diǎn)。哈希索引的優(yōu)勢(shì)是單點(diǎn)查詢O(1)但這個(gè)O(1)的前提是數(shù)據(jù)全在內(nèi)存或者哈希桶的沖突可控。一旦數(shù)據(jù)量大到需要落盤哈希索引的隨機(jī)IO會(huì)非常難受。傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)用B樹(shù)是為了讓范圍查詢和等值查詢都能走有序結(jié)構(gòu)但B樹(shù)的寫(xiě)入會(huì)產(chǎn)生大量隨機(jī)寫(xiě)頁(yè)SSD上還行機(jī)械盤上就是災(zāi)難。LSM Tree的思路是“順序?qū)憙?yōu)先”把隨機(jī)寫(xiě)轉(zhuǎn)換成內(nèi)存中的有序結(jié)構(gòu)再通過(guò)批量刷盤和后臺(tái)合并來(lái)持久化。代價(jià)是讀放大和寫(xiě)放大。這個(gè)取舍在筆試?yán)锝?jīng)常以對(duì)比題出現(xiàn)你要答出“為什么RocksDB和HBase都選LSM”核心就是高吞吐寫(xiě)入場(chǎng)景下LSM的代價(jià)可以接受而B(niǎo)樹(shù)的隨機(jī)寫(xiě)代價(jià)可能先拖垮你。2.2 跳表與有序數(shù)據(jù)結(jié)構(gòu)隱藏在Redis和內(nèi)存引擎里的選擇再說(shuō)跳表。Redis選跳表當(dāng)有序集合的底層實(shí)現(xiàn)筆試?yán)镆渤D贸鰜?lái)問(wèn)。跳表的核心是用多層索引換查詢速度實(shí)現(xiàn)比紅黑樹(shù)簡(jiǎn)單而且在并發(fā)場(chǎng)景下更容易做細(xì)粒度鎖。對(duì)存儲(chǔ)開(kāi)發(fā)來(lái)說(shuō)你要關(guān)注的不是跳表本身而是“當(dāng)我們需要一個(gè)有序結(jié)構(gòu)同時(shí)又希望并發(fā)性能好一點(diǎn)時(shí)跳表是一個(gè)工程上很務(wù)實(shí)的選擇”。我在實(shí)際寫(xiě)一個(gè)內(nèi)存KV引擎時(shí)也復(fù)現(xiàn)過(guò)類似方案——用跳表做主索引哈希表做熱點(diǎn)緩存。兩級(jí)結(jié)構(gòu)的好處是熱點(diǎn)數(shù)據(jù)走哈??焖倜欣鋽?shù)據(jù)走跳表保持有序性。筆試面到“如何設(shè)計(jì)一個(gè)內(nèi)存KV”時(shí)這種分層結(jié)構(gòu)很加分因?yàn)槟阌忻鞔_的取舍理由和數(shù)據(jù)支撐。2.3 索引存儲(chǔ)與哈希存儲(chǔ)的對(duì)比筆試?yán)镒钊菀妆蛔穯?wèn)的點(diǎn)索引存儲(chǔ)和哈希存儲(chǔ)也是熱搜詞里出現(xiàn)的內(nèi)容這確實(shí)是存儲(chǔ)開(kāi)發(fā)的基礎(chǔ)概念。哈希存儲(chǔ)適合等值查詢索引存儲(chǔ)適合范圍查詢和排序。很多存儲(chǔ)引擎會(huì)把兩者結(jié)合例如MySQL InnoDB用B樹(shù)做聚簇索引但二級(jí)索引也要走B樹(shù)Redis主要用哈希和跳表但也沒(méi)放棄數(shù)組和鏈表。筆試卷里通常會(huì)給一張表列上幾種存儲(chǔ)結(jié)構(gòu)讓你填各自的時(shí)間復(fù)雜度和適用場(chǎng)景。我建議你復(fù)習(xí)時(shí)自己畫(huà)一個(gè)對(duì)比表哈希、數(shù)組、鏈表、跳表、B樹(shù)、LSM從查詢、寫(xiě)入、范圍掃描、內(nèi)存占用、并發(fā)表現(xiàn)五個(gè)維度列一遍。這個(gè)表基本能覆蓋大多數(shù)數(shù)據(jù)結(jié)構(gòu)的考題。3. 分布式存儲(chǔ)系統(tǒng)背后的原理與權(quán)衡3.1 數(shù)據(jù)分布與一致性哈希從擴(kuò)容聊到虛擬節(jié)點(diǎn)分布式存儲(chǔ)繞不開(kāi)數(shù)據(jù)分布。筆試?yán)镆坏┝牡揭恢滦怨Mǔ2皇亲屇惚乘惴ǘ墙o一個(gè)場(chǎng)景現(xiàn)網(wǎng)有100個(gè)存儲(chǔ)節(jié)點(diǎn)數(shù)據(jù)用哈希分布某節(jié)點(diǎn)宕機(jī)后哪些key會(huì)受影響、怎么遷移、怎么避免雪崩。一個(gè)很常見(jiàn)的答題思路是先描述樸素取模方案的問(wèn)題——節(jié)點(diǎn)增減導(dǎo)致大量key重新映射再說(shuō)明一致性哈希通過(guò)哈希環(huán)和虛擬節(jié)點(diǎn)解決這個(gè)問(wèn)題最后補(bǔ)充工程實(shí)踐上的細(xì)節(jié)比如虛擬節(jié)點(diǎn)數(shù)量選擇、數(shù)據(jù)傾斜檢測(cè)、以及基于分片而不是真實(shí)節(jié)點(diǎn)的遷移策略。這個(gè)回答鏈越完整越能體現(xiàn)你不是只懂概念。我在做分布式存儲(chǔ)運(yùn)維時(shí)真遇到過(guò)某團(tuán)隊(duì)把一致性哈希的虛擬節(jié)點(diǎn)數(shù)設(shè)得太少導(dǎo)致流量不均衡。一臺(tái)節(jié)點(diǎn)熱點(diǎn)明顯其他節(jié)點(diǎn)閑置。這個(gè)問(wèn)題在筆試中不一定會(huì)寫(xiě)但面試官一旦追問(wèn)“你實(shí)際部署時(shí)怎么確認(rèn)分布是均勻的”你如果沒(méi)有真實(shí)經(jīng)驗(yàn)很容易露怯。所以復(fù)習(xí)時(shí)最好補(bǔ)一下“如何統(tǒng)計(jì)哈希環(huán)的分布方差”“如何根據(jù)容量調(diào)整虛擬節(jié)點(diǎn)權(quán)重”這些實(shí)操內(nèi)容。3.2 CAP理論與副本一致性別再說(shuō)“CAP三選二”分布式存儲(chǔ)的另一個(gè)高頻點(diǎn)是CAP。但很多人的理解停留在“一致性、可用性、分區(qū)容忍性只能選兩個(gè)”這其實(shí)是誤解。CAP的準(zhǔn)確表述是當(dāng)網(wǎng)絡(luò)分區(qū)發(fā)生時(shí)你只能在一致性和可用性之間做選擇。網(wǎng)絡(luò)沒(méi)有分區(qū)的時(shí)候三者可以同時(shí)滿足。筆試中常見(jiàn)考法某存儲(chǔ)系統(tǒng)采用強(qiáng)同步復(fù)制問(wèn)它在網(wǎng)絡(luò)分區(qū)時(shí)表現(xiàn)如何另一個(gè)系統(tǒng)采用異步復(fù)制問(wèn)它是否滿足最終一致性?;卮疬@類題你要能把系統(tǒng)行為映射到CAP的框架里。強(qiáng)同步復(fù)制在網(wǎng)絡(luò)分區(qū)時(shí)為了不丟數(shù)據(jù)會(huì)拒絕寫(xiě)入也就是犧牲可用性保證一致性異步復(fù)制在分區(qū)時(shí)還能接受寫(xiě)入但可能出現(xiàn)舊數(shù)據(jù)被讀到屬于犧牲強(qiáng)一致性換可用性最終靠重放日志達(dá)到最終一致。同時(shí)不要忽略了副本一致性里最經(jīng)典的raft/paxos。網(wǎng)易當(dāng)年筆試卷里對(duì)分布式共識(shí)考得不算特別深但一定會(huì)有一道題讓你描述“主從切換時(shí)如何保證日志不丟”。我給你的建議是自己用動(dòng)畫(huà)或代碼模擬一遍Raft的選主和日志復(fù)制比死記硬背強(qiáng)得多。3.3 緩存層與存儲(chǔ)層的分工讓Redis不再只當(dāng)“加速器”筆試卷里的緩存題通常不會(huì)只問(wèn)Redis的基本用法而是把緩存當(dāng)作存儲(chǔ)系統(tǒng)的一部分來(lái)考。比如一張典型架構(gòu)圖客戶端 - 緩存集群 - 存儲(chǔ)集群然后問(wèn)你緩存擊穿、緩存穿透、緩存雪崩的處理手段。這里要注意的是存儲(chǔ)開(kāi)發(fā)工程師看緩存視角和業(yè)務(wù)開(kāi)發(fā)不太一樣。業(yè)務(wù)開(kāi)發(fā)關(guān)心命中率和數(shù)據(jù)一致性存儲(chǔ)開(kāi)發(fā)關(guān)心的是緩存集群和存儲(chǔ)集群之間的連接管理、緩存節(jié)點(diǎn)故障時(shí)的降級(jí)策略、以及緩存閾值抖動(dòng)對(duì)底層存儲(chǔ)的沖擊。我在實(shí)際運(yùn)維中就遇到過(guò)緩存集群因?yàn)閹挻驖M導(dǎo)致所有請(qǐng)求直接穿透到對(duì)象存儲(chǔ)把底層IO打掛的情況。所以筆試答題時(shí)如果你能把視角從“Redis命令”提升到“緩存作為存儲(chǔ)前級(jí)保護(hù)機(jī)制”就能拉開(kāi)和普通候選人的差距。4. 對(duì)象存儲(chǔ)與文件存儲(chǔ)云計(jì)算場(chǎng)景下的必考應(yīng)用4.1 從頁(yè)式存儲(chǔ)到對(duì)象存儲(chǔ)一次架構(gòu)演進(jìn)熱搜詞里有“對(duì)象存儲(chǔ)服務(wù)”“NAS存儲(chǔ)”“分布式存儲(chǔ)”這些概念在網(wǎng)易筆試卷里會(huì)以各類場(chǎng)景題出現(xiàn)。對(duì)象存儲(chǔ)本質(zhì)上是把數(shù)據(jù)當(dāng)作“對(duì)象”來(lái)管理每個(gè)對(duì)象有唯一的key附帶元數(shù)據(jù)存儲(chǔ)在扁平化命名空間中。這樣的設(shè)計(jì)天然適合海量非結(jié)構(gòu)化數(shù)據(jù)比如圖片、視頻、日志備份。筆試?yán)镉幸坏澜?jīng)典設(shè)計(jì)題讓你設(shè)計(jì)一個(gè)簡(jiǎn)單的對(duì)象存儲(chǔ)系統(tǒng)支持put、get、delete、list。你至少要回答出幾個(gè)關(guān)鍵決策數(shù)據(jù)在物理節(jié)點(diǎn)上怎么分片元數(shù)據(jù)存在哪里小文件和大文件的處理策略是否一致上傳過(guò)程中斷后如何斷點(diǎn)續(xù)傳。這里建議你補(bǔ)充S3 API的熟悉程度因?yàn)楹芏嗷ヂ?lián)網(wǎng)公司的對(duì)象存儲(chǔ)都是兼容S3接口的網(wǎng)易NOS也不例外。我當(dāng)時(shí)復(fù)盤這道題時(shí)會(huì)把方案分成三條路徑控制面、數(shù)據(jù)面、元數(shù)據(jù)面??刂泼尕?fù)責(zé)權(quán)限校驗(yàn)和路由數(shù)據(jù)面負(fù)責(zé)把對(duì)象落到磁盤或分布式文件系統(tǒng)上元數(shù)據(jù)面用獨(dú)立的數(shù)據(jù)庫(kù)或KV保存對(duì)象與數(shù)據(jù)塊的映射。這樣拆解之后即使面試官再追問(wèn)“你的系統(tǒng)怎么支撐億級(jí)對(duì)象”你也可以在三個(gè)面上分別擴(kuò)展。4.2 分布式文件系統(tǒng)的元數(shù)據(jù)管理文件存儲(chǔ)和對(duì)象存儲(chǔ)很多原理是相通的但元數(shù)據(jù)管理更復(fù)雜。分布式文件系統(tǒng)里文件被拆成多個(gè)數(shù)據(jù)塊分布在多個(gè)節(jié)點(diǎn)上元數(shù)據(jù)要記錄文件到數(shù)據(jù)塊的映射以及數(shù)據(jù)塊到物理節(jié)點(diǎn)的映射。這個(gè)映射表一旦膨脹就成了性能瓶頸。筆試卷里關(guān)于文件系統(tǒng)的題一般會(huì)圍繞“元數(shù)據(jù)服務(wù)怎么擴(kuò)展”展開(kāi)。經(jīng)典方案有幾種元數(shù)據(jù)分片按目錄或哈希分散到不同元數(shù)據(jù)服務(wù)器引入緩存層把熱點(diǎn)元數(shù)據(jù)放在內(nèi)存或者采用無(wú)中心架構(gòu)用分布式KV存儲(chǔ)元數(shù)據(jù)。每一種方案都對(duì)應(yīng)不同的一致性代價(jià)答題時(shí)要把權(quán)衡說(shuō)出來(lái)而不是只堆方案名。4.3 小文件合并與大文件切片的取舍邏輯對(duì)象存儲(chǔ)場(chǎng)景里小文件多是一個(gè)普遍痛點(diǎn)。每個(gè)文件都有獨(dú)立的元數(shù)據(jù)如果100萬(wàn)張小圖片各占一個(gè)對(duì)象元數(shù)據(jù)服務(wù)的壓力會(huì)非常大而且小文件在磁盤上的存儲(chǔ)效率也低。常見(jiàn)的解法是把小文件合并成大文件用“數(shù)據(jù)塊偏移量”的方式索引而在上傳大文件時(shí)又要做切片并發(fā)上傳提高吞吐和斷點(diǎn)續(xù)傳能力。筆試卷如果讓你設(shè)計(jì)“一個(gè)支持文件上傳的存儲(chǔ)系統(tǒng)”你最好主動(dòng)提到小文件合并和大文件切分這組對(duì)稱設(shè)計(jì)。這會(huì)體現(xiàn)你真的處理過(guò)存儲(chǔ)容量和性能問(wèn)題而不是只會(huì)調(diào)用SDK。我個(gè)人的經(jīng)驗(yàn)是小文件合并的塊大小一般設(shè)置在4MB到64MB之間具體看對(duì)象平均大小和底層文件系統(tǒng)的塊大小大文件切片則與網(wǎng)絡(luò)環(huán)境和并發(fā)數(shù)相關(guān)不能盲目切小否則元數(shù)據(jù)本身會(huì)變成新的瓶頸。5. IO模型與性能調(diào)優(yōu)把系統(tǒng)設(shè)計(jì)落到工程層面5.1 零拷貝、直接IO與頁(yè)緩存存儲(chǔ)性能題的主角對(duì)象存儲(chǔ)和分布式存儲(chǔ)的性能瓶頸很多不在CPU而在IO路徑。筆試卷里常見(jiàn)的一道題是讀文件并發(fā)送到網(wǎng)絡(luò)這個(gè)過(guò)程中數(shù)據(jù)從磁盤到網(wǎng)卡拷貝了幾次如何減少拷貝次數(shù)。這里引出零拷貝、mmap、sendfile等概念。面試官想聽(tīng)到的回答是傳統(tǒng)readwrite會(huì)經(jīng)歷內(nèi)核態(tài)到用戶態(tài)兩次拷貝而mmap可以少一次sendfile可以做到真正意義上的“內(nèi)核態(tài)完成數(shù)據(jù)傳輸”。存儲(chǔ)開(kāi)發(fā)里零拷貝常用于對(duì)象存儲(chǔ)的下載鏈路因?yàn)閿?shù)據(jù)不需要經(jīng)過(guò)業(yè)務(wù)進(jìn)程的加工直接透?jìng)骷纯?。但在?xiě)路徑里零拷貝就不一定合適因?yàn)槟阈枰獙?duì)數(shù)據(jù)做校驗(yàn)和加密必須經(jīng)過(guò)用戶態(tài)。5.2 OS頁(yè)緩存的選擇與落盤策略存儲(chǔ)系統(tǒng)要不要用頁(yè)緩存取決于一致性要求。很多分布式存儲(chǔ)為了數(shù)據(jù)安全會(huì)強(qiáng)制寫(xiě)盤后才返回成功這樣即使節(jié)點(diǎn)宕機(jī)數(shù)據(jù)也不丟但代價(jià)是每次寫(xiě)入都伴隨一次fsync性能大幅下降。常見(jiàn)的折中方案是用組提交或批量刷盤來(lái)攤薄fsync代價(jià)同時(shí)在內(nèi)存里保留一個(gè)未提交窗口窗口大小直接影響宕機(jī)丟數(shù)據(jù)的概率。筆試卷如果考到“怎么保證寫(xiě)入不丟同時(shí)提升性能”你可以答WAL加批量刷盤。WAL先順序?qū)懭罩驹佼惒剿?shù)據(jù)頁(yè)崩潰恢復(fù)時(shí)通過(guò)日志重放未完成的事務(wù)。這個(gè)設(shè)計(jì)既能保證事務(wù)持久化又能避免每次寫(xiě)操作都隨機(jī)落盤。在答題時(shí)我建議你畫(huà)一個(gè)時(shí)間軸把寫(xiě)入請(qǐng)求、日志落盤、數(shù)據(jù)落盤、用戶響應(yīng)幾個(gè)節(jié)點(diǎn)標(biāo)出來(lái)邏輯會(huì)非常清晰。5.3 并發(fā)模型與多線程存儲(chǔ)服務(wù)的常見(jiàn)陷阱存儲(chǔ)服務(wù)通常需要支撐大量并發(fā)連接所以IO模型的選擇很重要。筆試?yán)锟赡軙?huì)給一個(gè)線程模型讓你指出它的瓶頸。比如一個(gè)簡(jiǎn)單的“每請(qǐng)求一線程”模型在高并發(fā)下會(huì)因線程上下文切換和內(nèi)存開(kāi)銷而崩潰更優(yōu)的方案是Reactor模型或Proactor模型。同時(shí)要考慮鎖競(jìng)爭(zhēng)。在多線程寫(xiě)同一個(gè)存儲(chǔ)引擎時(shí)如果全部串行化吞吐上不去但如果只加粗粒度鎖又可能出現(xiàn)偽共享和長(zhǎng)尾延遲。常見(jiàn)的優(yōu)化方向是分片鎖、無(wú)鎖隊(duì)列、以及避免在IO路徑上做耗時(shí)操作。你在答題時(shí)最好用具體數(shù)字說(shuō)明問(wèn)題比如“1000并發(fā)下每請(qǐng)求50ms延遲單線程只能處理20請(qǐng)求每秒改用8線程Reactor后能達(dá)到150”。6. 真題實(shí)戰(zhàn)復(fù)盤我把當(dāng)年的幾道典型題重新做了一遍6.1 場(chǎng)景題設(shè)計(jì)一個(gè)日志存儲(chǔ)系統(tǒng)怎么答比較穩(wěn)我印象很深的一道筆試題是給一個(gè)日志系統(tǒng)每天產(chǎn)生數(shù)十億條日志需要支持寫(xiě)入和按時(shí)間范圍查詢問(wèn)你如何設(shè)計(jì)存儲(chǔ)層。我的答題框架分四步第一日志寫(xiě)入是順序追加型優(yōu)先考慮LSM Tree或類Kafka的分段日志結(jié)構(gòu)第二為了支持時(shí)間范圍查詢必須建立時(shí)間索引和偏移量索引可以考慮用倒排索引或時(shí)間分桶第三日志數(shù)據(jù)生命周期短冷數(shù)據(jù)要定期歸檔到對(duì)象存儲(chǔ)降低本地存儲(chǔ)成本第四查詢接口要支持分頁(yè)和游標(biāo)避免一次拉取過(guò)多數(shù)據(jù)導(dǎo)致內(nèi)存溢出。這四步寫(xiě)下來(lái)比單純回答“我選HBase”要完整得多?,F(xiàn)場(chǎng)寫(xiě)代碼的時(shí)候我會(huì)先定義幾個(gè)核心接口append(log)、query(startTime, endTime, offset, limit)、archive(beforeTime)。然后給出一個(gè)簡(jiǎn)化版實(shí)現(xiàn)用TreeMap存內(nèi)存索引用隊(duì)列做批量寫(xiě)盤。代碼不需要很復(fù)雜但要讓面試官看到你有“先定接口再定實(shí)現(xiàn)”的工程習(xí)慣。6.2 手寫(xiě)一個(gè)簡(jiǎn)化的LSM存儲(chǔ)合并流程筆試卷里偶爾會(huì)讓手寫(xiě)一個(gè)小型KV存儲(chǔ)核心考點(diǎn)是LSM的寫(xiě)入流程和合并觸發(fā)條件。這個(gè)題不算難但比較容易寫(xiě)漏。我一般會(huì)實(shí)現(xiàn)三個(gè)模塊內(nèi)存表、WAL、SSTable列表。寫(xiě)入時(shí)先追加WAL再寫(xiě)入內(nèi)存表內(nèi)存表超過(guò)閾值后切換為不可變內(nèi)存表后臺(tái)刷盤生成SSTable當(dāng)SSTable數(shù)量或大小達(dá)到閾值觸發(fā)合并將多個(gè)SSTable按key歸并為一個(gè)更大的SSTable。刪除時(shí)插入tombstone標(biāo)記合并時(shí)清理。這套流程用Java或C寫(xiě)核心方法大概幾十行就能完成但足夠展示出你對(duì)存儲(chǔ)引擎內(nèi)部機(jī)制的理解。這里要特別注意一點(diǎn)合并過(guò)程會(huì)消耗IO和CPU所以需要控制觸發(fā)頻率。常見(jiàn)策略包括根據(jù)SSTable數(shù)量和大小雙重判斷或者根據(jù)讀放大率動(dòng)態(tài)調(diào)整。這個(gè)細(xì)節(jié)在筆試?yán)锊灰欢〞?huì)明確要求但你在注釋或額外說(shuō)明里寫(xiě)上會(huì)讓面試官覺(jué)得你有工程經(jīng)驗(yàn)。6.3 對(duì)象存儲(chǔ)上傳接口的完整實(shí)現(xiàn)思路最后一道類似附加題的場(chǎng)景設(shè)計(jì)對(duì)象存儲(chǔ)的上傳接口支持?jǐn)帱c(diǎn)續(xù)傳和秒傳。斷點(diǎn)續(xù)傳的經(jīng)典做法是客戶端將文件切片每個(gè)切片獨(dú)立上傳服務(wù)端記錄切片狀態(tài)全部完成后合并。秒傳則依賴哈希校驗(yàn)客戶端先上傳文件的MD5或SHA1服務(wù)端檢查是否已存在相同哈希的對(duì)象如果存在直接返回成功省去重復(fù)上傳的流量和時(shí)間。這道題在筆試中主要考察“你是否了解對(duì)象存儲(chǔ)API背后的邏輯”。很多同學(xué)用過(guò)OSS或S3的SDK但不一定了解分片上傳的完整生命周期。我建議你把createMultipartUpload、uploadPart、completeMultipartUpload三個(gè)接口的流程背熟并補(bǔ)充說(shuō)明每個(gè)階段服務(wù)端需要記錄的元數(shù)據(jù)uploadId、partNumber、etag、偏移量。這樣遇到類似的筆試題不管怎么問(wèn)都能接得住。7. 常見(jiàn)問(wèn)題與備考誤區(qū)我見(jiàn)過(guò)太多人倒在這些坑里7.1 為什么你背了很多題筆試還是過(guò)不了校招筆試和面試不一樣它更強(qiáng)調(diào)“在有限時(shí)間內(nèi)快速給出條理清晰、邏輯嚴(yán)謹(jǐn)?shù)姆桨浮薄:芏嗤瑢W(xué)背書(shū)式復(fù)習(xí)遇到具體場(chǎng)景就不知道怎么遷移。比如學(xué)過(guò)LSM Tree但面對(duì)“日志系統(tǒng)怎么設(shè)計(jì)”時(shí)還是答偏到MySQL上去了。我建議的備考方式是每學(xué)一個(gè)存儲(chǔ)組件都問(wèn)自己三個(gè)問(wèn)題——它解決什么問(wèn)題、它的核心原理是什么、如果讓我寫(xiě)一個(gè)簡(jiǎn)化版我會(huì)怎么設(shè)計(jì)。這三問(wèn)答清楚相關(guān)考點(diǎn)基本不會(huì)丟。我當(dāng)時(shí)復(fù)習(xí)Redis就逼自己寫(xiě)了一個(gè)簡(jiǎn)單的跳表版有序集合復(fù)習(xí)RocksDB就手動(dòng)模擬過(guò)一次SSTable合并。這個(gè)過(guò)程非常耗時(shí)但對(duì)筆試的幫助遠(yuǎn)超刷十套題。7.2 項(xiàng)目經(jīng)驗(yàn)怎么寫(xiě)才會(huì)讓面試官覺(jué)得你懂存儲(chǔ)網(wǎng)申階段通常要寫(xiě)項(xiàng)目經(jīng)歷很多同學(xué)把“用過(guò)Redis”“部署過(guò)HDFS”寫(xiě)成核心亮點(diǎn)這其實(shí)很難打動(dòng)面試官。真正有說(shuō)服力的寫(xiě)法是描述你在項(xiàng)目中遇到什么樣的存儲(chǔ)瓶頸你如何定位和解決最終帶來(lái)什么量化收益。我輔導(dǎo)過(guò)一位同學(xué)他在實(shí)驗(yàn)室做過(guò)一個(gè)圖像檢索系統(tǒng)項(xiàng)目本身不復(fù)雜但他把重點(diǎn)放在“向量數(shù)據(jù)如何存儲(chǔ)和檢索”上提到用了FAISS做近鄰搜索并用LSM結(jié)構(gòu)管理增量向量效果比直接用暴力搜索好很多。面試官對(duì)這個(gè)項(xiàng)目印象非常深因?yàn)樗皇恰坝眠^(guò)”而是“理解并改進(jìn)過(guò)”。如果你有類似的項(xiàng)目經(jīng)歷一定要挖掘出存儲(chǔ)層面的細(xì)節(jié)而不是停留在業(yè)務(wù)功能描述。7.3 關(guān)于經(jīng)典題的標(biāo)準(zhǔn)答案不要只停留在會(huì)背存儲(chǔ)方向有一個(gè)特點(diǎn)同一道題每個(gè)技術(shù)團(tuán)隊(duì)理解的“標(biāo)準(zhǔn)答案”都不一樣。同樣是“怎么保證Redis和MySQL數(shù)據(jù)一致”有人會(huì)聊刪除緩存策略有人會(huì)聊binlog消費(fèi)還有人會(huì)聊分布式事務(wù)。這些方向沒(méi)有對(duì)錯(cuò)但你要能結(jié)合題目上下文給出合理的分析路徑。筆試卷最怕的是“看起來(lái)答了很多但沒(méi)有邏輯主線”。我自己的習(xí)慣是遇到任何設(shè)計(jì)題先用一句話寫(xiě)出“核心目標(biāo)”再往下拆解“約束條件”最后才給“方案選型”。拿一個(gè)例子來(lái)說(shuō)目標(biāo)是“設(shè)計(jì)一個(gè)支持PB級(jí)數(shù)據(jù)的存儲(chǔ)系統(tǒng)”約束是“讀寫(xiě)比例10:1、可用性99.99%”那方案自然會(huì)偏向數(shù)據(jù)分片和副本冗余而不是單機(jī)優(yōu)化。有了這條主線即使某一個(gè)小點(diǎn)想不全面整體分?jǐn)?shù)也不會(huì)太低。8. 資料清單與備賽方向這些年我用下來(lái)很順手的學(xué)習(xí)路徑8.1 核心書(shū)籍與開(kāi)源項(xiàng)目怎么讀才能事半功倍如果你想系統(tǒng)性地準(zhǔn)備云計(jì)算存儲(chǔ)方向我比較推薦幾條主線。第一《數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計(jì)》DDIA作為總綱把存儲(chǔ)結(jié)構(gòu)、復(fù)制、分區(qū)、事務(wù)、一致性都過(guò)一遍第二MIT 6.824的視頻和lab作為分布式系統(tǒng)實(shí)操訓(xùn)練第三讀一個(gè)開(kāi)源存儲(chǔ)引擎的源碼不用太多選RocksDB或LevelDB其中一個(gè)就行。讀源碼不是讓你從頭到尾一行行看而是把核心模塊抽出來(lái)比如RocksDB的memtable怎么轉(zhuǎn)SSTable、compaction怎么觸發(fā)、WAL怎么管理??炊斯P試和面試?yán)锏拇鎯?chǔ)引擎題基本都能穩(wěn)定發(fā)揮。如果你有余力建議把Mini-LSM這類教學(xué)項(xiàng)目的實(shí)驗(yàn)做一遍它會(huì)在限制條件下逼你實(shí)現(xiàn)一個(gè)小型LSM存儲(chǔ)做完之后對(duì)整條鏈路的理解會(huì)非常具象。8.2 一套實(shí)用的復(fù)習(xí)時(shí)間表按周拆解不焦慮我常建議準(zhǔn)備校招的同學(xué)把存儲(chǔ)方向的復(fù)習(xí)周期定為六周。第一周“打地基”過(guò)一遍操作系統(tǒng)、網(wǎng)絡(luò)、數(shù)據(jù)結(jié)構(gòu)的重點(diǎn)第二到三周“專攻分布式理論”CAP、Raft、數(shù)據(jù)復(fù)制、分片等每天配合一道場(chǎng)景題練習(xí)第四周“深入存儲(chǔ)引擎”寫(xiě)一個(gè)簡(jiǎn)化版LSM或B樹(shù)的內(nèi)存模型第五周“刷真題和模擬題”重點(diǎn)練設(shè)計(jì)題和代碼題第六周“模擬面試”找朋友或自己對(duì)著題目口述答案訓(xùn)練表達(dá)和邏輯。當(dāng)然這不是唯一的時(shí)間表你可以根據(jù)自己基礎(chǔ)調(diào)整。但有一點(diǎn)很重要不要每天只輸入不輸出一定要用代碼或文字把學(xué)到的知識(shí)固化下來(lái)。我在準(zhǔn)備校招時(shí)每周末會(huì)把本周學(xué)到的核心知識(shí)點(diǎn)寫(xiě)成一篇復(fù)盤文章或者畫(huà)成一張系統(tǒng)架構(gòu)圖。這個(gè)過(guò)程很痛苦但堅(jiān)持下來(lái)筆試遇到陌生題也不慌因?yàn)槟阋呀?jīng)習(xí)慣了“拆解組裝”的思考方式。