阿里p7技術專家後的萬字面經分享

阿里p7技術專家後的萬字面經分享!

 

說明:本文整理自石杉架構班學員LEO同學在儒猿技術交流羣的面經分享

⼤家好,⾃我介紹⼀下:10年經驗,普本畢業,座標北京,這次跳槽進⼊了阿⾥。分享⼀下這次⾯試經驗,以及平時學習的積累。

 

我的⼯作年限算是⽐較⻓,都有中年危機了,跟着石杉⽼師的架構課學習了兩年,做技術⼀路⾛過只有腳踏實地的學習總結還有多積累、多思考纔能有所進步,本次跳槽其實我是整整準備了⼀年半,充分利⽤週末和休假的時間學習提⾼,看⽯杉⽼師的課程的同時⼀定同步的做筆記,重要部分標紅,我還看了很多相關書籍,書籍⾥的例⼦也是每個都必須敲⼀遍,看書的同時也做筆記把重要的記下來並標紅,⾯試前⼀周做突擊⽤

 

 

面試了哪些公司? 

 

阿⾥巴巴、快⼿、滴滴、京東數科,拿到了哪些公司的offer:阿⾥巴巴、快⼿。由於已經拿到了⼼儀的offer,就沒有繼續約其他⼤⼚的⾯試了

 

 

面試前的準備 

 

java基礎,代表的有原⽣的List、Map、併發和線程池、TCP、⽹絡等知識點對應的⽼師的課程:

  1. java架構課程的JDK源碼剖析系列,還有架構課程⾥⾯的其他專題,

  2. 互聯⽹Java⼯程師⾯試突擊(第⼀⼆三季)

  3. 儒猿技術窩上⾯的所有專欄

這個⼀集不漏的需要看完看懂,⽼師畫的圖看⾃⼰再⼿動默寫⼏遍理解原理,這些基礎知識太重要,必!問!

 

 

面試官提問的部分問題 

 

這些問題我都會結合⽂字+流程圖/原理圖,做⾮常深⼊的解答問題:簡述HashMap的底層原理

(1) hash算法:爲什麼要⾼位和低位做異或運算?答:讓⾼位也參與hash尋址運算,降低hash衝突

(2) hash尋址:爲什麼是hash值和數組.length - 1進⾏與運算?答:因爲取餘算法效率很低,按位與運算效率⾼

(3) hash衝突的機制:鏈表,超過8個以後,紅⿊樹(數組的容量⼤於等於64)

(4) 擴容機制:數組2倍擴容,重新尋址(rehash),hash & n - 1,判斷⼆進制結果中是否多出⼀個bit的1,如果沒多,那麼就是原來的index,如果多了出來,那麼就是index + oldCap,通過這個⽅式。就避免了rehash的時候,⽤每個hash對新數組.length取模,取模性能不⾼,位運算的性能⽐較⾼,JDK 1.8以後,優化了⼀下,如果⼀個鏈表的⻓度超過了8,就會⾃動將鏈表轉換爲紅⿊樹,查找的性能, 是O(logn),這個性能是⽐O(n)要⾼的

(5) 紅⿊樹是⼆叉查找樹,左⼩右⼤,根據這個規則可以快速查找某個值

(6) 但是普通的⼆叉查找樹,是有可能出現瘸⼦的情況,只有⼀條腿,不平衡了,導致查詢性能變成O(n),線性查詢了

(7) 紅⿊樹,紅⾊和⿊⾊兩種節點,有⼀⼤堆的條件限制,儘可能保證樹是平衡的,不會出現瘸腿的情況

(8) 如果插⼊節點的時候破壞了紅⿊樹的規則和平衡,會⾃動重新平衡,變⾊(紅 <-> ⿊),旋轉,左旋轉,右旋轉

 

問題:volatile關鍵字底層原理,volatile關鍵字是否可以禁⽌指令重排以及如何底層如何實現的指令重排

(1) 這⾥貼下⽯杉⽼師在講volatile關鍵字底層原理畫的圖:硬件級別的原理:

 

下⾯是我根據⽼師的思路學習的筆記

 

(2) 主動從內存模型開始講起,原⼦性、可⻅性、有序性的理解,volatile關鍵字的原理java內存模型:

 

 

 (3) 可⻅性:⼀個線程修改了變量,其他線程能⻢上讀取到該變量的最新值read(從主存讀取),load(將主存讀取到的值寫⼊⼯作內存),use(從⼯作內存讀取數據來計算),assign(將計算好的值重新賦值到⼯作內存中),store(將⼯作內存數據寫⼊主存),write(將store過去的變量值賦值給主存中的變量) 這個是流程圖:

 

 

(4) volatile讀的內存語義如下:當讀⼀個volatile變量時,JMM會把該線程對應的本地內存置爲⽆效。線程接下來將從主內存中讀取共享變量。

這個是流程圖:

 

 

(4-1)當讀flag變量後,本地內存B包含的值已經被置爲⽆效。此時,線程B必須從主內存中讀取共享變量,線程B的讀取操作將導致本地內存B與主內存中的共享變量的值變成⼀致。

 

(4-2)volatile寫和volatile讀的內存語義總結:

  • 線程A寫⼀個volatile變量,實質上是線程A向接下來將要讀這個volatile變量的某個線程發出了(其對共享變量所做修改的)消息。

  • 線程B讀⼀個volatile變量,實質上是線程B接收了之前某個線程發出的(在寫這個volatile變量之前對共享變量所做修改的)消息。

  • 線程A寫⼀個volatile變量,隨後線程B讀這個volatile變量,這個過程實質上是線程A通過主內存向線程B 發送消息。

 

(5) 鎖的釋放和獲取的內存語義:當線程釋放鎖時,JMM會把該線程對應的本地內存中的共享變量刷新   到主內存中。

 

當線程獲取鎖時,JMM會把該線程對應的本地內存置爲⽆效。從⽽使得被監視器保護的臨界區代碼必須    從主內存中讀取變量。

 

(6) 有序性:基於happens-before原則來看volatile關鍵字如何保證有序性這個是流程圖:http://note.youdao.com/s/BPU2J7te

 

 

happens-before規則

(6-1)程序順序規則:⼀個線程中的每個操作,happens-before於該線程中的任意後續操作。

(6-2)監視器鎖規則:對⼀個鎖的解鎖,happens-before於隨後對這個鎖的加鎖。

(6-3)volatile變量規則:對⼀個volatile變量域的寫,happens-before於任意後續對這個volatile域的讀

(6-4)傳遞性:如果A happens-before B,且B happens-before C,那麼A happens-before C。

(6-5)start()規則:如果線程A執⾏操作ThreadB.start()(啓動線程B),那麼A線程的ThreadB.start()操作happens-before於線程B中的任意操作。

(6-6)join()規則:如果線程A執⾏操作ThreadB.join()併成功返回,那麼線程B中的任意操作happens- before與線程A從ThreadB.join()操作成功返回。

 

(7) 原⼦性:volatile關鍵字不能保證原⼦性,唯⼀的場景就是在32位虛擬機,對long/double變量的賦值寫是原⼦的,volatile關鍵字底層原理,lock指令以及內存屏

 

(8) lock指令:volatile實現的兩條原則

(8-1)Lock前綴指令會引起處理器緩存回寫到內存。

(8-2)⼀個處理器的緩存回寫到內存會導致其他處理器的緩存失效。

(8-3)緩存⼀致性協議:

 

 

問題:線程有⼏種狀態,狀態之間的變化是怎樣的?

Java線程在運⾏的聲明週期中可能處於6種不同的狀態,在給定的⼀個時刻,線程只能處於其中的⼀個狀態。這⾥我整理了⼏張圖:

 

 

 

問題:簡述線程池的原理,⾃定義線程池的參數以及每個參數的意思,線程池有哪⼏種,分別的應⽤場景舉例

⼤家先看下這個構造圖:

 

 

corePoolSize:線程池⾥應該有多少個線程

maximumPoolSize:如果線程池⾥的線程不夠⽤了,等待隊列還塞滿了,此時有可能根據不同的線程池的類型,可能會增加⼀些線程出來,但是最多把線程數量增加到maximumPoolSize指定的數量keepAliveTime + TimeUnit:如果你的線程數量超出了corePoolSize的話,超出corePoolSize指定數量的線程,就會在空閒keepAliveTime毫秒之後,就會⾃動被釋放掉

workQueue:你的線程池的等待隊列是什麼隊列

threadFactory:在線程池⾥創建線程的時候,你可以⾃⼰指定⼀個線程⼯⼚,按照⾃⼰的⽅式創建線程出來

RejectedExecutionHandler:如果線程池⾥的線程都在執⾏任務,然後等待隊列滿了,此時增加額外線  程也達到了maximumPoolSize指定的數量了,這個時候實在⽆法承載更多的任務了,此時就會執⾏這個東

⻄(拒絕策略)

 

上⾯的基本參數的意義以外,我還推薦⼤家看下美團技術團隊寫的《Java線程池實現原理及其在美團業務中的實踐》

https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html 這篇⽂章,寫的⾮常⼲。

 

問題:簡述OSI七層⽹絡模型,TCP/IP四層⽹絡模型

OSI七層⽹絡模型,⽹絡的七層加⼯從下到上主要包括物理層,數據鏈路層,⽹絡層,傳輸層,會話層, 表示層,應⽤層

這個是OSI七層⽹絡模型:

 

 

問題:簡述TCP三次握⼿以及四次揮⼿TCP三次握⼿的過程如下:

(1)客戶端發送SYN(seq=x)報⽂給服務器端,進⼊SYN_SEND狀態。

(2)服務器端收到SYN報⽂,迴應⼀個SYN(seq=y)和ACK(ack = x+1)報⽂,進⼊SYN_RECV狀態。

(3)客戶端收到服務器端的SYN報⽂,迴應⼀個ACK(ack=y+1)報⽂,進⼊Established狀態。TCP三次握⼿的過程圖:

 

 

 

TCP四次揮⼿的過程如下:

 

 

學習資料:⽯杉⽼師在架構班講的:《講給Java⼯程師聽的⼤⽩話⽹絡課程》

推薦書籍: 《⽹絡是怎樣連接的》《圖解TCP/IP》 《圖解⽹絡硬件》 《圖解HTTP》

 

問題:CMS垃圾回收的過程

這個是JVM內存劃分的圖:

 

 

這⾥援引下儒猿羣羣友根據《從 0 開始帶你成爲JVM實戰⾼⼿》專欄 總結出來的圖,分享給⼤家

https://www.processon.com/view/link/5e69db12e4b055496ae4a673

 

CMS的⼯作機制相對複雜,垃圾回收過程包含如下4個步驟

(1) 初始標記:只標記和GC Roots直接關聯的對象,速度很快,需要暫停所有⼯作線程。

(2) 併發標記:和⽤戶線程⼀起⼯作,執⾏GC Roots跟蹤標記過程,不需要暫停⼯作線程。

(3) 重新標記:在併發標記過程中⽤戶線程繼續運⾏,導致在垃圾回收過程中部分對象的狀態發⽣變化,    爲了確保這部分對象的狀態正確性,需要對其重新標記並暫停⼯作線程。

(4) 併發清除:和⽤戶線程⼀起⼯作,執⾏清除GC Roots不可達對象的任務,不需要暫停⼯作線程。

 

問題:G1與CMS的區別,你們公司使⽤的是哪個,爲什麼?(這個需要結合⾃⼰的業務場景回答) 相對於CMS垃圾收集器,G1垃圾收集器兩個突出的改進。

(1) 基於標記整理算法,不產⽣內存碎⽚。

(2) 可以精確地控制停頓時間,在不犧牲吞吐量的前提下實現短停頓垃圾回收。

 

問題:JVM參數舉例,講講爲什麼這麼設置,爲了避免fullGC的停頓對系統的影響,有哪些解決⽅案?由於⽂本不⽅便貼代碼,貼在在了有道雲筆記⾥⾯:

 

 

爲解決應⽤在午⾼峯發⽣ full gc ⽽影響系統響應時間問題, 考慮低峯期主動進⾏ full gc 對 old 區進⾏釋放.確保啓動參數中 -XX:+DisableExplicitGC 項被刪除, 該參數作⽤是禁⽌ System.gc() 調⽤. (啓動參數⼀般配在 start 腳本中)在啓動參數中加⼊  -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses, 該參數的作⽤是主動 System.gc() 時調⽤ CMS 算法進⾏ gc 操作.

 

問題:內存模型以及分區,需要詳細到每個區放什麼

JVM 分爲堆區和棧區,還有⽅法區,初始化的對象放在堆⾥⾯,引⽤放在棧⾥⾯, class 類信息常量池(static 常量和 static 變量)等放在⽅法區

(1) ⽅法區:主要是存儲類信息,常量池(static 常量和 static 變量),編譯後的代碼(字 節碼)等數據

(2) 堆:初始化的對象,成員變量 (那種⾮ static 的變量),所有的對象實例和數組都要 在堆上分配

(3) 棧:棧的結構是棧幀組成的,調⽤⼀個⽅法就壓⼊⼀幀,幀上⾯存儲局部變量表,操 作數棧,⽅法出

⼝等信息,局部變量表存放的是 8 ⼤基礎類型加上⼀個應⽤類型,所 以還是⼀個指向地址的指針

(4) 本地⽅法棧:主要爲 Native ⽅法服務

(5) 程序計數器:記錄當前線程執⾏的⾏號

 

 

問題:JVM內存分那⼏個區,每個區的作⽤是什麼?java 虛擬機主要分爲以下⼀個區:

⽅法區:

1. 有時候也成爲永久代,在該區內很少發⽣垃圾回收,但是並不代表不發⽣ GC,在這⾥ 進⾏的 GC 主要是對⽅法區⾥的常量池和對類型的卸載

2. ⽅法區主要⽤來存儲已被虛擬機加載的類的信息、常量、靜態變量和即時編譯器編譯後 的代碼等數據。

3. 該區域是被線程共享的。

4. ⽅法區⾥有⼀個運⾏時常量池,⽤於存放靜態編譯產⽣的字⾯量和符號引⽤。該常量池具有動態性,也就是說常量並不⼀定是編譯時確定,運⾏時⽣成的常量也會存在這個常量池中。

 

虛擬機棧:

1. 虛擬機棧也就是我們平常所稱的棧內存,它爲 java ⽅法服務,每個⽅法在執⾏的時候都會創建⼀個棧幀,⽤於存儲局部變量表、操作數棧、動態鏈接和⽅法出⼝等信息。

2. 虛擬機棧是線程私有的,它的⽣命週期與線程相同。

3. 局部變量表⾥存儲的是基本數據類型、returnAddress 類型(指向⼀條字節碼指令的地 址)和對象引⽤, 這個對象引⽤有可能是指向對象起始地址的⼀個指針,也有可能是代表 對象的句柄或者與對象相關聯的位置。局部變量所需的內存空間在編譯器間確定

4. 操作數棧的作⽤主要⽤來存儲運算結果以及運算的操作數,它不同於局部變量表通過索 引來訪問,⽽是壓棧和出棧的⽅式    5.每個棧幀都包含⼀個指向運⾏時常量池中該棧幀所屬⽅法的引⽤,持有這個引⽤是爲了 ⽀持⽅法調⽤過程中的動態連接.動態鏈接就是將常量池中的符號引⽤在運⾏期轉化爲直接 引⽤。

 

本地⽅法棧和虛擬機棧類似,只不過本地⽅法棧爲 Native ⽅法服務。

堆:java 堆是所有線程所共享的⼀塊內存,在虛擬機啓動時創建,⼏乎所有的對象實例都在這 ⾥創建,因此該區域經常發⽣垃圾回收操作。

程序計數器內存空間⼩,字節碼解釋器⼯作時通過改變這個計數值可以選取下⼀條需要執⾏的字節碼 指令,分⽀、循環、跳轉、異常處理和線程恢復等功能都需要依賴這個計數器完成。該內 存區域是唯⼀⼀個java 虛擬機規範沒有規定任何 OOM 情況的區域。

 

問題:堆⾥⾯的分區:Eden,survival (from+ to),⽼年代,各⾃的特點。

堆⾥⾯分爲新⽣代和⽼⽣代(java8 取消了永久代,採⽤了 Metaspace),新⽣代包 含 Eden+Survivor 區,survivor 區⾥⾯分爲 from 和 to 區,內存回收時,如果⽤的是復 制算法,從 from 複製到 to,當經過⼀次或者多次 GC 之後,存活下來的對象會被移動 到⽼年區,當 JVM 內存不夠⽤的時候,會觸發 Full GC,清理 JVM ⽼年區 當新⽣區滿了之後會觸發 YGC,先把存活的對象放到其中⼀個Survice 區,然後進⾏垃圾清理。

 

因爲如果僅僅清理需要刪除的對象,這樣會導致內存碎 ⽚,因此⼀般會把 Eden 進⾏完全的清理,然後整理內存。那麼下次 GC 的時候,就會使⽤下⼀個 Survive,這樣循環使⽤。如果有特別⼤的對象,新⽣代放不下, 就會使⽤⽼年代的擔保,直接放到⽼年代⾥⾯。因爲 JVM 認爲,⼀般⼤對象的存 活時間⼀般⽐較久遠。

 

問題:如何判斷⼀個對象是否存活?(或者GC對象的判定⽅法) 判斷⼀個對象是否存活有兩種⽅法:

 

1. 引⽤計數法 

所謂引⽤計數法就是給每⼀個對象設置⼀個引⽤計數器,每當有⼀個地⽅引⽤這個對象時,就將計數器加⼀,引⽤失效時,計數器就減⼀。當⼀個對象的引⽤計數器爲零時,說明此對象沒有被引⽤,也就是「死對象」,將會被垃圾回收. 引⽤計數法有⼀個缺陷就是⽆法解決循環引⽤問題,也就是說當對象 A 引⽤對象 B,對象 B ⼜引⽤者對象 A,那麼此時 A,B對象的引⽤計數器都不爲零,也就造成⽆法完成垃圾回 收,所以主流的虛擬機都沒有采⽤這種算法。

 

2. 可達性算法(引⽤鏈法)

該算法的思想是:從⼀個被稱爲 GC Roots 的對象開始向下搜索,如果⼀個對象到 GC Roots 沒有任何引⽤鏈相連時,則說明此對象不可⽤。在 java 中可以作爲 GC Roots 的對象有以下⼏種: • 虛擬機棧中引⽤的對象,⽅法區類靜態屬性引⽤的對象 • ⽅法區常量池引⽤的對象

 

本地⽅法棧 JNI 引⽤的對象 雖然這些算法可以判定⼀個對象是否能被回收,但是當滿⾜上述條件時,⼀個對象⽐不⼀定會被回收。當⼀個對象不可達 GC Root 時,這個對象並 不會⽴⻢被回收,⽽是出於⼀個死緩的階段,若要被真正的回收需要經歷兩次標記,如果對象在可達性分析中沒有與  GC Root 的引⽤鏈,那麼此時就會被第⼀次標記並且進⾏ ⼀次篩選,篩選的條件是是否有必要執⾏finalize()⽅法。當對象沒有覆蓋 finalize()⽅法或者已被虛擬機調⽤過,那麼就認爲是沒必要的。

 

如果該對象有必要執⾏ finalize()⽅法,那麼這個對象將會放在⼀個稱爲 F-Queue 的對隊 列中,虛擬機會觸發⼀個 Finalize()線程去執⾏,此線程是低優先級的,並且虛擬機不會承諾⼀直等待它運⾏完,這是因爲如果 finalize()執⾏緩慢或者發⽣了死鎖,那麼就會造成 F- Queue 隊列⼀直等待,造成了內存回收系統的崩潰。GC 對處於 F-Queue 中的對象進⾏ 第⼆次被標記,這時,該對象將被移除」即將回收」集合,等待回收。

 

 

問題:服務類加載過多引發的OOM問題如何排查

如果服務出現⽆法調⽤接⼝假死的情況,⾸先要考慮的是兩種問題

(1) 第⼀種問題,這個服務可能使⽤了⼤量的內存,內存始終⽆法釋放,因此導致了頻繁GC問題。也許每秒都執⾏⼀次Full GC,結果每次都回收不了多少,最終導致系統因爲頻繁GC,頻繁Stop the World,接⼝調⽤出現頻繁假死的問題

 

(2) 第⼆種問題,可能是這臺機器的CPU負載太⾼了,也許是某個進程耗盡了CPU資源,導致你這個服務的線程始終⽆法得到CPU資源去執⾏,

也就⽆法響應接⼝調⽤的請求。

這也是⼀種情況。

 

在內存使⽤這麼⾼的情況下會發⽣什麼?

第⼀種,是內存使⽤率居⾼不下,導致頻繁的進⾏Full GC,gc帶來的stop the world問題影響了服務。

第⼆種,是內存使⽤率過多,導致JVM⾃⼰發⽣OOM。

第三種,是內存使⽤率過⾼,也許有的時候會導致這個進程因爲申請內存不⾜,直接被操作系統把這個進

程給殺掉了

 

問題:如何在JVM內存溢出的時候⾃動dump內存快照?

-XX:+HeapDumpOnOutOfMemoryError

-XX:HeapDumpPath=/usr/local/app/oom

第⼀個參數意思是在OOM的時候,⾃動dump內存快照出來,第⼆個參數是說把內存快照放到哪去

⾃⼰閱讀的書籍舉例:《實戰Java虛擬機:JVM故障診斷與性能優化(第2版)》

Netty知識點對應的⽼師的課程:《Netty核⼼功能精講以及核⼼源碼剖析》 問題:NIO開發的話爲什麼選擇netty

不選擇Java原⽣NIO編程的原因

(1) NIO的類庫和API的繁雜,使⽤麻煩,你需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。

 

(2) 需要具備其他的額外技能做鋪墊,例如熟悉Java多線程編程。這是因爲NIO編程涉及到Reactor模 式,你必須對多線程和⽹絡編程⾮常熟悉,才能寫出⾼質量的NIO程序。

 

(3) 可靠性能⼒補⻬,⼯作量和難度都⾮常⼤。例如客戶端⾯臨重連、⽹絡閃斷、半包讀寫、失敗緩存、⽹絡擁塞和異常碼流的處理的問題,NIO編程的特點就是功能開發相對容易,但是可靠性能⼒補⻬⼯作量和難度都⾮常⼤

 

(4) JDK NIO的BUG,例如臭名昭著的epoll bug,它會導致Selector空輪詢,最終導致CPU 100%

 

爲什麼選擇Netty

(1) API使⽤簡單,開發⻔檻低;

(2) 功能強⼤,預置了多種編解碼弄能,⽀持多種主流協議;

(3) 定製能⼒強,可以通過ChannelHandler對通信框架進⾏靈活地擴展;

(4) 性能⾼,通過與其他業界主流的NIO框架對⽐,Netty的綜合性能最優;

(5) 成熟、穩定,Netty修復了已經發現的所有JDK NIO BUG,業務開發⼈員不需要再爲NIO的BUG⽽煩惱;

(6) 社區活躍,版本迭代週期短,發現的BUG可以被及時修復,同時,更多的新功能會加⼊;

(7) 經歷了⼤規模的商業應⽤考驗,質量得到驗證。

 

 

問題:簡述TCP粘包拆包以及解決⽅案

開局⼀個圖:

 

 

假設客戶端分別發送了兩個數據包D1和D2給服務端,由於服務端⼀次讀取到的字節數是不確定的,故可能存在以下4種情況

 

(1) 服務端分兩次讀取到了兩個獨⽴的數據包,分別是D1和D2,沒有粘包和拆包;

(2) 服務端⼀次接收到了兩個數據包,D1和D2粘合在⼀起,被稱爲TCP粘包;

(3) 服務端分兩次讀取到了兩個數據包,第⼀次讀取到了完整的D1包和D2包的部分內容,第⼆次讀取到           了D2包的剩餘內容,這被稱爲TCP拆包;

(4) 服務端分兩次讀取到了兩個數據包,第⼀次讀取到了D1包的部分內容D1_1,第⼆次讀取到了D1包的       剩餘內容D1_2和D2包的整包。

 

TCP粘包/拆包發⽣的原因

(1) 應⽤程序write寫⼊的字節⼤⼩⼤於套接⼝發送緩衝區⼤⼩;

(2) 進⾏MSS(Maxitum Segment Size 最⼤分段⼤⼩)⼤⼩的TCP分段;

(3) 以太⽹幀的payload⼤於MTU(Maxitum Transmission Unit 最⼤傳輸單元)進⾏IP分⽚。

 

粘包問題的解決策略

(1) 消息定⻓,例如每個報⽂的⼤⼩爲固定⻓度200字節,如果不夠,空位補空格;

(2) 在包尾增加回⻋換換符進⾏分割,例如FTP協議;

(3) 將消息分爲消息頭和消息體,消息頭中包含表示消息總⻓度(或者消息體⻓度)的字段,通常設計思  想爲消息頭的⼀個字段使⽤int32來表示消息的總⻓度;

(4) 更復雜的應⽤層協議。

 

 

問題:簡述netty服務端和客戶端創建的流程

看下這個圖:

 

 

在⾯試的時候回答這個圖⾥⾯的流程

 

問題:簡述Netty的線程模型(這個最好畫圖,顯示出⾃⼰思路清新) 現場畫圖:

 

問題:Netty解決了java原⽣NIO哪些問題(空輪詢的bug,這個⼀定要說出來)

⼤家看下這個博客寫的挺好的:

https://blog.csdn.net/baiye_xing/article/details/73351330

 

 

問題:多路復⽤、零拷⻉等原理

1. 傳統數據傳送

傳統數據從Socket⽹絡中傳送,需要4次數據拷⻉和4次上下⽂切換:

• 將磁盤⽂件,讀取到操作系統內核緩衝區;

• 將內核緩衝區的數據,拷⻉到⽤戶空間的緩衝區;

• 數據從⽤戶空間緩衝區拷⻉到內核的socket⽹絡發送緩衝區;

• 數據從內核的socket⽹絡發送緩衝區拷⻉到⽹卡接⼝(硬件)的緩衝區,由⽹卡進⾏⽹絡傳輸。

這個是流程圖:

 

傳統⽅式,讀取磁盤⽂件並進⾏⽹絡發送,經過的4次數據拷⻉和4次上下⽂切換是⾮常繁瑣的。實際IO讀   寫,需要進⾏IO中斷,需要CPU響應中斷(帶來上下⽂切換),儘管後來引⼊DMA來接管CPU的中斷請求,但四次拷⻉仍在存在不必要的環節。

 

 

2. 零拷⻉實現原理

零拷⻉的⽬的是爲了減少IO流程中不必要的拷⻉,以及減少⽤戶進程地址空間和內核地址空間之間因爲上    下⽂切換⽽帶來的開銷。

 

由於虛擬機不能直接操作內核,因此它的實現需要操作系統OS的⽀持,也就是需要kernel內核暴漏API。

 

2.1 Netty中的零拷⻉

1. Direct Buffers:Netty的接收和發送ByteBuffer採⽤直接緩衝區(Direct Buffer)實現零拷⻉,直接在內存區域分配空間,避免了讀寫數據的⼆次內存拷⻉,這就實現了讀寫Socket的零拷⻉。

 

如果使⽤傳統的堆內存緩衝區(Heap Buffer)進⾏Socket讀寫,JVM會將堆內存Buffer拷⻉到直接內存中,然後才寫⼊Socket中。相⽐堆外直接內存,消息在發送過程中多了⼀次緩衝區的內存拷⻉。

 

2. CompositeByteBuf:它可以將多個ByteBuf封裝成ByteBuf,對外提供統⼀封裝後的ByteBuf接⼝。CompositeByteBuf並沒有真正將多個Buffer組合起來,⽽是保存了它們的引⽤,從⽽避免了數據的拷⻉,實現了零拷⻉。

 

傳統的ByteBuffer,如果需要將兩個ByteBuffer中的數據組合到⼀起,我們需要⾸先創建⼀個size=size1+size2⼤⼩的新的數組,然後將兩個數組中的數據拷⻉到新的數組中。但是使⽤Netty提供的組合ByteBuf,就可以避免這樣的操作。

 

3. Netty的⽂件傳輸類DefaultFileRegion通過調⽤FileChannel.transferTo()⽅法實現零拷⻉,⽂件緩衝區的數據會直接發送給⽬標Channel。底層調⽤Linux操作系統中的sendfile()實現的,數據從⽂件由DMA 引擎拷⻉到內核read緩衝區,;DMA從內核read緩衝區將數據拷⻉到⽹卡接⼝(硬件)的緩衝區,由⽹卡進⾏⽹絡傳輸。

 

 

問題:簡述netty整體架構

⽼師在講netty的時候整體架構圖:

把⽼師這張圖熟記於⼼,這個流程最好能在⾯試中畫出來。

 

Redis知識點對應的⽼師的課程:億級流量電商詳情⻚系統實戰

⾯試官提問的部分問題:

問題:分別介紹下redis的內存模型和線程模型

https://gitee.com/shishan100/Java-Interview-Advanced/blob/master/docs/high-concurrency/redis-single-thread-model.md  (⽼師的⾯試訓練營)

 

問題:緩存雪崩以及穿透的解決⽅案?緩存雪崩發⽣的現象,緩存雪崩的事前事中事後的解決⽅案

 

事前:redis⾼可⽤,主從+哨兵,redis cluster,避免全盤崩潰

事中:本地ehcache緩存 + hystrix限流&降級,避免MySQL被打死事後:redis持久化,快速恢復緩存數據

 

緩存雪崩現象圖:

 

如何解決緩存⾎崩 :

 

緩存穿透現象以及解決⽅案:

 

 

 

簡述redis分佈式鎖的原理

https://gitee.com/shishan100/Java-Interview-Advanced/blob/master/docs/distributed-system/distributed-lock-redis-vs-zookeeper.md ⽼師的⾯試訓練營

⾃⼰閱讀的書籍舉例:《Redis設計與實現》

 

ZooKeeper

知識點對應的⽼師的課程:《08_ZooKeeper頂尖⾼⼿課程:從實戰到源碼》問題:2PC與3PC是什麼,兩者的流程,以及優缺點

 

2PC, 即⼆階段提交, 爲了是基於分佈式系統架構下的所有節點在進⾏事務處理過程中能夠保持原⼦性和⼀致性⽽設計的⼀種算法。

 

通常,⼆階段提交協議也被認爲是⼀種⼀致性協議,⽤來保證分佈式系統數據的⼀致性。⽬前絕⼤部分的關係型數據庫都是採⽤⼆階段提交協議,來完成分佈式處理的,利⽤該協議能夠⾮常⽅便地完成所有分佈式事務參與者的協調,統⼀決定事務的提交或回滾,從⽽能夠有效地保證分佈式數據⼀致性,因  此⼆階段提交協議被⼴泛地應⽤在許多分佈式系統中。

 

階段⼀:提交事務請求

(1) 事務詢問

(2) 執⾏事務。

(3) 各參與者向協調者反饋事務詢問的響應。階段⼆:執⾏事務提交

(1) 發送提交請求

(2) 事務提交

(3) 反饋事務提交結果

(4) 完成事務

 

中斷事務

(1) 發送回滾請求

(2) 事務回滾

(3) 反饋事務回滾結果

⼆階段提交協議的優點:原理簡單,實現⽅便

⼆階段提交協議的缺點:同步阻塞,單點問題,腦裂,太過保守這個是流程圖:

 

3PC,即三階段提交,是2PC的改進版,其將⼆階段提交協議的提交事務請求過程⼀分爲⼆,形成了由CanCommit、PreCommit和doCommit三個階段組成的事務處理協議

階段⼀:CanCommit

1、事務詢問

2、各參與者向協調者反饋事務詢問的響應。

 

階段⼆:PreCommit

執⾏事務預提交

1、發送預提交請求

2、事務預提交

3、各參與者向協調者反饋事務執⾏的響應。

 

中斷事務

1、發送中斷請求。

2、中斷事務

 

階段三:doCommit

1、發送提交請求

2、事務提交

3、反饋事務提交結果

4、完成事務

 

中斷事務

1、發送中斷請求

2、事務回滾

3、反饋事務回滾結果

4、中斷事務

 

三階段提交協議的優點:相較於⼆階段提交協議,三階段提交協議最⼤的優點就是降低了參與者的阻塞範圍,並能夠在出現單點故障後繼續達成⼀致。

 

三階段提交協議的缺點:三階段提交協議在去除阻塞的同時也引⼊了新的問題,那就是在參與者接收到PreCommit消息後,如果⽹絡出現分區,此時協調者所在的節點和參與者⽆法進⾏正常的⽹絡通信,在這種情況下,該參與者依然會進⾏事務提交,這必然出現數據的不⼀致。

 

這個是流程圖:

 

問題:簡述ZAB協議

類似於⼀個兩階段提交。

(1) 羣⾸向所有追隨者發送⼀個PROPOSAL消息p。

(2) 當⼀個追隨者接收到消息p後,會響應羣⾸⼀個ACK消息,通知羣⾸其已接受提案(proposal)

(3) 當收到仲裁數量的服務器發送的確認消息後(該仲裁數包括羣⾸⾃⼰),羣⾸就會發送消息通知追隨者進⾏提交(COMMIT)操作。

 

問題:強⼀致性和最終⼀致性的區別,ZooKeeper的⼀致性是怎樣的?

強⼀致性:只要寫⼊⼀條數據,⽴⻢⽆論從zk哪臺機器上都可以⽴⻢讀到這條數據,強⼀致性,你的寫⼊ 操作卡住,直到leader和全部follower都進⾏了commit之後,才能讓寫⼊操作返回,認爲寫⼊成功了,此時只要寫⼊成功,⽆論你從哪個zk機器查詢,都是能查到的,強⼀致性

 

 

ZAB協議機制,zk⼀定不是強⼀致性

最終⼀致性:寫⼊⼀條數據,⽅法返回,告訴你寫⼊成功了,此時有可能你⽴⻢去其他zk機器上查是查不  到的,短暫時間是不⼀致的,但是過⼀會⼉,最終⼀定會讓其他機器同步這條數據,最終⼀定是可以查到的

 

研究了ZooKeeper的ZAB協議之後,你會發現,其實過半follower對事務proposal返回ack,就會發送commit給所有follower了,只要follower或者leader進⾏了commit,這個數據就會被客戶端讀取到了

 

那麼有沒有可能,此時有的follower已經commit了,但是有的follower還沒有commit?

 

絕對會的,所以 有可能其實某個客戶端連接到follower01,可以讀取到剛commit的數據,但是有的客戶端連接到follower02在這個時間還沒法讀取到,所以zk不是強⼀致的,不是說leader必須保證⼀條數據被全部follower都commit了纔會讓你讀取到數據,⽽是過程中可能你會在不同的follower上讀取到不⼀致的數據,但是最終⼀定會全部commit後⼀致,讓你 讀到⼀致的數據的

 

zk官⽅給⾃⼰的定義:順序⼀致性

因此zk是最終⼀致性的,但是其實他⽐最終⼀致性更好⼀點,出去要說是順序⼀致性的,因爲leader⼀定會保證所有的proposal同步到follower上都是按照順序來⾛的,起碼順序不會亂。但是全部follower的數據⼀致確實是最終才能實現⼀致的如果要求強⼀致性,可以⼿動調⽤zk的sync()操作

 

問題:⽺羣效應是什麼,如何解決的?

zk在共享鎖的獲取和釋放流程圖:

 

在整個分佈式鎖的競爭過程中,⼤量的「Watcher通知」和「⼦節點列表獲取」兩個操作重複運⾏,並且絕⼤多數的運⾏結果都是判斷出⾃⼰並⾮是序號最⼩的節點,從⽽繼續等待下⼀次通知,這看起來顯然不怎麼科學。客戶端⽆端地接收到過多和⾃⼰不相關的事件通知,如果在集羣規模⽐較⼤的=情況下,不僅會對ZooKeeper服務器造成巨⼤的性能影響和⽹絡衝擊。

 

更爲嚴重的是,如果同⼀時間有多個節點對應的客戶端完成事務或是事務中斷引起節點消失,ZooKeeper 服務器就會在短時間內向其餘客戶端發送⼤量的事件通知,這就是⽺羣效應。這個ZooKeeper分佈式鎖實現中出現⽺羣效應的根源在於,沒有找準客戶端真正的關注點。我們再來回顧⼀下上⾯分佈式鎖的競爭過程。

 

它的核⼼邏輯在於:判斷⾃⼰是否是所有⼦節點中序號最⼩的。於是很容易可以聯想到,每個節點對應的    客戶端只需要關注⽐⾃⼰序號⼩的那個相關節點的變更情況就可以了,⽽不需要關注全局的⼦列表變更情況。

改進過的zk在共享鎖的獲取和釋放流程圖:

 

 

 

小結 

 

我本次⾯試阿⾥,⾯試總共經歷了6輪,前3輪技術⾯試都做了算法題,第四輪技術最終⾯試沒有做算法題,聊項⽬和離職原因等。

 

HR我⾯了2輪,第⼀輪HR⾯試主要聊⼊職阿⾥要做的產品以及我本⼈的⼀個職業發展規劃,第⼆輪HR⾯試是HRBP⾯的,主要是談薪資和股票等。

 

⼀些技術的問題⼤概就是上⾯列舉出來的,重點是問我⼯作經歷中做的項⽬,以及項⽬中的設計,遇到的問題以及解決⽅案,還有就是⾯試官會給出⼀個他們產品中遇到的問題讓你通過你過往的⼯作經驗給⼀個解決⽅案,也就是技術探討。

 

我本次⾯試的重點說的項⽬是⾃研API⽹關,apollo配置中⼼的⼆次開發,運單系統的分庫分表⽅案等。

 

要想進⼊例如阿⾥這樣的⼤⼚,必須要⾃⼰有⼀些含⾦量⽐較⾼的項⽬拿出來給⾯試官講解,並且要講解細節,例如項⽬整體的架構,整體流程,項⽬部署的機器配置,平時與活動的QPS峯值,流量估算經驗,⾃定義擴展開發或者⾃研的原  因,踩過哪些坑,以及解決的⽅案是什麼

 

⽯杉⽼師的互聯⽹java⼯程師⾯試突擊訓練第⼀季、第⼆季、第三季都要看完並且理解,⼒扣註冊⼀個會員,刷題,我⽤了1年半刷了140道題,題量不要求多,但要有代表性,例如:鏈表、遞歸、迭代等,然後充分理解解題思路即可,平時沒事的時候,對着題能把代碼寫出來