秒殺:大廠面試官與謝飛機(jī)的三輪回合)
從Java SE到微服務(wù)秒殺大廠面試官與謝飛機(jī)的三輪回合上午十點(diǎn)某互聯(lián)網(wǎng)大廠會(huì)議室。面試官老張翻開簡(jiǎn)歷對(duì)面坐著一位頭發(fā)凌亂、眼神飄忽的程序員謝飛機(jī)。桌上擺著兩瓶礦泉水空調(diào)溫度恰到好處但謝飛機(jī)的額頭還是滲出了細(xì)密的汗珠。老張推了推眼鏡開口“謝飛機(jī)是吧看過你簡(jiǎn)歷Java 后端方向。咱們隨便聊聊你不用緊張。”謝飛機(jī)搓了搓手“張哥您隨意問我要是答不上來(lái)那也算正常發(fā)揮。”老張嘴角微微上翹“那咱們先來(lái)點(diǎn)基礎(chǔ)的熱身。”第一輪基礎(chǔ)熱場(chǎng)JVM 與構(gòu)建工具老張拿起筆在紙上寫了個(gè)數(shù)字“你平時(shí)主要用 Java 8 還是 11或者已經(jīng)用上 17 了說說這幾個(gè)版本里你覺得最有用的特性?!敝x飛機(jī)眼睛一亮這個(gè)他熟“我用 Java 8 最多不過最近在搞 11。8 里面 Lambda 和 Stream 太好使了寫集合過濾簡(jiǎn)直爽歪歪。11 的話…有個(gè) var 關(guān)鍵字哦對(duì)還有那個(gè) HttpClient發(fā) HTTP 請(qǐng)求不用再引 Apache 了?!崩蠌堻c(diǎn)了點(diǎn)頭“不錯(cuò)。那如果讓你用一個(gè)項(xiàng)目同時(shí)給 Java 8 和 17 編譯Maven 里怎么配置”謝飛機(jī)有點(diǎn)懵“同時(shí)那就…配兩個(gè) profile或者用 parent 的 java.version我一般都只配一個(gè)版本沒玩過雙的?!崩蠌垱]有追問繼續(xù)說“行。那 Maven 和 Gradle 你更常用哪個(gè)它們的依賴管理有什么區(qū)別”謝飛機(jī)搓了搓下巴“Maven 用的多pom.xml 寫起來(lái)啰嗦但清楚。Gradle 也用build.gradle 簡(jiǎn)潔用 Groovy 寫感覺執(zhí)行快一點(diǎn)。區(qū)別…Maven 是 XMLGradle 是編程靈活還能自定義 task?!崩蠌堎澰S地“嗯”了一聲“Spring Boot 啟動(dòng)類上那些注解SpringBootApplication 到底干了什么”謝飛機(jī)松了口氣“組合注解有 Configuration、EnableAutoConfiguration、ComponentScan。就是讓 Spring 自動(dòng)掃 bean自動(dòng)配置不用自己寫一堆 XML?!薄昂芎谩!崩蠌埿χf“那 JVM 一次方法調(diào)用的內(nèi)存分配過程大概是什么樣的棧幀里都有什么”謝飛機(jī)眼神開始渙散“這個(gè)…就是棧啊堆啊。方法調(diào)用就壓棧返回就彈棧。棧幀里有局部變量表操作數(shù)棧還有…那個(gè)…動(dòng)態(tài)鏈接方法返回地址反正在 JVM 里轉(zhuǎn)一圈就完事了?!崩蠌埪牫隽怂哪:珱]深究低頭在簡(jiǎn)歷上畫了個(gè)勾“基本功還行。接著來(lái)第二輪吧?!钡诙喼虚g件與數(shù)據(jù)一致性緩存、消息和數(shù)據(jù)庫(kù)老張把簡(jiǎn)歷翻到項(xiàng)目經(jīng)驗(yàn)“你上家公司做過電商訂單系統(tǒng)對(duì)吧如果用戶下一單扣庫(kù)存和生成訂單這兩個(gè)操作你怎么保證一致性”謝飛機(jī)來(lái)了精神“用事務(wù)Transactional 一加數(shù)據(jù)庫(kù)層面鎖住天然原子性?!薄叭绻唵畏?wù)庫(kù)存服務(wù)是拆開的兩個(gè)微服務(wù)呢”謝飛機(jī)撓了撓頭“那…那就不能用本地事務(wù)了。得用分布式事務(wù)吧比如 Seata但 Seata 我沒真正用過… 我可以搞個(gè)本地消息表或者發(fā) MQ 消息讓庫(kù)存服務(wù)消費(fèi)?!崩蠌堊穯枴澳侨绻?Redis 里緩存了庫(kù)存數(shù)量用戶秒殺的時(shí)候大量請(qǐng)求打到 Redis緩存沒命中全去數(shù)據(jù)庫(kù)會(huì)發(fā)生什么”謝飛機(jī)“這題我知道緩存穿透可以用布隆過濾器或者緩存空值?!崩蠌垺澳侨绻彺婷辛说珟?kù)存只剩 1 個(gè)1000 個(gè)請(qǐng)求同時(shí)下單每個(gè)都讀到庫(kù)存是 1都能扣成功嗎”謝飛機(jī)“啊這就是超賣問題。得用 Redis 的 Lua 腳本原子扣減或者加分布式鎖。反正不能讓并發(fā)同時(shí)扣?!崩蠌埬抗馊缇妗叭绻?Redis 扣減成功了但數(shù)據(jù)庫(kù)事務(wù)提交失敗Redis 庫(kù)存已經(jīng)被扣了怎么辦”謝飛機(jī)額頭上又冒汗了“這個(gè)…好像要保證最終一致性??梢杂?mq 發(fā)消息如果數(shù)據(jù)庫(kù)失敗就發(fā)消息補(bǔ)償回滾 Redis。但這個(gè)過程我也說不細(xì)反正就是發(fā)消息消費(fèi)者再改回來(lái)?!崩蠌埼⑽u頭“消息隊(duì)列如果本身丟消息呢你 Kafka 怎么保證消息不丟”謝飛機(jī)“配置 acksall生產(chǎn)者重試消費(fèi)者手動(dòng)提交 offset大概這樣吧。具體要不要開冪等我也記不清了反正都會(huì)配。”老張嘆了口氣“那你說說RabbitMQ 和 Kafka 分別適合什么場(chǎng)景”謝飛機(jī)“Kafka 處理高吞吐適合日志和削峰。RabbitMQ 是消息路由靈活適合業(yè)務(wù)消息。對(duì)了 Redis 的 pub/sub 也可以做消息但是不持久化掛了就沒了?!崩蠌堄浟藥坠P“行你雖然有些地方含糊但思路是有的。最后一輪聊點(diǎn)系統(tǒng)的設(shè)計(jì)題?!钡谌喯到y(tǒng)設(shè)計(jì)與云原生秒殺、JVM 調(diào)優(yōu)與 CI/CD老張喝了一口水“假設(shè)我們要做一個(gè)秒殺系統(tǒng)從用戶點(diǎn)擊到看到結(jié)果你整體怎么設(shè)計(jì)只用一句話概括核心流程?!敝x飛機(jī)搶答“先把請(qǐng)求擋住限流然后一步步放過去用消息隊(duì)列排隊(duì)Instant 秒殺?!薄昂??!崩蠌堻c(diǎn)頭“那用戶請(qǐng)求先到哪一層”謝飛機(jī)“先到 Nginx然后到網(wǎng)關(guān)網(wǎng)關(guān)做限流。再進(jìn)業(yè)務(wù)服務(wù)業(yè)務(wù)服務(wù)先查 RedisRedis 標(biāo)記還有庫(kù)存就生成一條消息丟給 Kafka然后馬上返回‘排隊(duì)中’。后面消費(fèi)者慢慢處理訂單?!崩蠌垎枴叭绻脩粢槊霘⒔Y(jié)果呢訂單狀態(tài)放哪”謝飛機(jī)“訂單狀態(tài)可以在 Redis 存一個(gè) key處理完給用戶設(shè)成‘成功’客戶端定時(shí)輪詢查一下?!崩蠌垺澳侨绻霘⒔Y(jié)束后要統(tǒng)計(jì)訂單數(shù)據(jù)把數(shù)據(jù)導(dǎo)給大數(shù)據(jù)部門用 Flink 還是 Spark”謝飛機(jī)“呃…Flink 是流處理可以實(shí)時(shí)消費(fèi) Kafka。Spark 是批處理跑離線。一般用 Flink 做實(shí)時(shí)大屏Spark 做 T1 報(bào)表?!崩蠌垺澳沁@個(gè)秒殺系統(tǒng)部署到 Kubernetes 上需要給 JVM 容器設(shè)置哪些參數(shù)特別是堆內(nèi)存和 CPU 限制”謝飛機(jī)“啊”了一聲“這個(gè)我熟容器里得用 -XX:MaxRAMPercentage 和 -XX:InitialRAMPercentage讓 JVM 根據(jù)容器內(nèi)存自動(dòng)調(diào)整堆大小。不能用 -Xmx 寫死因?yàn)槿萜飨拗屏藢懰罆?huì) OOM。還要開個(gè)優(yōu)雅停機(jī)配 readinessProbe 和 livenessProbe?!崩蠌堁壑虚W過一絲光“不錯(cuò)。那如果系統(tǒng)線上出現(xiàn)了 CPU 飆升你會(huì)怎么排查從命令到分析說說思路?!敝x飛機(jī)擦了擦汗“先用 top -Hp 找到線程 ID然后 jstack 把線程棧打出來(lái)找到對(duì)應(yīng) pid 的 nid 看堆棧找業(yè)務(wù)代碼比如死循環(huán)或者 GC 線程。如果是 GC 問題再用 jstat 看 GC 情況最后用 MAT 分析 dump 文件?!崩蠌埿α恕半m然你有的問題答得跟漿糊一樣但到了最后居然還能擠出幾個(gè)詞。我等會(huì)兒還有下一個(gè)面試者你先回去等通知吧?!敝x飛機(jī)站起來(lái)一把握住老張的手“張哥你意思是我還有戲”老張抽回手“我是說回家等通知?!敝x飛機(jī)走到門口回頭“張哥我是不是沒戲了”老張“你猜?!遍T關(guān)上老張看著簡(jiǎn)歷搖了搖頭又忍不住笑了笑“這孩子菜得真有意思?!备饺喢嬖囶}目的詳細(xì)答案與業(yè)務(wù)場(chǎng)景拆解這篇文章不但給你一個(gè)樂子更重要的是讓新手看完能學(xué)到東西。我們按面試官的提問邏輯把每道題背后的場(chǎng)景和技術(shù)點(diǎn)掰開揉碎講清楚。第一輪答案Java 版本、Maven/Gradle、Spring Boot 與 JVM 棧幀1. Java 8 / 11 / 17 的特性區(qū)別Java 8引入 Lambda 表達(dá)式、Stream API、Optional、新的日期時(shí)間 APIjava.time以及接口默認(rèn)方法。這是目前企業(yè)中最普及的版本很多老項(xiàng)目都停留在這里。Java 11在 8 基礎(chǔ)上增加了var局部變量類型推斷Java 10 就引入了標(biāo)準(zhǔn)化了 HttpClient。注意var只能用于局部變量不能用于成員變量和方法參數(shù)。另外Java 11 是 LTS長(zhǎng)期支持版本Oracle 后來(lái)收費(fèi)所以很多公司改用 OpenJDK。Java 17是一個(gè)重要的 LTS帶來(lái)密封類sealed class、模式匹配 switch 預(yù)覽、外鍵 API 等。17 之后Spring Boot 3.x 框架開始原生要求 Java 17 起步。面試時(shí)如果答不出太多先把 Lambda、Stream、var 說出來(lái)它們才是日常高頻點(diǎn)最后提一句“每個(gè) LTS 版本都有一些新特性我平時(shí)重點(diǎn)關(guān)注跟業(yè)務(wù)相關(guān)的”。千萬(wàn)不要不懂裝懂。2. 同時(shí)為 Java 8 和 Java 17 編譯的 Maven 配置真實(shí)場(chǎng)景有些老版本 JDK 編譯出來(lái)的字節(jié)碼在低版本 JDK 上運(yùn)行需要指定release或source/target。Maven 中可以使用maven-compiler-plugin并通過 Maven Profile 來(lái)按環(huán)境激活profiles profile idjava8/id properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties /profile profile idjava17/id properties maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties /profile /profiles還可以用--release參數(shù)替代-source/-target因?yàn)?-release不僅能控制語(yǔ)法版本還會(huì)限制 API 只能使用該版本的標(biāo)準(zhǔn)庫(kù)。例如plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.11.0/version configuration release11/release /configuration /plugin面試時(shí)能答出“用 profile 區(qū)分環(huán)境 用 release 參數(shù)”已經(jīng)加分了。3. Maven vs Gradle 依賴管理Maven基于 XML依賴坐標(biāo)由 groupId、artifactId、version 組成。依賴傳遞采用“最近優(yōu)先聲明優(yōu)先”有沖突時(shí)可以通過exclusions排除或使用 dependencyManagement 統(tǒng)一版本。Maven 生命周期固定clean, compile, test, package, install, deploy插件系統(tǒng)強(qiáng)大。Gradle基于 Groovy/Kotlin 構(gòu)建腳本提供更大的靈活。增量構(gòu)建快支持自定義 task。依賴配置用implementation、api、compileOnly等。Gradle 也會(huì)從 Maven Central 或 JCenter 拉包。大廠很多新項(xiàng)目會(huì)用 Gradle老項(xiàng)目用 Maven。面試時(shí)不需要說誰(shuí)好誰(shuí)壞重點(diǎn)說“Maven 重約定Gradle 重靈活”。4. SpringBootApplication 組合注解干了什么SpringBootApplication實(shí)際上等于SpringBootConfiguration // 標(biāo)識(shí)這是一個(gè) Spring Boot 配置類 EnableAutoConfiguration // 開啟自動(dòng)裝配根據(jù) classpath 依賴自動(dòng)配置 Bean ComponentScan // 掃描當(dāng)前包及其子包下的 Component、Service、Repository、Controller 等其中最關(guān)鍵的是EnableAutoConfiguration。它通過導(dǎo)入AutoConfigurationImportSelector讀取META-INF/spring.factories舊版或META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports新版中配置的自動(dòng)配置類按條件裝配ConditionalOnClass、ConditionalOnMissingBean 等決定是否需要?jiǎng)?chuàng)建對(duì)應(yīng) Bean。面試容易踩坑說成“啟動(dòng)流程”或“嵌套注解”。要明確它是“組合注解自動(dòng)裝配”。5. JVM 方法調(diào)用的內(nèi)存過程 與 棧幀每個(gè)線程有自己的虛擬機(jī)棧每調(diào)用一個(gè)方法就壓入一個(gè)棧幀。棧幀包含局部變量表存放方法參數(shù)和局部變量槽位slot可以復(fù)用。操作數(shù)棧存放中間計(jì)算過程和調(diào)用指令的操作數(shù)。動(dòng)態(tài)鏈接指向運(yùn)行時(shí)常量池中該方法的引用支持多態(tài)。方法返回地址方法正常退出或異常退出后回到調(diào)用位置所需的信息。方法執(zhí)行完棧幀出棧局部變量引用置空對(duì)象若沒有被其他地方引用之后由 GC 自動(dòng)回收。高級(jí)面試還會(huì)問“方法內(nèi)聯(lián)”“逃逸分析”和“棧上分配”。如果你回答到“JVM 在 JIT 編譯階段會(huì)對(duì)熱點(diǎn)代碼做逃逸分析變量不逃逸可以棧上分配”面試官會(huì)眼前一亮。謝飛機(jī)只答了基本盤談不上深入但沒全錯(cuò)。第二輪答案分布式事務(wù)、Redis 緩存與消息可靠性1. 訂單與庫(kù)存的一致性為什么不能只用本地事務(wù)在單體應(yīng)用里訂單表和庫(kù)存表在同一個(gè)數(shù)據(jù)庫(kù)你可以用Transactional鎖住要么全成功要么全回滾。但是在微服務(wù)架構(gòu)下訂單服務(wù)和庫(kù)存服務(wù)往往獨(dú)立部署、獨(dú)立數(shù)據(jù)庫(kù)本地事務(wù)管不住另一臺(tái)機(jī)器的數(shù)據(jù)庫(kù)。這時(shí)候需要分布式事務(wù)。常見的分布式事務(wù)方案XA/2PC兩階段提交數(shù)據(jù)庫(kù)原生支持但性能差鎖時(shí)間長(zhǎng)。TCCTry-Confirm-Cancel業(yè)務(wù)層面拆分性能好但侵入性強(qiáng)實(shí)現(xiàn)復(fù)雜。本地消息表消息最終一致性在業(yè)務(wù)數(shù)據(jù)庫(kù)里加一張消息表寫業(yè)務(wù)和寫消息在同一本地事務(wù)再通過定時(shí)任務(wù)/MQ異步通知下游。事務(wù)消息RocketMQ 支持Kafka 的 EOS 也有一定能力先發(fā) half message執(zhí)行完本地事務(wù)后 commit 或 rollback消費(fèi)者端保證冪等。Seata AT 模式一種自動(dòng)補(bǔ)償型分布式事務(wù)框架通過全局鎖和日志表實(shí)現(xiàn)對(duì)業(yè)務(wù)代碼侵入較小。面試時(shí)謝飛機(jī)說“Transactional 加鎖”只能算單體方案后來(lái)補(bǔ)了“MQ 本地消息表”就對(duì)了。這個(gè)問題就是希望你能說出“本地事務(wù)解決不了跨庫(kù)問題必須要最終一致性”。2. 緩存穿透、擊穿、雪崩緩存穿透查詢一個(gè)不存在的 key緩存沒有數(shù)據(jù)庫(kù)沒有。每次請(qǐng)求都打到 DB。處理辦法緩存空值設(shè)置短過期時(shí)間、布隆過濾器Bloom filter攔截?zé)o效 key。緩存擊穿一個(gè)熱點(diǎn) key 在緩存失效瞬間大量并發(fā)打到數(shù)據(jù)庫(kù)。處理辦法互斥鎖分布式鎖、邏輯過期不設(shè)置物理過期后臺(tái)異步刷新。緩存雪崩大量 key 同時(shí)過期或 Redis 宕機(jī)導(dǎo)致請(qǐng)求直達(dá) DB。處理辦法過期時(shí)間加隨機(jī)值Redis 高可用主從哨兵集群本地限流/熔斷。謝飛機(jī)把“穿透”和“擊穿”有點(diǎn)混。面試官故意問“緩存沒命中”其實(shí)是引導(dǎo)但謝飛機(jī)沒區(qū)分。真正的標(biāo)準(zhǔn)答案必須把這三個(gè)詞說清楚。3. 秒殺超賣問題Redis Lua 原子扣減假設(shè)庫(kù)存只有 11000 個(gè)并發(fā)請(qǐng)求。如果每個(gè)進(jìn)程都是“讀 Redis 庫(kù)存 - 判斷 0 - 減一”那么就可能出現(xiàn) A 線程一讀是 1B 線程一讀也是 1然后都減成 0結(jié)果賣出 2 件。正確做法使用 Redis 的Lua 腳本在腳本里檢查庫(kù)存大于 0 再 decr這一步是原子性的。也可以使用DECR命令本身它會(huì)返回扣減后的值如果小于 0 就逆操作加回去但 Lua 更靈活。如果跨進(jìn)程還要保護(hù)數(shù)據(jù)庫(kù)常用分布式鎖Redis setnx Redisson或樂觀鎖數(shù)據(jù)庫(kù) update 時(shí)where stock 0。SQL 示例update stock set num num - 1 where id ? and num 0。如果影響行數(shù)為 1 才扣成功天然避免超賣。4. 數(shù)據(jù)庫(kù)事務(wù)失敗但 Redis 已扣減如何補(bǔ)償這屬于跨緩存和數(shù)據(jù)一致性問題。推薦方案在 Redis 扣減的 Lua 腳本里同時(shí)記錄一條“扣減記錄”比如用 Redis List 或 Hash 存操作痕跡。下單時(shí)先發(fā) MQ 消息或?qū)懕镜叵⒈碇笙M(fèi)者執(zhí)行數(shù)據(jù)庫(kù)事務(wù)。如果數(shù)據(jù)庫(kù)事務(wù)失敗消費(fèi)者發(fā)送補(bǔ)償消息把 Redis 的庫(kù)存加回來(lái)要使用 Lua 腳本保證原子加。最終一致性通過 MQ 事務(wù)消息或本地消息表重試來(lái)保證。更優(yōu)雅的做法是“把 Redis 當(dāng)作熱點(diǎn)窗口真正的扣減還是以數(shù)據(jù)庫(kù)為準(zhǔn)”Redis 只是為了擋流量數(shù)據(jù)庫(kù)才是最終狀態(tài)。5. Kafka 消息不丟三大環(huán)節(jié)Kafka 生產(chǎn)到消費(fèi)不丟消息需要從三個(gè)環(huán)節(jié)保證生產(chǎn)者端設(shè)置acksall即要求 ISR 中所有副本都確認(rèn)收到才算成功開啟重試retries0冪等enable.idempotencetrue防止重試導(dǎo)致重復(fù)。Broker 端設(shè)置min.insync.replicas2確保至少兩個(gè)副本在線不需要關(guān)注消費(fèi)者是否消費(fèi)但消息默認(rèn)保留 7 天。消費(fèi)者端關(guān)閉自動(dòng)提交enable.auto.commitfalse業(yè)務(wù)處理成功后手動(dòng)提交 offset消費(fèi)處理要冪等。謝飛機(jī)說“消費(fèi)者手動(dòng)提交 offset”這半句是對(duì)的但“生產(chǎn)端 acksall”和“冪等”沒全說出來(lái)。面試官特別看重 Kafka 的生產(chǎn)者配置這是大數(shù)據(jù)量場(chǎng)景下的必考項(xiàng)。6. RabbitMQ vs KafkaRabbitMQ基于 AMQP支持復(fù)雜的路由模式direct、topic、fanout、headers提供消息確認(rèn)和重試機(jī)制對(duì)低延遲和靈活路由友好。適合訂單通知、任務(wù)分發(fā)等業(yè)務(wù)交互。Kafka一個(gè)分布式流平臺(tái)基于分區(qū)和日志吞吐量極高支持消息回放適合日志收集、用戶行為埋點(diǎn)、秒殺削峰、實(shí)時(shí)流處理。Kafka 不擅長(zhǎng)復(fù)雜路由也不支持每消息 TTL 和優(yōu)先級(jí)。Redis Pub/Sub輕量級(jí)廣播但消息不持久化發(fā)送者不知道消費(fèi)者是否收到適合集群內(nèi)廣播通知不適合作為可靠消息隊(duì)列。秒殺場(chǎng)景選 Kafka因?yàn)橥掏赂摺⑾鞣鍙?qiáng)訂單狀態(tài)通知選 RabbitMQ因?yàn)槁酚伸`活。第三輪答案秒殺整體架構(gòu)、K8s 下 JVM 調(diào)優(yōu)、線上 CPU 飆升排查1. 秒殺系統(tǒng)整體設(shè)計(jì)思路一個(gè)典型的秒殺流程簡(jiǎn)化版前端/接入層Nginx 做靜態(tài)資源分發(fā)和 IP 限流。網(wǎng)關(guān)層Spring Cloud Gateway 做全局限流Sentinel / Resilience4j防止突發(fā)流量打崩后端。應(yīng)用層業(yè)務(wù)服務(wù)先檢查 Redis 中的庫(kù)存預(yù)占標(biāo)記如果庫(kù)存足夠則用 Lua 腳本扣減 Redis 庫(kù)存并生成一條唯一訂單消息發(fā)送到 Kafka。異步消費(fèi)層消費(fèi)者從 Kafka 拉取消息串行或批量處理后生成數(shù)據(jù)庫(kù)訂單扣減數(shù)據(jù)庫(kù)庫(kù)存更新 Redis 狀態(tài)為“秒殺成功”。結(jié)果查詢客戶端輪詢查詢 Redis 中的秒殺狀態(tài)或查訂單表。注意輪詢頻率要低最好讓前端用 WebSocket 推送。監(jiān)控層Prometheus Grafana 監(jiān)控 QPS、RT、Redis 內(nèi)存、Kafka 堆積等。核心要點(diǎn)先限流再削峰再異步化最后最終一致。2. 為什么用 Flink 又用 SparkFlink真正的流處理毫秒級(jí)延遲支持事件時(shí)間、watermark、精確一次語(yǔ)義。當(dāng)秒殺訂單產(chǎn)生后可以直接從 Kafka 消費(fèi)實(shí)時(shí)訂單流計(jì)算累計(jì) GMV、實(shí)時(shí)銷量喂給大屏。Spark主要是批處理適合 T1 報(bào)表如昨天所有訂單的銷售額、地域分布。Spark Streaming 雖能處理流但本質(zhì)是微批次延遲不如 Flink 低。面試出現(xiàn)“大數(shù)據(jù)”關(guān)鍵詞時(shí)記住一個(gè)萬(wàn)能句式“實(shí)時(shí)用 Flink離線用 Spark中間用 Kafka 橋接”。3. 容器化部署 JVM 參數(shù)怎么設(shè)置在 Kubernetes 里每個(gè)容器有 CPU 和內(nèi)存 limits。如果 JVM 使用-Xmx固定堆大小容易造成兩個(gè)問題設(shè)置太大容器內(nèi)存如果小于堆 元空間 線程棧JVM 直接 OOM。設(shè)置太小浪費(fèi)容器資源。正確姿勢(shì)是使用adaptive heap sizing-XX:InitialRAMPercentage50 -XX:MaxRAMPercentage75 -XX:MinRAMPercentage50這樣 JVM 會(huì)自動(dòng)根據(jù)容器可用內(nèi)存計(jì)算堆大小。注意 JDK 8u191 之后才支持對(duì)容器內(nèi)存的完整感知。還有這些參數(shù)-XX:UseG1GC默認(rèn) GC適合大堆。-XX:MaxMetaspaceSize限制元空間。-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/tmp/dump.hprof崩潰時(shí)留下現(xiàn)場(chǎng)。配合 K8s 設(shè)置 readinessProbe 和 livenessProbe實(shí)現(xiàn)優(yōu)雅上線和自動(dòng)重啟。謝飛機(jī)答出了 RAMPercentage說明他是真見過不只是背概念。4. 線上 CPU 飆升的排查步驟這是大廠高頻實(shí)戰(zhàn)題。完整流程如下top看系統(tǒng)負(fù)載記住 CPU 高進(jìn)程 PID。top -Hp pid查看該進(jìn)程內(nèi)哪個(gè)線程占用 CPU 最高拿到線程 ID十進(jìn)制。把線程 ID 轉(zhuǎn) 16 進(jìn)制printf %x\n tid。jstack pid stack.txt然后在文件里搜nid0x十六進(jìn)制看線程棧。看是用戶業(yè)務(wù)線程還是 GC 線程如果是 GC 線程使用jstat -gcutil pid 1000看 GC 頻率可能是堆滿或內(nèi)存泄漏。如果是業(yè)務(wù)線程定位到具體代碼通常是死循環(huán)、讀大文件、鎖競(jìng)爭(zhēng)、正則回溯等。如果是內(nèi)存問題執(zhí)行jmap -dump:formatb,fileheap.hprof pid導(dǎo)出堆再用 Eclipse MAT 或 JProfiler 分析最重的對(duì)象。如果 CPU 過高是頻繁 Full GC優(yōu)先考慮調(diào)整堆大小或定位大對(duì)象持有引用。這條鏈路包含top / jstack / jstat / jmap / MAT是每個(gè) Java 后端必須刻在腦子里的。謝飛機(jī)雖然很多地方“含糊其辭”但每個(gè)問題他都能沾上邊尤其最后說對(duì) RAMPercentage 和 jstack 排查說明他有實(shí)踐底子只是缺乏系統(tǒng)性和深入性。這種候選人如果遇到一個(gè)愿意帶的團(tuán)隊(duì)其實(shí)能很快進(jìn)步。你學(xué)會(huì)了多少如果讓你去面試能不能比謝飛機(jī)答得更硬氣記住面試不是背八股而是把技術(shù)點(diǎn)串成一條解決問題的業(yè)務(wù)主鏈。希望這篇文章能幫你理清思路下回面試別像謝飛機(jī)一樣只靠“那個(gè)…那個(gè)”混過去。要真到了大廠門口你得明白一點(diǎn)回家等通知不可怕可怕的是等通知的時(shí)候連 Redis 和 Kafka 都沒分清楚。愿你成為下一個(gè)認(rèn)真準(zhǔn)備、系統(tǒng)歸納的“謝飛機(jī)”但別真的只會(huì)說“大概、應(yīng)該、好像”。從今天開始把 JVM、緩存、消息、微服務(wù)和容器化全部鏈接起來(lái)你也能穩(wěn)穩(wěn)接住面試官的每一輪提問。