大家好,我是小林。
今天又來分享面經了,這次騰訊春招實習的面經,崗位是 java 后端開發。
讀者的背景是985碩,根據讀者的面試感受說是,騰訊面試比之前的要難,但是增加自信了,遇到不會的問題,開始會胡說八道了。
說一下BIO、NIO和AIO
讀者答:
-
BIO是阻塞IO。在上一個線程的任務執行完之前,該線程必須阻塞等待上一個線程執行完畢。
-
NIO是非阻塞IO。一旦是響應事件發生了,該線程就會將對應的響應事件交給對應的事件處理器進行處理。
-
AIO是異步IO。主線程接收到請求后,可以分發給其他線程進行異步處理,主線程繼續接收其他請求。
小林補充:
BIO(Blocking IO)、NIO(Non-Blocking IO)和AIO(Asynchronous IO)是Java中常用的IO模式。它們之間的主要區別在于IO的處理方式和效率。
BIO是同步阻塞IO,在進行IO操作時,必須等待IO操作完成后才能進行下一步操作,這時線程會被阻塞。BIO適用于連接數比較小且固定的架構,由于線程阻塞等待IO操作,所以并發處理能力不強。
NIO是同步非阻塞IO,可以支持多個連接同時進行讀寫操作,因此可以用較少的線程來處理大量的連接。NIO通過Selector來監聽多個Channel的狀態,當Channel中有數據可讀或可寫時,Selector會通知程序進行讀寫操作。NIO適用于連接數多且連接時間較短的場景。
AIO是異步非阻塞IO,與NIO不同的是,AIO不需要用戶線程等待IO操作完成,而是由操作系統來完成IO操作,操作系統完成IO操作后會通知用戶線程處理。AIO適用于連接數較多且連接時間較長的場景,如高性能網絡服務器等。
你說一下NIO是如何實現同步非阻塞的?主線程是只有一個嘛?
讀者答:
NIO底層是用Selector、Channel和ByteBuffer來實現的。主線程在循環使用select方法進行阻塞等待,當有acceptable、readable或者writable事件發生的時候,循環就會往下走,將對應的事件交給對應的事件處理器進行處理。
他可以多線程的,可以有多個accept()線程和多個worker線程。
小林補充:
在NIO中,使用了多路復用器Selector來實現同步非阻塞的IO操作。Selector是一個可以監控多個通道(Channel)是否有數據可讀或可寫的對象,當一個或多個Channel準備好讀或寫時,Selector會通知程序進行讀寫操作,而不是像BIO一樣阻塞等待IO操作完成。
在NIO中,主線程通常只有一個,但是可以使用Selector來管理多個Channel,實現多個連接的非阻塞讀寫操作。當有多個Channel需要進行IO操作時,Selector會輪詢這些Channel,檢查它們的狀態是否可讀或可寫,如果有可讀或可寫的Channel,就將其加入到一個已選擇鍵集合中,等待程序處理。這樣,一個線程就可以同時處理多個Channel,提高了系統的并發處理能力。
你用過哪些設計模式
單例模式,觀察者模式,責任鏈模式
講一下觀察者模式
讀者答:
觀察者模式就是他有多個觀察者,有一個觀察管理者,觀察者一開始會都注冊到觀察管理者的列表當中,當對應的位置發生了相應的事件呢,就會由觀察管理者調用相應的觀察者的方法執行相應的動作。
小林補充:
觀察者模式(Observer Pattern)是一種設計模式,它定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象,當主題對象狀態發生變化時,它的所有觀察者都會收到通知并自動更新。
在觀察者模式中,有兩個核心角色:Subject(主題)和Observer(觀察者)。主題是被觀察的對象,它維護了一個觀察者列表,可以動態添加或刪除觀察者。當主題狀態發生變化時,它會通知所有觀察者,并調用它們的更新方法。觀察者是接收主題通知的對象,它定義了一個更新方法,使主題在狀態發生變化時能夠及時通知到它。
觀察者模式可以實現松耦合的設計,主題對象和觀察者對象之間沒有直接的耦合關系,它們之間通過抽象的接口進行通信,可以方便地增加或刪除觀察者,而不需要修改主題對象的代碼。觀察者模式在很多場景中都有應用,比如GUI事件處理、消息隊列、發布訂閱系統等。
java 代碼示例:
當然,以下是一個簡單的Java代碼實例,演示了觀察者模式的基本實現:
importjava.util.ArrayList;
importjava.util.List;
//主題(Subject)接口
interfaceSubject{
voidregisterObserver(Observerobserver);
voidremoveObserver(Observerobserver);
voidnotifyObservers();
}
//觀察者(Observer)接口
interfaceObserver{
voidupdate(Stringmessage);
}
//具體主題(ConcreteSubject)實現
classConcreteSubjectimplementsSubject{
privateListobservers=newArrayList<>();
privateStringmessage;
@Override
publicvoidregisterObserver(Observerobserver){
observers.add(observer);
}
@Override
publicvoidremoveObserver(Observerobserver){
observers.remove(observer);
}
@Override
publicvoidnotifyObservers(){
for(Observerobserver:observers){
observer.update(message);
}
}
publicvoidsetMessage(Stringmessage){
this.message=message;
notifyObservers();
}
}
//具體觀察者(ConcreteObserver)實現
classConcreteObserverimplementsObserver{
privateStringname;
publicConcreteObserver(Stringname){
this.name=name;
}
@Override
publicvoidupdate(Stringmessage){
System.out.println(name+"receivedmessage:"+message);
}
}
//測試類
publicclassObserverPatternDemo{
publicstaticvoidmain(String[]args){
ConcreteSubjectsubject=newConcreteSubject();
Observerobserver1=newConcreteObserver("Observer1");
Observerobserver2=newConcreteObserver("Observer2");
Observerobserver3=newConcreteObserver("Observer3");
subject.registerObserver(observer1);
subject.registerObserver(observer2);
subject.registerObserver(observer3);
subject.setMessage("Hello,everyone!");
subject.removeObserver(observer2);
subject.setMessage("Howareyoudoing?");
}
}
運行上述代碼,輸出如下:
Observer1receivedmessage:Hello,everyone!
Observer2receivedmessage:Hello,everyone!
Observer3receivedmessage:Hello,everyone!
Observer1receivedmessage:Howareyoudoing?
Observer3receivedmessage:Howareyoudoing?
可以看到,當主題對象狀態發生變化時,它會通知所有觀察者,并調用它們的更新方法。觀察者可以根據接收到的消息進行相應的處理。
java內存結構
讀者答:
JVM內存結構分為5大區域,程序計數器、虛擬機棧、本地方法棧、堆內存、方法區。
方法區:
用來存儲加載的類信息、常量、靜態變量、編譯后的代碼等數據。
堆內存:
堆內存可以細分為:老年代、新生代(Eden、From Survivor、To Survivor)。JVM啟動時創建,用來存放對象的實例。堆內存是垃圾收集器管理的主要區域。
虛擬機棧:
線程私有的。虛擬機棧由多個棧幀組成。一個線程會執行一個或多個方法,一個方法對應一個棧幀。每一次方法調用都會有一個對應的棧幀被壓入棧中,每一個方法調用結束后,都會有一個棧幀被彈出。棧幀內容包含:局部變量表、操作數棧、動態鏈接、方法返回地址等信息。
本地方法棧:
和虛擬機棧功能類似,虛擬機棧是為虛擬機執行JAVA方法而準備的,本地方法棧是為虛擬機使用Native本地方法而準備的。
程序計數器(Program Counter Register):
線程私有的,程序計數器主要有兩個作用:
- 作為當前線程所執行的字節碼的行號指示器,通過它實現代碼的流程控制,如:順序執行、分支、循環、異常處理。
- 在多線程的情況下,程序計數器用于記錄當前線程執行的位置,當線程被切換回來的時候可以通過程序計數器中的信息獲取上次執行的位置,然后繼續執行。
說一下數據庫事務的四大特性
讀者答:
事務有ACID四大特性。就是原子性、一致性、隔離性和持久性。原子性就是指事務中的操作要么全做要么全不做,不存在中間態。一致性是指事務執行前后數據庫的完整性不被破壞,保持一致。隔離性是指多個事務并發執行時事務之間互不影響。持久性是指事務執行成功后,事務對于數據庫的操作會永久的保存在磁盤上,永不丟失。
從一定程度上來講,AID是手段,C是目的,就是通過原子性、隔離性和持久性來保證一致性。
char和varchar的區別
讀者答:
char是固定長度的字符串類型,varchar是可變長度的字符串類型。拿char(128)和varchar(128)舉例來說。char(128)是無論字符串大小,都會在磁盤上分配128個字符的內存空間。而varchar(128)會根據字符本身的長短來分配內存空間。
小林補充:
在MySQL中,CHAR
和VARCHAR
都是用于存儲字符類型數據的數據類型,它們的區別在于存儲方式和使用場景。
CHAR
類型用于存儲固定長度的字符串,其長度在定義表時就已經固定,且最大長度為255個字符。當存儲的字符串長度小于定義的長度時,MySQL會在其后面補充空格使其長度達到定義的長度。由于存儲的長度是固定的,因此CHAR
類型的讀取速度比VARCHAR
類型更快。
VARCHAR
類型則用于存儲可變長度的字符串,其長度可以在存儲數據時動態地改變,但最大長度也為255個字符。當存儲的字符串長度小于定義的長度時,MySQL不會在其后面補充空格。由于存儲的長度是可變的,因此VARCHAR
類型的存儲空間相對更小,但讀取速度比CHAR
類型稍微慢一些。
那與varchar相比,char字段是不是一無是處呢?
大部分情況,是的,最好使用varchar。不過考慮一個極端的場景:某個字段的最大長度是100字節,但是會頻繁修改。如果使用char(100)
,則插入記錄后就分配了100個字節,后續修改不會造成頁分裂、頁空隙等問題,而varchar(100)
由于沒有提前分配存儲空間,后續修改時可能出現頁分裂,進而導致性能下降。
說一下外鍵約束
讀者答:
舉例來說,某一個字段是表b的主鍵,但是它也是表a中的字段,表a中該字段的使用范圍取決于表b。外鍵約束主要是用來維護兩個表的一致性。
小林補充:
外鍵約束的作用是維護表與表之間的關系,確保數據的完整性和一致性。讓我們舉一個簡單的例子:
假設你有兩個表,一個是學生表,另一個是課程表,這兩個表之間有一個關系,即一個學生可以選修多門課程,而一門課程也可以被多個學生選修。在這種情況下,我們可以在學生表中定義一個指向課程表的外鍵,如下所示:
CREATETABLEstudents(
idINTPRIMARYKEY,
nameVARCHAR(50),
course_idINT,
FOREIGNKEY(course_id)REFERENCEScourses(id)
);
這里,students
表中的course_id
字段是一個外鍵,它指向courses
表中的id
字段。這個外鍵約束確保了每個學生所選的課程在courses
表中都存在,從而維護了數據的完整性和一致性。
如果沒有定義外鍵約束,那么就有可能出現學生選了不存在的課程或者刪除了一個課程而忘記從學生表中刪除選修該課程的學生的情況,這會破壞數據的完整性和一致性。因此,使用外鍵約束可以幫助我們避免這些問題。
說一下binlog
讀者答:
binlog是二進制日志文件。他主要用來做主從同步。他有statement格式和row格式。statement記錄了執行的SQL語句,Row 格式保存哪條記錄被修改。binlog事務提交的時候才寫入的。也可以用來做歸檔。
小林補充:
binlog日志是MySQL數據庫的一種日志記錄機制,用于記錄數據庫的修改操作(如插入、更新、刪除等),以便在需要時進行數據恢復、數據復制和數據同步等操作。
binlog日志的實現以下功能:
- 數據恢復:binlog日志可以用于回滾到之前的某個時間點,從而恢復數據。
- 數據復制:binlog日志可以用于在主從數據庫之間復制數據,從而實現數據的高可用和負載均衡等功能。
MySQL的binlog日志有三種格式,分別是Statement格式、Row格式和Mixed格式。它們之間的區別如下:
- STATEMENT:每一條修改數據的 SQL 都會被記錄到 binlog 中(相當于記錄了邏輯操作,所以針對這種格式, binlog 可以稱為邏輯日志),主從復制中 slave 端再根據 SQL 語句重現。但 STATEMENT 有動態函數的問題,比如你用了 uuid 或者 now 這些函數,你在主庫上執行的結果并不是你在從庫執行的結果,這種隨時在變的函數會導致復制的數據不一致;
- ROW:記錄行數據最終被修改成什么樣了(這種格式的日志,就不能稱為邏輯日志了),不會出現 STATEMENT 下動態函數的問題。但 ROW 的缺點是每行數據的變化結果都會被記錄,比如執行批量 update 語句,更新多少行數據就會產生多少條記錄,使 binlog 文件過大,而在 STATEMENT 格式下只會記錄一個 update 語句而已;
- MIXED:包含了 STATEMENT 和 ROW 模式,它會根據不同的情況自動使用 ROW 模式和 STATEMENT 模式;
說一下分庫分表
讀者答:
我可能知道的就是想我簡歷上調研過的這個mycat組件,他是根據業務字段的hash值來確定分片的,比如user_id不同的用戶信息就會存儲到不同分片當中,他是多個分片同時提供服務。
小林補充:
當數據量過大造成事務執行緩慢時,就要考慮分表,因為減少每次查詢數據總量是解決數據查詢緩慢的主要原因。你可能會問:“查詢可以通過主從分離或緩存來解決,為什么還要分表?”但這里的查詢是指事務中的查詢和更新操作。
為了應對高并發,一個數據庫實例撐不住,即單庫的性能無法滿足高并發的要求,就把并發請求分散到多個實例中去,這種就是分庫。
總的來說,分庫分表使用的場景不一樣: 分表是因為數據量比較大,導致事務執行緩慢;分庫是因為單庫的性能無法滿足要求。
遇到過數據庫死鎖嗎
讀者答:
事務A通過數據修改操作占用著資源A,事務B通過數據修改操作占用著資源B,而他們又同時請求對方的資源,互不退讓就造成了死鎖。如果沒有終止一個事務或者回滾過一段時間或超時。
小林補充:
假設有兩事務,一個事務要插入訂單 1007 ,另外一個事務要插入訂單 1008,因為需要對訂單做冪等性校驗,所以兩個事務先要查詢該訂單是否存在,不存在才插入記錄,過程如下:
![2b03beee-d83e-11ed-bfe3-dac502259ad0.png](https://file1.elecfans.com//web2/M00/99/B8/wKgZomTniUiAJmjPAAJl6gsbM-g952.png)
可以看到,兩個事務都陷入了等待狀態(前提沒有打開死鎖檢測),也就是發生了死鎖,因為都在相互等待對方釋放鎖。
死鎖的四個必要條件:互斥、占有且等待、不可強占用、循環等待。只要系統發生死鎖,這些條件必然成立,但是只要破壞任意一個條件就死鎖就不會成立。
TCP和UDP的區別
讀者答:
1.TCP是面向連接的協議,建立和釋放連接需要進行三次握手和四次揮手。UDP是面向無連接的協議,無需進行三次握手和四次揮手。說明udp比TCP實時性更強。
2.TCP 是流式傳輸,沒有邊界,但保證順序和可靠。UDP 是一個包一個包的發送,是有邊界的,但可能會丟包和亂序。
3.TCP連接的可靠性強,UDP的可靠性不強。
4.TCP只能一對一,UDP支持一對多和多對多。
5.TCP的頭部開銷比UDP大。TCP 首部長度較長,會有一定的開銷,首部在沒有使用「選項」字段時是 20 個字節,如果使用了「選項」字段則會變長的。UDP 首部只有 8 個字節,并且是固定不變的,開銷較小。
TCP是如何保證可靠的?
讀者答:
- tcp的序列號可以避免亂序的問題,保證收到的tcp報文都是有序的。
- 在 TCP 中,當發送端的數據到達接收主機時,接收端主機會返回一個確認應答消息,表示已收到消息。
- TCP 針對數據包丟失的情況,會用重傳機制解決。
- 用快重傳解決個別報文段的丟失問題。
- 使用滑動窗口實現流量控制。使用接收方確認報文中的窗口字段來控制發送方發送窗口大小,進而控制發送方的發送速率,使得接收方來得及接收。
- 使用基于窗口的擁塞控制,來盡量避免避免網絡擁塞。
流量控制是使用什么數據結構來實現的?
讀者答:
流量控制是使用滑動窗口來實現的。接收方確認報文中的窗口字段可以用來控制發送方窗口的大小。如果窗戶的值為0,則發送方停止發送數據,但是發送方會定期的向接收方發送窗口探測報文以得到窗口的大小。
小林補充:
TCP傳輸協議中,流量控制是使用滑動窗口(Sliding Window)來實現的。滑動窗口是一種基于數據流的、動態調整的、可變大小的窗口,它通過協商雙方的接收窗口和發送窗口大小,控制數據的傳輸速率。
在TCP協議中,每個數據包都有一個序號,接收方通過序號來確認是否收到了正確的數據包。發送方將數據分成若干個數據段,每個數據段的大小不超過發送窗口的大小,然后將這些數據段發送給接收方。接收方會確認已經收到的數據,同時告訴發送方自己的接收窗口大小。發送方根據接收方的窗口大小,動態調整自己的發送窗口大小,從而控制數據的傳輸速率。
滑動窗口的大小是可以動態調整的,它可以根據網絡狀況和雙方的能力來自適應地調整,從而實現流量控制的功能。如果接收方的接收窗口變小,發送方會相應地減小自己的發送窗口,以避免過多的數據堆積在網絡中導致擁塞。如果接收方的接收窗口變大,發送方會相應地增加自己的發送窗口,以提高數據傳輸速率。
分塊傳輸
讀者答:
分塊傳輸這一塊有個nagle算法。他的目的是盡量發送大數據塊,以減少發送報文的數量,提高傳輸效率。nagle算法規定在上一個未被確認的分組的確認到達之前,不能發送下一個分組。
分塊傳輸其實更像是http協議里的chunk傳輸。如果是特指tcp 的話,如果應用層的數據超過mss的大小,數據會在tcp層進行分塊。
小林補充:
分塊傳輸感覺是說http協議里的chunk傳輸。它允許將數據分成多個塊(Chunk)進行傳輸,每個塊都包含一段數據和該塊數據的長度。在傳輸數據時,先發送一個塊的長度,然后發送該塊的數據,接著發送下一個塊的長度和數據,以此類推,直到所有的數據都傳輸完畢。
![2b180570-d83e-11ed-bfe3-dac502259ad0.png](https://file1.elecfans.com//web2/M00/99/B8/wKgZomTniUmALRaCAAKW6Su7SJA422.png)
如果是特指tcp 的話,如果應用層的數據超過mss的大小,數據會在tcp層進行分塊。
剛才你說nagle算法是為了發送大數據塊,數據塊是越大越好嗎?(我沒太聽懂這個問題,他又換了個說法,其實我還是沒懂,但是他提到了MTU,我就借坡下驢了)
讀者答:
您剛才提到了MTU,有可能因為數據塊比較大,可能會出現拆包的問題,將一個大數據塊分為幾個MTU單元進行傳輸,因為TCP是按照序列號順序讀取的,所以可能會出現阻塞問題。
小林補充:
看業務場景,negle算法不適合像ssh這種傳輸小報文的場景,會增加延遲。
項目問題
縮減很多,只放出了共性的問題。
- 性能調優是怎么做的?
- 你覺得你的這個項目性能瓶頸在哪里?
- 項目你自己做的嗎?開源了嗎?
無算法題
面試總結
感覺:
- 騰訊面試比之前的要難,增加自信了,開始會胡說八道了。即使知道自己說的不是對的,例如分塊傳輸那個問題。
不足之處:
- 再積累積累吧
-
JAVA
+關注
關注
19文章
2975瀏覽量
105154 -
騰訊
+關注
關注
7文章
1666瀏覽量
49605
原文標題:騰訊面試比之前的要難,開始會胡說八道了
文章出處:【微信號:小林coding,微信公眾號:小林coding】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
![](https://file1.elecfans.com/web2/M00/82/EF/wKgZomRl1HCAVMuuAADbWH7Dju0133.png)
評論