作者:小傅哥,博客:https://bugstack.cn
哈嘍,大家好,我是技術(shù)UP主小傅哥。
作為求職者參與了那么多場面試,那你知道站在面試官的角度,拿到一份簡歷后,如何下手面試的嗎?今天小傅哥就接著一份簡歷,給大家分享下,一個項目都會問哪些問題(有粗有細(xì))!
小傅哥本身也是一個面試官,面試過實習(xí)、校招、社招、架構(gòu)師,不同的面試訴求會有不同的考察范圍。通過這些考察范圍的問題讓求職者通過案例舉證,來闡述自己的能力項。而這些能力項的匹配,則是招聘的要求。這里不得不提到,有些小卡拉米的簡歷,只言片語的項目描述,浮皮潦草的個人職責(zé)。是真的沒進(jìn)入面試就沒了!
接下來小傅哥會通過求職者
、面試官
,兩個角色進(jìn)行分享。你可以在這個過程中,閱讀簡歷和提問。
本次項目摘選小傅哥星球「碼農(nóng)會鎖」第8個實戰(zhàn)項目,簡歷編寫和面試提問。項目地址:http://gaga.plus
一、求職者
面試官您好,大營銷平臺的 Raffle 抽獎模塊,是我獨立負(fù)責(zé)實現(xiàn)的一個(學(xué)習(xí)/工作)項目,此項目模塊在架構(gòu)設(shè)計上運用了 DDD 分層架構(gòu)和模板模式、責(zé)任鏈模式、組合模式、工廠模式等,這樣的設(shè)計模式對業(yè)務(wù)流程進(jìn)行解耦和實現(xiàn)。Raffle 抽獎模塊的完整開發(fā),讓我對 SpringBoot 框架技術(shù),分布式技術(shù)棧的運用更加熟練,也把設(shè)計模式在實際場景的使用了起來,積累了豐富的設(shè)計實現(xiàn)經(jīng)驗。這寫技術(shù)學(xué)習(xí)的內(nèi)容,也可以更好的應(yīng)對以后的開發(fā)工作。非常感謝您給我這次面試機(jī)會。
項目名稱:大營銷平臺 - Raffle 抽獎服務(wù)
項目架構(gòu):微服務(wù)架構(gòu)、DDD 領(lǐng)域驅(qū)動模型、前后端分離設(shè)計
核心技術(shù):SpringBoot、MyBatis、MySQL、Redis、SpringCloud/Dubbo【按需添加,只是對外的接口形式】、React、TypeScript
項目描述:Raffle 抽獎模塊是整個大營銷平臺系統(tǒng)中非常重要的一個模塊,也是本次項目中我來負(fù)責(zé)的設(shè)計和實現(xiàn)的模塊。此模塊主要以支撐各類差異化抽獎流程,如;通用抽獎、黑名單、人群、N消耗積分指定抽獎范圍、抽獎N次解鎖獎品等各類玩法的支持。在此系統(tǒng)模塊的設(shè)計中運用到了模板模式、責(zé)任鏈模式、組合模式、工廠模式,解決代碼的可擴(kuò)展性,并對抽獎的計算和秒殺做了設(shè)計的優(yōu)化,可以支撐單機(jī) 2c4g 服務(wù)器 1500 ~ 2000 TPS 的吞吐量?!覆煌?wù)器,帶寬,以及是否還配置有環(huán)境相關(guān),會有不同的數(shù)據(jù)效果」
核心職責(zé):
以PRD文檔訴求和對功能的評審,設(shè)計出抽獎的領(lǐng)域模型功能,以及在抽獎的流程抽象上,分為;抽獎前、抽獎中、抽獎后,的節(jié)點上擴(kuò)展各項行為動作。如抽獎前的人群判斷、抽獎中庫存扣減、抽獎后兜底獎勵等。
依賴于領(lǐng)域模型的定義,設(shè)計出抽獎庫表。抽象抽獎過程為抽獎策略表、策略明細(xì)表、規(guī)則配置表、規(guī)則樹動作表,這樣會讓抽獎更好擴(kuò)展。
設(shè)計模板模式定義抽獎流程標(biāo)準(zhǔn),再在模板模式中,調(diào)用責(zé)任鏈完成抽獎,對于抽獎中和后的動作使用組合模式的規(guī)則樹進(jìn)行動態(tài)處理【支持庫表配置】。
在項目架構(gòu)中定義統(tǒng)一標(biāo)準(zhǔn)的 api 由觸發(fā)器層實現(xiàn),在觸發(fā)器層定義監(jiān)聽、任務(wù)、http、rpc模塊,所有的行為動作,都理解為觸發(fā)行為。
抽獎也是一種峰值流量高的業(yè)務(wù)場景,因此在設(shè)計獎品庫存扣減上,采用了 Redis decr 分段消費和加鎖兜底的設(shè)計,同時對于消費成功的庫存,異步隊列方式 + 定時任務(wù)更新庫存。這樣可以不超賣的同時,又減少數(shù)據(jù)庫的壓力。
在項目開發(fā)中熟練運用了 IntelliJ IDEA、WEbStorm、Docker、MySQL、云服務(wù)器、SSH工具,并已將項目完整部署到線上【在?;锇榭梢蕴峁┚€上案例版】。
二、面試官
謝飛機(jī)同學(xué)你好,你做的這個項目我非常感興趣,接下來我們可以來聊一下關(guān)于這個《大營銷平臺項目》你完成的抽獎服務(wù)部分。
1. 你的項目工程是怎么搭建的?
項目的搭建類似于使用 start.spring.io 腳手架創(chuàng)建的,在我們的項目中,使用了統(tǒng)一 maven-archetype-plugin 插件,自定義了一套 DDD 工程骨架腳手架。同類項目都是使用這套腳手架創(chuàng)建項目,因為腳手架定義了統(tǒng)一的版本標(biāo)準(zhǔn)和對應(yīng)配套的開發(fā)環(huán)境,所以使用起來更加容易?!救绻由焯釂?,你還了解哪種搭建腳手架的方式,可以回答 FreeMarker】
2. 在你的這個項目中,都用到了什么開發(fā)工具?項目是怎么部署的
使用到了 IntelliJ IDEA、WebStorm、Sequel Ace/Navicat、ApiPost、Docker、SwitchHosts、Termius(SSH)等,項目上線的時候使用了2個方式,一個是本地構(gòu)建前后端鏡像,PUSH 到 Docker Hub,再通過編寫 docker compose 腳本,在云服務(wù)器部署。另外一個是搭建 Jenkins 配置部署流水線的方式進(jìn)行部署。
3. 整個項目開發(fā)過程中,你熟練的掌握了哪些技術(shù)棧
在整個項目開發(fā)中熟練的使用了 SpringBoot、MyBatis、Redisson 等技術(shù)框架,在編程功能實現(xiàn),熟練的結(jié)合與 Spring 容器,通過 Map 自動裝配 Java 語言實現(xiàn)的策略模式。以及在項目中較多的使用了 Redisson 框架,向 Redis 寫入 key-value、queue、map、lock 等數(shù)據(jù)類型,實現(xiàn)業(yè)務(wù)功能。
4. 在項目開發(fā)過程中,你有遇到過哪些運行時異常,怎么排查解決的
如剛開始項目開發(fā)引入腳手架以外的組件,進(jìn)行調(diào)試的時候,因為Jar版本不同。出現(xiàn)過編譯通過,調(diào)用的時候方法不存在。通過 Maven Helper 插件,檢查到有其他組件多引入了相同Jar包。另外還有一些如開發(fā)調(diào)試中發(fā)現(xiàn)的空指針問題,如查后需要增加空對象判斷。此外其他一些更多是功能流程實現(xiàn)細(xì)節(jié)上,如項目中的規(guī)則樹節(jié)點判斷流程問題,拋出一些自定義的異常。這些通過在方法上斷點調(diào)試逐步的解決。
5. 因為你的項目是前后端分離的,接口跨域怎么做的?
首先我們知道,Web跨域(Cross-Origin Resource Sharing,CORS)是一種安全機(jī)制,用于限制一個域下的文檔或腳本如何與另一個源的資源進(jìn)行交互。這是一個由瀏覽器強(qiáng)制執(zhí)行的安全特性,旨在防止惡意網(wǎng)站讀取或修改另一個網(wǎng)站的數(shù)據(jù),這種攻擊通常被稱為“跨站點腳本”(Cross-Site Scripting,XSS)。
所以在我的前后端分離項目中,通過配置 @CrossOrigin 注解來解決跨越問題。開發(fā)階段 Access-Control-Allow-Origin: *
、上線階段 Access-Control-Allow-Origin: https://gaga.plus
6. 看到你的項目用到了 DDD,這也是我們很感興趣的技術(shù)點,你可以介紹下你在使用 DDD 做這個項目時,都運用了 DDD 哪些知識。
DDD 是一種軟件設(shè)計方法,軟件設(shè)計方法涵蓋了范式、模型、框架、方法論等內(nèi)容,而 DDD 的很多規(guī)范約定,都是為了提高工程交付質(zhì)量。如幾個很重要的知識點;框架分層結(jié)構(gòu)、領(lǐng)域、實體、聚合、值對象、依賴倒置等。它所有的手段,都希望以一個功能邏輯的實現(xiàn)為聚合,將功能所需的對象、接口、邏輯,按照領(lǐng)域劃分到自己的領(lǐng)域內(nèi)。
就像在這個項目中,我負(fù)責(zé)實現(xiàn)的抽獎中的策略,就是一個獨立的領(lǐng)域模型。在這個領(lǐng)域中我需要提供策略的裝載、隨機(jī)數(shù)算法計算、抽獎模板調(diào)用(含責(zé)任鏈和規(guī)則樹)功能,這樣一個領(lǐng)域就像劃分好的一個獨立個體,它擁有屬于它的對象信息(實體、值對象、聚合),當(dāng)需要使用數(shù)據(jù)庫資源、緩存資源,以及外部接口資源的時候,都通過依賴倒置進(jìn)行調(diào)用。也就是說,我的領(lǐng)域不做其他模塊的引入,而是領(lǐng)域只負(fù)責(zé)業(yè)務(wù)功能實現(xiàn),所需的所有數(shù)據(jù),則有外部接口通過依賴倒置提供。更多理論知識
7. 你的抽獎流程中,哪些被定義為值對象,哪些被定義為實體對象
在 DDD 的規(guī)范定義中,值對象通常用于描述對象屬性的值,不具備唯一ID,不影響數(shù)據(jù)變化。如;數(shù)據(jù)庫中字段的枚舉值、業(yè)務(wù)流程中屬性對象。如抽獎流程中,RuleLimitTypeVO 規(guī)則限定方式的枚舉值對象、還有 RuleTreeVO 規(guī)則樹值對象。而那些實體對象,則具備唯一ID,會影響到最后的寫庫動作。如;抽獎發(fā)起實體、獎品信息實體對象。并且我們可以把一些和實體對象相關(guān)的功能聚合到對象內(nèi),這樣的通用性會更好,避免所有調(diào)用方都需要自己編寫邏輯。
8. 關(guān)于訪問數(shù)據(jù)層的依賴倒置,是怎么使用的,有什么好處,你可以描述下嗎
DDD 中的依賴倒置是一個非常好的設(shè)計,尤其是與 MVC 結(jié)構(gòu)對比的時候,MVC 的貧血模型結(jié)構(gòu)設(shè)計,數(shù)據(jù)庫持久化對象,很容易被當(dāng)做業(yè)務(wù)對象使用,這樣后期非常難維護(hù)。但在 DDD 的分層結(jié)構(gòu)用,是以 domain 領(lǐng)域?qū)崿F(xiàn)為核心,一個 domain 領(lǐng)域下所需的外部服務(wù),都由領(lǐng)域?qū)佣x接口,讓基礎(chǔ)層做具體實現(xiàn)。而數(shù)據(jù)庫持久化操作,定義的 PO 對象,就被這樣的方式被限定在基礎(chǔ)層了,外部是沒法引入使用的,也就天然的防止了數(shù)據(jù)庫持久化對象進(jìn)入業(yè)務(wù)中。
9. 我看你簡歷有提到,把抽獎劃分為抽獎前、中、后,三個動作。請具體結(jié)合場景講解下,為什么這樣設(shè)計
這個的設(shè)計得益于在 Spring/MyBatis 框架源碼的學(xué)習(xí),在源碼中經(jīng)常會出現(xiàn)對一個流程進(jìn)行拆分解耦,流程可擴(kuò)展的點,如 Spring 是 Bean 對象的拆解,MyBatis 是會話流程的拆解。所以在設(shè)計大營銷的抽獎模塊時,對于需求中的各類功能點;黑名單抽獎、權(quán)重抽獎、默認(rèn)抽獎、抽獎N次解鎖、兜底抽獎等等情況,是可以拆解為抽獎前、中、后,3個行為動作的,基于這樣的考慮后,就可以設(shè)計出非常容易擴(kuò)展的松耦合結(jié)構(gòu)。
10. 是什么場景下使用了責(zé)任鏈模式,什么場景使用了組合模式,為什么?
在設(shè)計完抽獎前、中、后,搜耦合的結(jié)構(gòu)模型后,對于抽獎前要執(zhí)行哪種抽獎,但單向選擇問題。所以這里使用了責(zé)任鏈模式,進(jìn)行節(jié)點流程判斷,從黑名單、權(quán)重,最后到默認(rèn),走一個單獨的具體抽獎,所以使用責(zé)任鏈更為合適。
之后是進(jìn)入抽獎的中和后,這兩部的流程是相對復(fù)雜的,需要判斷用戶抽獎了幾次,對于不同次會限定是否能獲得某個獎品,同時還有庫存的扣減,如果庫存不足或者不滿足n次抽獎得到某個獎品,則會進(jìn)行兜底。那么這就是一個樹規(guī)則的交叉流程,所以會使用了組合模式構(gòu)建一顆規(guī)則樹,并通過數(shù)據(jù)庫表的動態(tài)配置決定在抽獎前完成后,后續(xù)的流程要如何進(jìn)行。
11. 抽獎也是一種瞬時峰值很高的業(yè)務(wù)場景,那么對于抽中獎品后的庫存扣減是怎么做的?
關(guān)于庫存的扣減,是一個非常重要的流程。尤其是這種單獨資源競爭的場景,如果設(shè)計的不好,很容易把服務(wù)打掛。
所以在這套系統(tǒng)設(shè)計中,為了避免庫存扣減直接更新庫表的行級鎖,而導(dǎo)致大量的用戶進(jìn)行等待狀態(tài)。所以把數(shù)據(jù)庫表的庫存同步到 Redis 緩存中,在通過 incr 扣減的方式進(jìn)行消費,同時為了確保在臨界狀態(tài)、庫存恢復(fù)、異常處理等情況下不超賣,而對每一條產(chǎn)生從 incr 值,與抽獎的策略ID組合一個key,進(jìn)行 setnx 加鎖兜底,來保證不超賣?!?這樣的設(shè)計是顆粒度更小的鎖方案設(shè)計,性能接近于無鎖化。
12. 你講到庫存的扣減是通過 Redis 滑塊鎖實現(xiàn)的,那么最終同步庫是怎么做的,怎么降低對數(shù)據(jù)庫的壓力的?
關(guān)于 redis 緩存和數(shù)據(jù)庫表庫存數(shù)據(jù)的流程,設(shè)計了異步更新,保持最終一致性的設(shè)計。在執(zhí)行完庫存的扣減操作后(在抽獎中規(guī)則樹庫存節(jié)點流程),發(fā)送一個扣減完成到 Redis 的異步隊列(可以使用MQ+延遲消費),之后通過定時 Schedule Job 來消費隊列。這樣就可以控制效率速率,降低對數(shù)據(jù)庫的壓力。(因為我們不能 Redis 扣減的多快,就直接打到庫表上,那樣對數(shù)據(jù)庫的壓力依然很大,容易打掛)
13. 你提到了接口的單一職責(zé)設(shè)計,這部分具體講解下。
單一職責(zé)原則的核心思想是,一個類應(yīng)該只有一個引起它變化的原因。也就是說一個類應(yīng)該只負(fù)責(zé)一項任務(wù)或功能,如果一個類承擔(dān)了過多的職責(zé),那么這個類就會變得復(fù)雜,難以維護(hù)和擴(kuò)展。
這樣的原則在一些需要長期使用、迭代、維護(hù)的功能設(shè)計上,是非常重要的。我們要盡可能的讓大營銷的抽獎領(lǐng)域領(lǐng)域模塊具備獨立性,所以要使用單一職責(zé)原則。在這個原則約束下,設(shè)計了3個接口類;抽獎策略接口、獎品信息接口、庫存處理接口(異步扣減等),這樣3個接口的設(shè)計,在將來需要擴(kuò)展的時候,會非常容易。(可能會問具體編碼,問的比較多樣性,這部分需要自己閱讀代碼來學(xué)習(xí))
14. 在項目中你提到了可以支持不同場景的抽獎訴求,比如;多少積分后可以抽獎一個固定范圍的獎品,或者抽獎n次后,才可以中獎某個獎品。這部分你是怎么做的?庫表怎么設(shè)計的?
這塊的流程,就是前面關(guān)于大營銷抽獎領(lǐng)域模型的設(shè)計,從而確定的庫表設(shè)計。也就是常說的領(lǐng)域->驅(qū)動設(shè)計。
庫表包括;策略表、策略明細(xì)(庫存、概率、規(guī)則key)、獎品表、規(guī)則表、規(guī)則樹(3個表)—— 這部分在學(xué)習(xí)項目后,需要具備能在紙上畫出庫表ER圖。
15. 你的抽獎接口響應(yīng)時間是多少?
這樣的問題主要考察你是否做了項目的上線,以及了解過接口的響應(yīng)時間。如果做過就非常好回答,沒做過亂說是挺容易被繼續(xù)提問的。
參考數(shù)據(jù);2c2g 云服務(wù)器,部署項目(含mysql、redis),占用63%內(nèi)存,抽獎接口響應(yīng)時間為38~55毫秒(項目有完整的手把手部署教程,還有監(jiān)控部署教程,可以自己部署驗證)。
16.(開放問題)你在做項目中,什么問題難住你的時間最長,為什么?
這是一個開放問題,重點考察你對項目的開發(fā)中個人的積累。你可以針對自己的學(xué)習(xí)過程中,有哪個流程的實現(xiàn),讓你最為有感觸,即可回答。
如;可以對大營銷抽獎模型流程的設(shè)計和庫表設(shè)計,最為耗時,因為你不斷的在思考如何拆解出一個好擴(kuò)展的松耦合結(jié)構(gòu),同時拆解后,還要保證搜耦合下的高內(nèi)聚。所以這部分是比較耗時的。同時也可以說在設(shè)定某個方法的,名稱、入?yún)ⅰ⒊鰠r,做了大量的思考。因為名字的定義非常影響以后的理解。好的代碼就是文檔,所以對于這樣的東西花費不少時間。
三、干項目
這就是跟著小傅哥學(xué)習(xí)項目
可以獲得的收獲;從需求設(shè)定
、框架搭建
、領(lǐng)域設(shè)計
、庫表設(shè)計
、功能開發(fā)
、項目打包
、發(fā)布部署
、運維監(jiān)控
、簡歷編寫
、面試解析
,這樣一整條線的內(nèi)容,全部交給你!有了這樣一套的組合,那你學(xué)習(xí)完找份工作還不是嘎嘎滴容易!
這樣的項目學(xué)習(xí)在小傅哥星球「碼農(nóng)會鎖」有8個,每個都是從0到1開發(fā)并提供簡歷模板和面試題,并且還在繼續(xù)開發(fā),后續(xù)還將有更多!價格嘎嘎實惠,早點加入,早點提升自己。項目地址:https://gaga.plus