招銀網(wǎng)絡(luò)一面,還算基礎(chǔ)!

JavaGuide 官方網(wǎng)站:javaguide.cn
JavaGuide&Java面試交流圈:JavaGuide 官方知識(shí)星球
前段時(shí)間分享了阿里淘天和快手一面的面經(jīng),都是附帶詳細(xì)參考答案那種。
今天再給大家分享一位武漢理工大學(xué)同學(xué)的招銀網(wǎng)絡(luò)一面面經(jīng),同樣附帶超詳細(xì)的參考答案。大家可以用來查漏補(bǔ)缺,針對(duì)性地補(bǔ)短板。
招銀網(wǎng)絡(luò)一面還是比較簡單的,基本都是一些比較重要且高頻的常規(guī)八股,項(xiàng)目問的不多。到了二面的時(shí)候, 會(huì)開始主要考察你的項(xiàng)目。
很多同學(xué)覺得這種基礎(chǔ)問題的考查意義不大,實(shí)際上還是很有意義的,這種基礎(chǔ)性的知識(shí)在日常開發(fā)中也會(huì)需要經(jīng)常用到。例如,線程池這塊的拒絕策略、核心參數(shù)配置什么的,如果你不了解,實(shí)際項(xiàng)目中使用線程池可能就用的不是很明白,容易出現(xiàn)問題。而且,其實(shí)這種基礎(chǔ)性的問題是最容易準(zhǔn)備的,像各種底層原理、系統(tǒng)設(shè)計(jì)、場景題以及深挖你的項(xiàng)目這類才是最難的!
1、自我介紹
自我介紹一般是你和面試官的第一次面對(duì)面正式交流,換位思考一下,假如你是面試官的話,你想聽到被你面試的人如何介紹自己呢?一定不是客套地說說自己喜歡編程、平時(shí)花了很多時(shí)間來學(xué)習(xí)、自己的興趣愛好是打球吧?
我覺得一個(gè)好的自我介紹應(yīng)該包含這幾點(diǎn)要素:
-
用簡單的話說清楚自己主要的技術(shù)棧于擅長的領(lǐng)域; -
把重點(diǎn)放在自己在行的地方以及自己的優(yōu)勢之處; -
重點(diǎn)突出自己的能力比如自己的定位的 bug 的能力特別厲害;
自我介紹并不需要死記硬背,記住要說的要點(diǎn),面試的時(shí)候根據(jù)公司的情況臨場發(fā)揮也是沒問題的。另外,網(wǎng)上一般建議的是準(zhǔn)備好兩份自我介紹:一份對(duì) hr 說的,主要講能突出自己的經(jīng)歷,會(huì)的編程技術(shù)一語帶過;另一份對(duì)技術(shù)面試官說的,主要講自己會(huì)的技術(shù)細(xì)節(jié)和項(xiàng)目經(jīng)驗(yàn)。
自我介紹模板:
2、項(xiàng)目中的 Dubbo 用的什么通信協(xié)議
協(xié)議是兩個(gè)網(wǎng)絡(luò)實(shí)體進(jìn)行通信的基礎(chǔ),它規(guī)定了數(shù)據(jù)在網(wǎng)絡(luò)中的傳輸內(nèi)容和格式。除了必要的請求和響應(yīng)數(shù)據(jù)之外,協(xié)議通常還包含一些控制數(shù)據(jù),例如版本號(hào)、事件類型、序列化方式、壓縮方式、超時(shí)時(shí)間等等。
Dubbo2 的時(shí)候,一般使用 Dubbo 協(xié)議(這里指的是 Dubbo2 默認(rèn)協(xié)議) + 自定義序列化(如 Hessian2、ProtoBuf、Kryo、FST)。
<!-- 協(xié)議使用 Dubbo,序列化使用Kryo-->
<dubbo:protocol name="dubbo" serialization="kryo"/>
<!-- 協(xié)議使用 Dubbo,序列化使用FST-->
<dubbo:protocol name="dubbo" serialization="fst"/>
Dubbo 協(xié)議的格式如下:
-
0-15(Magic - Magic High & Magic Low): 魔數(shù),判斷是否是 dubbo 協(xié)議 -
16(Req/Res): 標(biāo)識(shí)是請求或響應(yīng)。請求:1; 響應(yīng):0。 -
17(2 Way): 僅在 Req/Res 為 1(請求)時(shí)才有用,標(biāo)記是否期望從服務(wù)器返回值。如果需要來自服務(wù)器的返回值,則設(shè)置為 1。 -
18(Event): 表示是否為事件消息,如心跳事件 -
19-23(Serialization ID): 標(biāo)識(shí)序列化類型,為數(shù)字,每個(gè)數(shù)字指代一種序列化類型。 -
24-31(Status): 標(biāo)識(shí)響應(yīng)狀態(tài)(類似 Http Status)。 -
32-63(Request ID):請求 id,類型為 long。 -
64-95(Data Length): 內(nèi)容長度(字節(jié)) -
96-?(Variable Part): 序列化后的內(nèi)容(換行符分隔)
可以看出,Dubbo 協(xié)議設(shè)計(jì)上還是挺緊湊的,是由頭部和內(nèi)容組成的格式。頭部占用 96 字節(jié),96 字節(jié)之后的內(nèi)容就是序列化后的內(nèi)容,默認(rèn)采用 Hessian2 序列化方式,也可以根據(jù)需要更換為 Fastjson、ProtoBuf、Kryo、FST 等其他序列化方式。
如果我們自己要寫一個(gè) RPC 框架的,也可以參考著 Dubbo 協(xié)議的格式來設(shè)計(jì)。我之前寫的玩具級(jí)別的 RPC 框架就是模仿的 Dubbo 協(xié)議。
Triple 協(xié)議是 Dubbo3 推出的主力協(xié)議,被稱為下一代 RPC 協(xié)議。它完全兼容 gRPC 協(xié)議,支持 Request-Response、Streaming 流式等通信模型,可同時(shí)運(yùn)行在 HTTP/1 和 HTTP/2 之上,讓你可以直接使用 curl、瀏覽器訪問后端 Dubbo 服務(wù)。并且,Triple 協(xié)議的還具備下面這些特性:
-
具備跨語言交互的能力 -
支持 TLS 加密、Plaintext 明文數(shù)據(jù)傳輸 -
支持反壓與限流 -
……
Triple 協(xié)議是 Dubbo 協(xié)議的升級(jí)版,它解決了 Dubbo 協(xié)議在跨語言、云原生、網(wǎng)關(guān)代理等方面的互通性問題。
Dubbo 框架實(shí)現(xiàn)專注在 Triple 協(xié)議自身,而對(duì)于底層的網(wǎng)絡(luò)通信、HTTP/2 協(xié)議解析等選擇依賴那些經(jīng)過長期檢驗(yàn)的網(wǎng)絡(luò)庫。比如 Dubbo Java 基于 Netty 構(gòu)建,而 Dubbo Go 則是直接使用的 Go 官方 HTTP 庫。
如果決定要升級(jí)到 Dubbo3 的 Triple 協(xié)議,只需要修改配置中的協(xié)議名稱為 tri (注意: 不是 triple)即可。Dubbo3 為了完美兼容 Dubbo2,做了很多工作來保證升級(jí)的無感,目前默認(rèn)的序列化和 Dubbo2 保持一致(Hessian2)。
更多關(guān)于 Dubbo 通信協(xié)議的介紹,建議閱讀官網(wǎng)文檔:https://cn.dubbo.apache.org/zh-cn/overview/home/。
3、講講 BIO,NIO,AIO,IO 多路復(fù)用了解嗎?
BIO (Blocking I/O)
BIO 屬于同步阻塞 IO 模型 。
同步阻塞 IO 模型中,應(yīng)用程序發(fā)起 read 調(diào)用后,會(huì)一直阻塞,直到內(nèi)核把數(shù)據(jù)拷貝到用戶空間。
在客戶端連接數(shù)量不高的情況下,是沒問題的。但是,當(dāng)面對(duì)十萬甚至百萬級(jí)連接的時(shí)候,傳統(tǒng)的 BIO 模型是無能為力的。因此,我們需要一種更高效的 I/O 處理模型來應(yīng)對(duì)更高的并發(fā)量。
NIO (Non-blocking/New I/O)
Java 中的 NIO 于 Java 1.4 中引入,對(duì)應(yīng) java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解為 Non-blocking,不單純是 New。它是支持面向緩沖的,基于通道的 I/O 操作方法。對(duì)于高負(fù)載、高并發(fā)的(網(wǎng)絡(luò))應(yīng)用,應(yīng)使用 NIO 。
Java 中的 NIO 可以看作是 I/O 多路復(fù)用模型。也有很多人認(rèn)為,Java 中的 NIO 屬于同步非阻塞 IO 模型。
跟著我的思路往下看看,相信你會(huì)得到答案!
我們先來看看 同步非阻塞 IO 模型。
同步非阻塞 IO 模型中,應(yīng)用程序會(huì)一直發(fā)起 read 調(diào)用,等待數(shù)據(jù)從內(nèi)核空間拷貝到用戶空間的這段時(shí)間里,線程依然是阻塞的,直到在內(nèi)核把數(shù)據(jù)拷貝到用戶空間。
相比于同步阻塞 IO 模型,同步非阻塞 IO 模型確實(shí)有了很大改進(jìn)。通過輪詢操作,避免了一直阻塞。
但是,這種 IO 模型同樣存在問題:應(yīng)用程序不斷進(jìn)行 I/O 系統(tǒng)調(diào)用輪詢數(shù)據(jù)是否已經(jīng)準(zhǔn)備好的過程是十分消耗 CPU 資源的。
這個(gè)時(shí)候,I/O 多路復(fù)用模型 就上場了。
IO 多路復(fù)用模型中,線程首先發(fā)起 select 調(diào)用,詢問內(nèi)核數(shù)據(jù)是否準(zhǔn)備就緒,等內(nèi)核把數(shù)據(jù)準(zhǔn)備好了,用戶線程再發(fā)起 read 調(diào)用。read 調(diào)用的過程(數(shù)據(jù)從內(nèi)核空間 -> 用戶空間)還是阻塞的。
目前支持 IO 多路復(fù)用的系統(tǒng)調(diào)用,有 select,epoll 等等。select 系統(tǒng)調(diào)用,目前幾乎在所有的操作系統(tǒng)上都有支持。
select 調(diào)用:內(nèi)核提供的系統(tǒng)調(diào)用,它支持一次查詢多個(gè)系統(tǒng)調(diào)用的可用狀態(tài)。幾乎所有的操作系統(tǒng)都支持。 epoll 調(diào)用:linux 2.6 內(nèi)核,屬于 select 調(diào)用的增強(qiáng)版本,優(yōu)化了 IO 的執(zhí)行效率。
IO 多路復(fù)用模型,通過減少無效的系統(tǒng)調(diào)用,減少了對(duì) CPU 資源的消耗。
Java 中的 NIO ,有一個(gè)非常重要的選擇器 ( Selector ) 的概念,也可以被稱為 多路復(fù)用器。通過它,只需要一個(gè)線程便可以管理多個(gè)客戶端連接。當(dāng)客戶端數(shù)據(jù)到了之后,才會(huì)為其服務(wù)。
AIO (Asynchronous I/O)
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改進(jìn)版 NIO 2,它是異步 IO 模型。
異步 IO 是基于事件和回調(diào)機(jī)制實(shí)現(xiàn)的,也就是應(yīng)用操作之后會(huì)直接返回,不會(huì)堵塞在那里,當(dāng)后臺(tái)處理完成,操作系統(tǒng)會(huì)通知相應(yīng)的線程進(jìn)行后續(xù)的操作。
目前來說 AIO 的應(yīng)用還不是很廣泛。Netty 之前也嘗試使用過 AIO,不過又放棄了。這是因?yàn)椋琋etty 使用了 AIO 之后,在 Linux 系統(tǒng)上的性能并沒有多少提升。
最后,來一張圖,簡單總結(jié)一下 Java 中的 BIO、NIO、AIO。
關(guān)于 Java IO 模型的詳細(xì)介紹,推薦閱讀 常見的 IO 模型有哪些?Java 中 BIO、NIO、AIO 的區(qū)別?這篇原創(chuàng)好文。
4、TCP 三握四揮
建立連接-TCP 三次握手:
建立一個(gè) TCP 連接需要“三次握手”,缺一不可:
-
一次握手:客戶端發(fā)送帶有 SYN(SEQ=x) 標(biāo)志的數(shù)據(jù)包 -> 服務(wù)端,然后客戶端進(jìn)入 SYN_SEND 狀態(tài),等待服務(wù)器的確認(rèn); -
二次握手:服務(wù)端發(fā)送帶有 SYN+ACK(SEQ=y,ACK=x+1) 標(biāo)志的數(shù)據(jù)包 –> 客戶端,然后服務(wù)端進(jìn)入 SYN_RECV 狀態(tài) -
三次握手:客戶端發(fā)送帶有 ACK(ACK=y+1) 標(biāo)志的數(shù)據(jù)包 –> 服務(wù)端,然后客戶端和服務(wù)器端都進(jìn)入ESTABLISHED 狀態(tài),完成 TCP 三次握手。
當(dāng)建立了 3 次握手之后,客戶端和服務(wù)端就可以傳輸數(shù)據(jù)啦!
斷開連接-TCP 四次揮手:
斷開一個(gè) TCP 連接則需要“四次揮手”,缺一不可:
-
第一次揮手:客戶端發(fā)送一個(gè) FIN(SEQ=x) 標(biāo)志的數(shù)據(jù)包->服務(wù)端,用來關(guān)閉客戶端到服務(wù)器的數(shù)據(jù)傳送。然后客戶端進(jìn)入 FIN-WAIT-1 狀態(tài)。 -
第二次揮手:服務(wù)器收到這個(gè) FIN(SEQ=X) 標(biāo)志的數(shù)據(jù)包,它發(fā)送一個(gè) ACK (ACK=x+1)標(biāo)志的數(shù)據(jù)包->客戶端 。然后服務(wù)端進(jìn)入 CLOSE-WAIT 狀態(tài),客戶端進(jìn)入 FIN-WAIT-2 狀態(tài)。 -
第三次揮手:服務(wù)端發(fā)送一個(gè) FIN (SEQ=y)標(biāo)志的數(shù)據(jù)包->客戶端,請求關(guān)閉連接,然后服務(wù)端進(jìn)入 LAST-ACK 狀態(tài)。 -
第四次揮手:客戶端發(fā)送 ACK (ACK=y+1)標(biāo)志的數(shù)據(jù)包->服務(wù)端,然后客戶端進(jìn)入TIME-WAIT狀態(tài),服務(wù)端在收到 ACK (ACK=y+1)標(biāo)志的數(shù)據(jù)包后進(jìn)入 CLOSE 狀態(tài)。此時(shí)如果客戶端等待 2MSL 后依然沒有收到回復(fù),就證明服務(wù)端已正常關(guān)閉,隨后客戶端也可以關(guān)閉連接了。
只要四次揮手沒有結(jié)束,客戶端和服務(wù)端就可以繼續(xù)傳輸數(shù)據(jù)!
這里順帶總結(jié)一下 TCP 三次握手和四次揮手(非常重要)相關(guān)的面試題:
-
為什么要三次握手? -
第 2 次握手傳回了 ACK,為什么還要傳回 SYN? -
為什么要四次揮手? -
為什么不能把服務(wù)器發(fā)送的 ACK 和 FIN 合并起來,變成三次揮手? -
如果第二次揮手時(shí)服務(wù)器的 ACK 沒有送達(dá)客戶端,會(huì)怎樣? -
為什么第四次揮手客戶端需要等待 2*MSL(報(bào)文段最長壽命)時(shí)間后才進(jìn)入 CLOSED 狀態(tài)?
參考答案:TCP 為什么要三次握手和四次揮手?(原創(chuàng)好文)。
5、CopyOnWrite 了解嗎
下面是維基百科對(duì) Copy-On-Write 的介紹,介紹的挺不錯(cuò):
寫入時(shí)復(fù)制(英語:Copy-on-write,簡稱 COW)是一種計(jì)算機(jī)程序設(shè)計(jì)領(lǐng)域的優(yōu)化策略。其核心思想是,如果有多個(gè)調(diào)用者(callers)同時(shí)請求相同資源(如內(nèi)存或磁盤上的數(shù)據(jù)存儲(chǔ)),他們會(huì)共同獲取相同的指針指向相同的資源,直到某個(gè)調(diào)用者試圖修改資源的內(nèi)容時(shí),系統(tǒng)才會(huì)真正復(fù)制一份專用副本(private copy)給該調(diào)用者,而其他調(diào)用者所見到的最初的資源仍然保持不變。這過程對(duì)其他的調(diào)用者都是透明的。此作法主要的優(yōu)點(diǎn)是如果調(diào)用者沒有修改該資源,就不會(huì)有副本(private copy)被創(chuàng)建,因此多個(gè)調(diào)用者只是讀取操作時(shí)可以共享同一份資源。
CopyOnWriteArrayList 線程安全的核心在于其采用了 寫時(shí)復(fù)制(Copy-On-Write) 的策略,這里再以 CopyOnWriteArrayList為例介紹:當(dāng)需要修改( add,set、remove 等操作) CopyOnWriteArrayList 的內(nèi)容時(shí),不會(huì)直接修改原數(shù)組,而是會(huì)先創(chuàng)建底層數(shù)組的副本,對(duì)副本數(shù)組進(jìn)行修改,修改完之后再將修改后的數(shù)組賦值回去,這樣就可以保證寫操作不會(huì)影響讀操作了(推薦大家順便去看一下 CopyOnWriteArrayList 源碼分析這篇原創(chuàng)好文)。
可以看出,寫時(shí)復(fù)制機(jī)制非常適合讀多寫少的并發(fā)場景,能夠極大地提高系統(tǒng)的并發(fā)性能。
不過,寫時(shí)復(fù)制機(jī)制并不是銀彈,其依然存在一些缺點(diǎn),下面列舉幾點(diǎn):
-
內(nèi)存占用:每次寫操作都需要復(fù)制一份原始數(shù)據(jù),會(huì)占用額外的內(nèi)存空間,在數(shù)據(jù)量比較大的情況下,可能會(huì)導(dǎo)致內(nèi)存資源不足。 -
寫操作開銷:每一次寫操作都需要復(fù)制一份原始數(shù)據(jù),然后再進(jìn)行修改和替換,所以寫操作的開銷相對(duì)較大,在寫入比較頻繁的場景下,性能可能會(huì)受到影響。 -
數(shù)據(jù)一致性問題:修改操作不會(huì)立即反映到最終結(jié)果中,還需要等待復(fù)制完成,這可能會(huì)導(dǎo)致一定的數(shù)據(jù)一致性問題。 -
……
最后,建議大家順便去看看下面這幾篇原創(chuàng)并發(fā)面試題總結(jié):
-
Java 并發(fā)常見面試題總結(jié)(上) -
Java 并發(fā)常見面試題總結(jié)(中) -
Java 并發(fā)常見面試題總結(jié)(下)
6、Redis 持久化了解嗎,講一下 AOF
Redis 持久化機(jī)制屬于后端面試超高頻的面試知識(shí)點(diǎn),老生常談了,需要重點(diǎn)花時(shí)間掌握。即使不是準(zhǔn)備面試,日常開發(fā)也是需要經(jīng)常用到的。
關(guān)于 Redis 持久化的詳細(xì)介紹,還請移步這篇原創(chuàng)好文:美團(tuán)面試:宕機(jī)了,Redis 如何避免數(shù)據(jù)丟失?,介紹的比較全面。
7、Redis 的緩存擊穿,穿透,雪崩及其解決方案
緩存穿透
什么是緩存穿透?
緩存穿透說簡單點(diǎn)就是大量請求的 key 是不合理的,根本不存在于緩存中,也不存在于數(shù)據(jù)庫中 。這就導(dǎo)致這些請求直接到了數(shù)據(jù)庫上,根本沒有經(jīng)過緩存這一層,對(duì)數(shù)據(jù)庫造成了巨大的壓力,可能直接就被這么多請求弄宕機(jī)了。
舉個(gè)例子:某個(gè)黑客故意制造一些非法的 key 發(fā)起大量請求,導(dǎo)致大量請求落到數(shù)據(jù)庫,結(jié)果數(shù)據(jù)庫上也沒有查到對(duì)應(yīng)的數(shù)據(jù)。也就是說這些請求最終都落到了數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力。
有哪些解決辦法?
最基本的就是首先做好參數(shù)校驗(yàn),一些不合法的參數(shù)請求直接拋出異常信息返回給客戶端。比如查詢的數(shù)據(jù)庫 id 不能小于 0、傳入的郵箱格式不對(duì)的時(shí)候直接返回錯(cuò)誤消息給客戶端等等。
1)緩存無效 key
如果緩存和數(shù)據(jù)庫都查不到某個(gè) key 的數(shù)據(jù)就寫一個(gè)到 Redis 中去并設(shè)置過期時(shí)間,具體命令如下:SET key value EX 10086 。這種方式可以解決請求的 key 變化不頻繁的情況,如果黑客惡意攻擊,每次構(gòu)建不同的請求 key,會(huì)導(dǎo)致 Redis 中緩存大量無效的 key 。很明顯,這種方案并不能從根本上解決此問題。如果非要用這種方式來解決穿透問題的話,盡量將無效的 key 的過期時(shí)間設(shè)置短一點(diǎn)比如 1 分鐘。
另外,這里多說一嘴,一般情況下我們是這樣設(shè)計(jì) key 的:表名:列名:主鍵名:主鍵值 。
如果用 Java 代碼展示的話,差不多是下面這樣的:
public Object getObjectInclNullById(Integer id) {
// 從緩存中獲取數(shù)據(jù)
Object cacheValue = cache.get(id);
// 緩存為空
if (cacheValue == null) {
// 從數(shù)據(jù)庫中獲取
Object storageValue = storage.get(key);
// 緩存空對(duì)象
cache.set(key, storageValue);
// 如果存儲(chǔ)數(shù)據(jù)為空,需要設(shè)置一個(gè)過期時(shí)間(300秒)
if (storageValue == null) {
// 必須設(shè)置過期時(shí)間,否則有被攻擊的風(fēng)險(xiǎn)
cache.expire(key, 60 * 5);
}
return storageValue;
}
return cacheValue;
}
2)布隆過濾器
布隆過濾器是一個(gè)非常神奇的數(shù)據(jù)結(jié)構(gòu),通過它我們可以非常方便地判斷一個(gè)給定數(shù)據(jù)是否存在于海量數(shù)據(jù)中。我們需要的就是判斷 key 是否合法,有沒有感覺布隆過濾器就是我們想要找的那個(gè)“人”。
具體是這樣做的:把所有可能存在的請求的值都存放在布隆過濾器中,當(dāng)用戶請求過來,先判斷用戶發(fā)來的請求的值是否存在于布隆過濾器中。不存在的話,直接返回請求參數(shù)錯(cuò)誤信息給客戶端,存在的話才會(huì)走下面的流程。
加入布隆過濾器之后的緩存處理流程圖如下。
但是,需要注意的是布隆過濾器可能會(huì)存在誤判的情況。總結(jié)來說就是:布隆過濾器說某個(gè)元素存在,小概率會(huì)誤判。布隆過濾器說某個(gè)元素不在,那么這個(gè)元素一定不在。
為什么會(huì)出現(xiàn)誤判的情況呢? 我們還要從布隆過濾器的原理來說!
我們先來看一下,當(dāng)一個(gè)元素加入布隆過濾器中的時(shí)候,會(huì)進(jìn)行哪些操作:
-
使用布隆過濾器中的哈希函數(shù)對(duì)元素值進(jìn)行計(jì)算,得到哈希值(有幾個(gè)哈希函數(shù)得到幾個(gè)哈希值)。 -
根據(jù)得到的哈希值,在位數(shù)組中把對(duì)應(yīng)下標(biāo)的值置為 1。
我們再來看一下,當(dāng)我們需要判斷一個(gè)元素是否存在于布隆過濾器的時(shí)候,會(huì)進(jìn)行哪些操作:
-
對(duì)給定元素再次進(jìn)行相同的哈希計(jì)算; -
得到值之后判斷位數(shù)組中的每個(gè)元素是否都為 1,如果值都為 1,那么說明這個(gè)值在布隆過濾器中,如果存在一個(gè)值不為 1,說明該元素不在布隆過濾器中。
然后,一定會(huì)出現(xiàn)這樣一種情況:不同的字符串可能哈希出來的位置相同。 (可以適當(dāng)增加位數(shù)組大小或者調(diào)整我們的哈希函數(shù)來降低概率)
更多關(guān)于布隆過濾器的內(nèi)容可以看我的這篇原創(chuàng):《不了解布隆過濾器?一文給你整的明明白白!》 ,強(qiáng)烈推薦,個(gè)人感覺網(wǎng)上應(yīng)該找不到總結(jié)的這么明明白白的文章了。
緩存擊穿
什么是緩存擊穿?
緩存擊穿中,請求的 key 對(duì)應(yīng)的是 熱點(diǎn)數(shù)據(jù) ,該數(shù)據(jù) 存在于數(shù)據(jù)庫中,但不存在于緩存中(通常是因?yàn)榫彺嬷械哪欠輸?shù)據(jù)已經(jīng)過期) 。這就可能會(huì)導(dǎo)致瞬時(shí)大量的請求直接打到了數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力,可能直接就被這么多請求弄宕機(jī)了。
舉個(gè)例子:秒殺進(jìn)行過程中,緩存中的某個(gè)秒殺商品的數(shù)據(jù)突然過期,這就導(dǎo)致瞬時(shí)大量對(duì)該商品的請求直接落到數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力。
有哪些解決辦法?
-
設(shè)置熱點(diǎn)數(shù)據(jù)永不過期或者過期時(shí)間比較長。 -
針對(duì)熱點(diǎn)數(shù)據(jù)提前預(yù)熱,將其存入緩存中并設(shè)置合理的過期時(shí)間比如秒殺場景下的數(shù)據(jù)在秒殺結(jié)束之前不過期。 -
請求數(shù)據(jù)庫寫數(shù)據(jù)到緩存之前,先獲取互斥鎖,保證只有一個(gè)請求會(huì)落到數(shù)據(jù)庫上,減少數(shù)據(jù)庫的壓力。
緩存穿透和緩存擊穿有什么區(qū)別?
緩存穿透中,請求的 key 既不存在于緩存中,也不存在于數(shù)據(jù)庫中。
緩存擊穿中,請求的 key 對(duì)應(yīng)的是 熱點(diǎn)數(shù)據(jù) ,該數(shù)據(jù) 存在于數(shù)據(jù)庫中,但不存在于緩存中(通常是因?yàn)榫彺嬷械哪欠輸?shù)據(jù)已經(jīng)過期) 。
緩存雪崩
什么是緩存雪崩?
我發(fā)現(xiàn)緩存雪崩這名字起的有點(diǎn)意思,哈哈。
實(shí)際上,緩存雪崩描述的就是這樣一個(gè)簡單的場景:緩存在同一時(shí)間大面積的失效,導(dǎo)致大量的請求都直接落到了數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力。 這就好比雪崩一樣,摧枯拉朽之勢,數(shù)據(jù)庫的壓力可想而知,可能直接就被這么多請求弄宕機(jī)了。
另外,緩存服務(wù)宕機(jī)也會(huì)導(dǎo)致緩存雪崩現(xiàn)象,導(dǎo)致所有的請求都落到了數(shù)據(jù)庫上。
舉個(gè)例子:數(shù)據(jù)庫中的大量數(shù)據(jù)在同一時(shí)間過期,這個(gè)時(shí)候突然有大量的請求需要訪問這些過期的數(shù)據(jù)。這就導(dǎo)致大量的請求直接落到數(shù)據(jù)庫上,對(duì)數(shù)據(jù)庫造成了巨大的壓力。
有哪些解決辦法?
針對(duì) Redis 服務(wù)不可用的情況:
-
采用 Redis 集群,避免單機(jī)出現(xiàn)問題整個(gè)緩存服務(wù)都沒辦法使用。 -
限流,避免同時(shí)處理大量的請求。
針對(duì)熱點(diǎn)緩存失效的情況:
-
設(shè)置不同的失效時(shí)間比如隨機(jī)設(shè)置緩存的失效時(shí)間。 -
緩存永不失效(不太推薦,實(shí)用性太差)。 -
設(shè)置二級(jí)緩存。
緩存雪崩和緩存擊穿有什么區(qū)別?
緩存雪崩和緩存擊穿比較像,但緩存雪崩導(dǎo)致的原因是緩存中的大量或者所有數(shù)據(jù)失效,緩存擊穿導(dǎo)致的原因主要是某個(gè)熱點(diǎn)數(shù)據(jù)不存在與緩存中(通常是因?yàn)榫彺嬷械哪欠輸?shù)據(jù)已經(jīng)過期)。
8、Spring 循環(huán)依賴了解嗎,怎么解決
循環(huán)依賴是指 Bean 對(duì)象循環(huán)引用,是兩個(gè)或多個(gè) Bean 之間相互持有對(duì)方的引用,例如 CircularDependencyA → CircularDependencyB → CircularDependencyA。
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyB circB;
}
@Component
public class CircularDependencyB {
@Autowired
private CircularDependencyA circA;
}
單個(gè)對(duì)象的自我依賴也會(huì)出現(xiàn)循環(huán)依賴,但這種概率極低,屬于是代碼編寫錯(cuò)誤。
@Component
public class CircularDependencyA {
@Autowired
private CircularDependencyA circA;
}
SpringBoot 2.6.x 以前是默認(rèn)允許循環(huán)依賴的,也就是說你的代碼出現(xiàn)了循環(huán)依賴問題,一般情況下也不會(huì)報(bào)錯(cuò)。解決辦法是三級(jí)緩存機(jī)制,提前暴露的對(duì)象存放在三級(jí)緩存中,二級(jí)緩存存放過渡 bean(半成品),一級(jí)緩存存放最終形態(tài)的 bean。但是這種機(jī)制也有一些缺點(diǎn),比如增加了內(nèi)存開銷(需要維護(hù)三級(jí)緩存,也就是三個(gè) Map),降低了性能(需要進(jìn)行多次檢查和轉(zhuǎn)換)。并且,還有少部分情況是不支持循環(huán)依賴的,比如非單例的 bean 和@Async注解的 bean 無法支持循環(huán)依賴(具體可以參考聊透 Spring 循環(huán)依賴這篇文章,關(guān)于為什么是三級(jí)緩存而不是二級(jí)緩存里面也有提到)。
SpringBoot 2.6.x 以后官方不再推薦編寫存在循環(huán)依賴的代碼,建議開發(fā)者自己寫代碼的時(shí)候去減少不必要的互相依賴。這其實(shí)也是我們最應(yīng)該去做的,循環(huán)依賴本身就是一種設(shè)計(jì)缺陷,我們不應(yīng)該過度依賴 Spring 而忽視了編碼的規(guī)范和質(zhì)量,說不定未來某個(gè) SpringBoot 版本就徹底禁止循環(huán)依賴的代碼了。
SpringBoot 2.6.x 以后,如果你不想重構(gòu)循環(huán)依賴的代碼的話,也可以采用下面這些方法:
-
在全局配置文件中設(shè)置允許循環(huán)依賴存在: spring.main.allow-circular-references=true。最簡單粗暴的方式,不太推薦。 -
在導(dǎo)致循環(huán)依賴的 Bean 上添加 @Lazy注解,這是一種比較推薦的方式。@Lazy用來標(biāo)識(shí)類是否需要延遲加載,可以作用在類上、方法上、構(gòu)造器上、方法參數(shù)上、成員變量中。 -
……
更多關(guān)于 Spring 循環(huán)依賴的內(nèi)容可以看看下面這幾篇文章:
-
Spring 循環(huán)依賴那些事兒(含 Spring 詳細(xì)流程圖) - 阿里開發(fā)者 -
淺談 Spring 如何解決 Bean 的循環(huán)依賴問題
9、MySQL 索引了解嗎?底層數(shù)據(jù)結(jié)構(gòu)是什么?
MySQL 索引這個(gè)知識(shí)點(diǎn)真的太重要了,面試高頻高點(diǎn),性價(jià)非常高的 SQL 優(yōu)化手段。
我專門寫了一篇文章來總結(jié) MySQL 索引常見的問題,傳送門:MySQL 索引詳解。更多 MySQL 相關(guān)的文章,可以前往 JavaGuide 官方網(wǎng)站進(jìn)行閱讀,地址:javaguide.cn。
10、MySQL 的日志講一下, binlog 作用是什么?
MySQL 中常見的日志類型主要有下面幾類(針對(duì)的是 InnoDB 存儲(chǔ)引擎):
-
錯(cuò)誤日志(error log) :對(duì) MySQL 的啟動(dòng)、運(yùn)行、關(guān)閉過程進(jìn)行了記錄。 -
二進(jìn)制日志(binary log,binlog) :主要記錄的是更改數(shù)據(jù)庫數(shù)據(jù)的 SQL 語句。 -
一般查詢?nèi)罩荆╣eneral query log) :已建立連接的客戶端發(fā)送給 MySQL 服務(wù)器的所有 SQL 記錄,因?yàn)?SQL 的量比較大,默認(rèn)是不開啟的,也不建議開啟。 -
慢查詢?nèi)罩荆╯low query log) :執(zhí)行時(shí)間超過 long_query_time秒鐘的查詢,解決 SQL 慢查詢問題的時(shí)候會(huì)用到。 -
事務(wù)日志(redo log 和 undo log) :redo log 是重做日志,undo log 是回滾日志。 -
中繼日志(relay log) :relay log 是復(fù)制過程中產(chǎn)生的日志,很多方面都跟 binary log 差不多。不過,relay log 針對(duì)的是主從復(fù)制中的從庫。 -
DDL 日志(metadata log) :DDL 語句執(zhí)行的元數(shù)據(jù)操作。
二進(jìn)制日志(binlog)和事務(wù)日志(redo log 和 undo log)比較重要,需要我們重點(diǎn)關(guān)注。
binlog(binary log 即二進(jìn)制日志文件) 主要記錄了對(duì) MySQL 數(shù)據(jù)庫執(zhí)行了更改的所有操作(數(shù)據(jù)庫執(zhí)行的所有 DDL 和 DML 語句),包括表結(jié)構(gòu)變更(CREATE、ALTER、DROP TABLE……)、表數(shù)據(jù)修改(INSERT、UPDATE、DELETE...),但不包括 SELECT、SHOW 這類不會(huì)對(duì)數(shù)據(jù)庫造成更改的操作。
不過,并不是不對(duì)數(shù)據(jù)庫造成修改就不會(huì)被記錄進(jìn) binlog。即使表結(jié)構(gòu)變更和表數(shù)據(jù)修改操作并未對(duì)數(shù)據(jù)庫造成更改,依然會(huì)被記錄進(jìn) binlog。
binlog 最主要的應(yīng)用場景是 主從復(fù)制 ,主備、主主、主從都離不開 binlog,需要依靠 binlog 來同步數(shù)據(jù),保證數(shù)據(jù)一致性。
主從復(fù)制的原理如下圖所示:
-
主庫將數(shù)據(jù)庫中數(shù)據(jù)的變化寫入到 binlog -
從庫連接主庫 -
從庫會(huì)創(chuàng)建一個(gè) I/O 線程向主庫請求更新的 binlog -
主庫會(huì)創(chuàng)建一個(gè) binlog dump 線程來發(fā)送 binlog ,從庫中的 I/O 線程負(fù)責(zé)接收 -
從庫的 I/O 線程將接收的 binlog 寫入到 relay log 中。 -
從庫的 SQL 線程讀取 relay log 同步數(shù)據(jù)本地(也就是再執(zhí)行一遍 SQL )。
關(guān)于 MySQL binlog 的詳細(xì)介紹,可以查看這篇文章:面試官:MySQL binlog 有什么作用?主從延遲的了解么?。
11、給定一個(gè)字符串,統(tǒng)計(jì)字符串中每個(gè)字符的出現(xiàn)次數(shù),按照字母表順序輸出,a2b2c4 這種
利用 TreeMap 可以比較簡單地解決這個(gè)問題:
public static String countCharacters(String str) {
//使用 TreeMap 實(shí)現(xiàn)有序輸出
Map<Character, Integer> countMap = new TreeMap<>();
// 統(tǒng)計(jì)每個(gè)字符串出現(xiàn)的次數(shù)
for (char c : str.toCharArray()) {
countMap.put(c, countMap.getOrDefault(c, 0) + 1);
}
StringBuilder sb = new StringBuilder();
for (Map.Entry<Character, Integer> entry : countMap.entrySet()) {
sb.append(entry.getKey()).append(entry.getValue());
}
return sb.toString();
}
測試代碼:
String str = "ababccdccddddf";
String result = countCharacters(str);
System.out.println(result);
輸出:
a2b2c4d5f1
·············· END ··············
??專屬面試小冊/一對(duì)一提問/簡歷修改/專屬求職指南/學(xué)習(xí)打卡,歡迎加入 JavaGuide 官方知識(shí)星球 。
??近期文章精選:
