美團面試,問的都是基礎啊!
大家好,我是小林。
今天分享一位讀者的春招面經,美團基礎架構的面經。
問的全是基礎,一個編程語言的問都沒有。
問題記錄
MySQL-MVCC
讀者答:InooDB是通過 MVCC 實現可重復讀的隔離級別的,MVCC 就是多版本并發控制,它其實記錄了歷史版本的數據,解決了讀寫并發沖突問題。有一個版本編碼,然后它進入了各種操作下的數據狀態,能夠根據當前這個指令的狀態來讀取不同時期的數據快照。主要實現方法的話就是通過事務版本號,讀取視圖還有undo日志進行完善的。
小林補充:具體的實現原理過程,可以去 xiaolincoding.com 網站->圖解MySQL->事務隔離級別是怎么實現的?這篇文章學習。
MySQL-原子性怎么實現的
說錯了,說成redoLog了。應該是undoLog。
讀者答:原子性的話會在寫數據之前有一個,就是WAL的過程,就是寫一個 redo log,然后如果數據沒有寫完或者是執行操作失敗的話,可以恢復所有已提交的事務或者回滾。
小林補充:
事務的原子性是通過 undo log 實現的。
undo log 是一種用于撤銷回退的日志。在事務沒提交之前,MySQL 會先記錄更新前的數據到 undo log 日志文件里面,當事務回滾時,可以利用 undo log 來進行回滾。如下圖:
回滾事務
每當 InnoDB 引擎對一條記錄進行操作(修改、刪除、新增)時,要把回滾時需要的信息都記錄到 undo log 里,比如:
- 在插入一條記錄時,要把這條記錄的主鍵值記下來,這樣之后回滾時只需要把這個主鍵值對應的記錄刪掉就好了;
- 在刪除一條記錄時,要把這條記錄中的內容都記下來,這樣之后回滾時再把由這些內容組成的記錄插入到表中就好了;
- 在更新一條記錄時,要把被更新的列的舊值記下來,這樣之后回滾時再把這些列更新為舊值就好了。
在發生回滾時,就讀取 undo log 里的數據,然后做原先相反操作。比如當 delete 一條記錄時,undo log 中會把記錄中的內容都記下來,然后執行回滾操作的時候,就讀取 undo log 里的數據,然后進行 insert 操作。
不同的操作,需要記錄的內容也是不同的,所以不同類型的操作(修改、刪除、新增)產生的 undo log 的格式也是不同的,具體的每一個操作的 undo log 的格式我就不詳細介紹了,感興趣的可以自己去查查。
MySQL-持久性是怎么實現的
讀者答:通過 redo log 保證持久化。buffer pool 中有 undo 頁,對 undo 頁的修改也都會記錄到 redo log。redo log 會每秒刷盤,提交事務時也會刷盤,數據頁和 undo 頁都是靠這個機制保證持久化的。
通過兩次寫來實現,當緩沖池的臟頁刷新時,并不直接寫磁盤,而是會通過memcpy函數將臟頁先拷貝到內存中的doublewrite buffer,之后通過doublewrite buffer再分兩次,每次寫入1MB到共享表空間的物理磁盤上,然后馬上調用fsync函數,同步磁盤,進行數據持久化。
小林補充:
事務的持久性是通過 redo log 實現的。
我們修改某條記錄,其實該記錄并不是馬上刷入磁盤的,而是將 Innodb 的 Buffer Pool 標記為臟頁,等待后續的異步刷盤。
Buffer Pool 是提高了讀寫效率沒錯,但是問題來了,Buffer Pool 是基于內存的,而內存總是不可靠,萬一斷電重啟,還沒來得及落盤的臟頁數據就會丟失。
為了防止斷電導致數據丟失的問題,當有一條記錄需要更新的時候,InnoDB 引擎就會先更新內存(同時標記為臟頁),然后將本次對這個頁的修改以 redo log 的形式記錄下來,這個時候更新就算完成了。
后續,InnoDB 引擎會在適當的時候,由后臺線程將緩存在 Buffer Pool 的臟頁刷新到磁盤里,這就是 WAL (Write-Ahead Logging)技術。
WAL 技術指的是, MySQL 的寫操作并不是立刻寫到磁盤上,而是先寫日志,然后在合適的時間再寫到磁盤上。
過程如下圖:
redo log 是物理日志,記錄了某個數據頁做了什么修改,比如對 XXX 表空間中的 YYY 數據頁 ZZZ 偏移量的地方做了AAA 更新,每當執行一個事務就會產生這樣的一條或者多條物理日志。
在事務提交時,只要先將 redo log 持久化到磁盤即可,可以不需要等到將緩存在 Buffer Pool 里的臟頁數據持久化到磁盤。
當系統崩潰時,雖然臟頁數據沒有持久化,但是 redo log 已經持久化,接著 MySQL 重啟后,可以根據 redo log 的內容,將所有數據恢復到最新的狀態。
操作系統-死鎖怎么產生的
讀者答:死鎖會產生的話一般會出現就是嗯資源就是互相占用,但是沒有辦法解鎖,形成循環這樣的情況,比如說 a 線程有一部分 b 線程需要的資源, b 線程有一部分 a 需要的資源,那他兩個人互相的互斥等待形成了死鎖,兩個線程都沒有辦法完成任務。
小林補充:
死鎖問題的產生是由兩個或者以上線程并行執行的時候,爭奪資源而互相等待造成的。
死鎖只有同時滿足互斥、持有并等待、不可剝奪、環路等待這四個條件的時候才會發生。
所以要避免死鎖問題,就是要破壞其中一個條件即可,最常用的方法就是使用資源有序分配法來破壞環路等待條件。
操作系統-怎么避免死鎖
讀者答:
- 避免死鎖的話可以手動的 kill 掉某一個進程來結束當前的死鎖狀態。
- 也可以說設置一些搶占的規則。如果這個進程占用的時間非常長的話,通過上下文切換給另外一個進程運行的機會,
- 也可以在分配資源的時候進行預先的設計,就是有一個銀行家算法來進行一個不會發生死鎖的進程運行的調度
操作系統-pageCache是什么
讀者答:緩存一些比較常訪問的文件到緩存中,這樣子的話它就能減少兩次從內核空間拷貝的過程,就是來減少查詢這個內容的時間。
小林補充:
為了提升對文件的讀寫效率,Linux 內核會以頁大小(4KB)為單位,將文件劃分為多數據塊。當用戶對文件中的某個數據塊進行讀寫操作時,內核首先會申請一個內存頁(稱為 頁緩存)與文件中的數據塊進行綁定。如下圖所示:
如上圖所示,當用戶對文件進行讀寫時,實際上是對文件的 頁緩存 進行讀寫。所以對文件進行讀寫操作時,會分以下兩種情況進行處理:
- 當從文件中讀取數據時,如果要讀取的數據所在的頁緩存已經存在,那么就直接把頁緩存的數據拷貝給用戶即可。否則,內核首先會申請一個空閑的內存頁(頁緩存),然后從文件中讀取數據到頁緩存,并且把頁緩存的數據拷貝給用戶。
- 當向文件中寫入數據時,如果要寫入的數據所在的頁緩存已經存在,那么直接把新數據寫入到頁緩存即可。否則,內核首先會申請一個空閑的內存頁(頁緩存),然后從文件中讀取數據到頁緩存,并且把新數據寫入到頁緩存中。對于被修改的頁緩存,內核會定時把這些頁緩存刷新到文件中。
計算機網絡-TCP的可靠性和順序性怎么實現的
讀者答:TCP 它實現可靠性和有序性的操作的話,是通過快重傳或者是回退 n 這樣子的設計來實現。如果報文在傳遞的過程中丟失之后能夠進行重傳。而會怎么能發現這個報文丟失呢?主要是根據一些序列號和 ACK的配合來幫助兩個服務之間知道當前傳遞的信息會丟失。
計算機網絡-怎么進行流量控制的?
回答成擁塞控制了;
讀者答:內部維護了一個能接收消息的一個窗口的大小,如果他出現就是消息丟失的情況,然后這個消息窗口的大小會減半。啟動的時候采用慢啟動的方式,從0開始指數級增加窗口大小,直到到達閥值之后線性增加窗口大小。
小林補充:
流量控制主要是可以讓「發送方」根據「接收方」的實際接收能力控制發送的數據量。
實現的方式,接收方會有一個接收緩沖區,如果內核接收到了數據,沒有被應用讀取的話,接收窗口就會收縮,然后會在tcp報文攜帶接收窗口的大小,發送發收到后,就會控制的發送流量。
下面舉個栗子,為了簡單起見,假設以下場景:
- 客戶端是接收方,服務端是發送方
- 假設接收窗口和發送窗口相同,都為 200
- 假設兩個設備在整個傳輸過程中都保持相同的窗口大小,不受外界影響
流量控制
根據上圖的流量控制,說明下每個過程:
- 客戶端向服務端發送請求數據報文。這里要說明下,本次例子是把服務端作為發送方,所以沒有畫出服務端的接收窗口。
- 服務端收到請求報文后,發送確認報文和 80 字節的數據,于是可用窗口 Usable 減少為 120 字節,同時 SND.NXT 指針也向右偏移 80 字節后,指向 321,這意味著下次發送數據的時候,序列號是 321。
- 客戶端收到 80 字節數據后,于是接收窗口往右移動 80 字節,RCV.NXT 也就指向 321,這意味著客戶端期望的下一個報文的序列號是 321,接著發送確認報文給服務端。
- 服務端再次發送了 120 字節數據,于是可用窗口耗盡為 0,服務端無法再繼續發送數據。
- 客戶端收到 120 字節的數據后,于是接收窗口往右移動 120 字節,RCV.NXT 也就指向 441,接著發送確認報文給服務端。
- 服務端收到對 80 字節數據的確認報文后,SND.UNA 指針往右偏移后指向 321,于是可用窗口 Usable 增大到 80。
- 服務端收到對 120 字節數據的確認報文后,SND.UNA 指針往右偏移后指向 441,于是可用窗口 Usable 增大到 200。
- 服務端可以繼續發送了,于是發送了 160 字節的數據后,SND.NXT 指向 601,于是可用窗口 Usable 減少到 40。
- 客戶端收到 160 字節后,接收窗口往右移動了 160 字節,RCV.NXT 也就是指向了 601,接著發送確認報文給服務端。
- 服務端收到對 160 字節數據的確認報文后,發送窗口往右移動了 160 字節,于是 SND.UNA 指針偏移了 160 后指向 601,可用窗口 Usable 也就增大至了 200。
Redis-怎么持久化的數據
讀者答:Redis 的話,它其實提供了兩種持久化數據的方法,一種是AOF,一種是RDB。然后 AOF 的話它是一種,就是說每一條操作信息它都會進行追加記錄這樣的一種持久化的方式。當那個數據庫重新啟動的時候,它就會根據 AOF 里面記錄的數據操作,然后來進行一個數據庫內容的重建。而 RDB 的話,它是做快照,也就是說在數據庫運行的過程中,它可能會另開一個 IO 的線程來進行數據庫的快照記錄,這樣子的話來記錄它某一個時間段的數據情況,這樣子它進行恢復,數據庫再次啟動的時候就可以直接根據 RDB 文件來進行恢復這兩個操作。
這樣一執行的話就可以看出來, AOF 的話,它雖然就是在執行的過程中性能的損耗是小的,但是如果數據庫要進行重新啟動的話,那它需要的耗時是比較長的。而 RDB 的話,它雖然重新啟動的耗時小,但是說它在過程中會有一定的性能損耗。而且如果是在兩個快照創建的中間就是數據庫宕機,或者是這樣子沒有做成快照的話,會造成一部分數據的缺失。
Redis-集群是怎么做的?就是數據怎么分片的,然后它的集群的高可用是什么?怎么部署的,這個有沒有了解過?
讀者答:我了解到它是有一個主從模型的,從它從模型的話就是復制一份主節點的備份,然后如果主節點宕機的情況下,從節點是可以成為主節點來提供服務的,別的就沒有什么了解的。
后續查資料補充add:在應對數據量擴容時,雖然增加內存這種縱向擴展的方法簡單直接,但是會造成數據庫的內存過大,導致性能變慢。Redis 切片集群提供了橫向擴展的模式,也就是使用多個實例,并給每個實例配置一定數量的哈希槽,數據可以通過鍵的哈希值映射到哈希槽,再通過哈希槽分散保存到不同的實例上。這樣做的好處是擴展性好,不管有多少數據,切片集群都能應對。
分布式-分布式事務是什么
讀者答:我只知道分布式事務中的 2 階段提交和 3 階段提交這樣一個概念
- 2 階段提交:準備階段和提交階段
- 3 階段提交:比2PC多了一個階段,即把原來2PC的準備階段拆分成CanCommit和PreCommit兩個階段,同時引入超時機制來解決2PC的同步阻塞問題。
后續查資料補充add:實際使用都是使用消息隊列+本地消息表保證最終一致性,2PC這種強一致性用在一些金融業務中,實現很麻煩。
分布式-paxos和raft的區別
讀者答:Paxos在一個節點當選為就是 leader 節點之后,其他的從節點如果不滿主節點的那個投票策略的話,是可以對主節點的投票就是進行否決的。Paxos就是三階段提交。但是 raft 的話就是只要集群中存在 leader 節點的話,從節點就是會按照主節點的策略來進行一致性的執行。
分布式-為什么就是分布式的共識算法都需要要求多數派提交才能完成它的分布式一致性?
讀者答:有作惡節點,消息可能到的順序不一樣,扯了拜占庭問題
場景題
- 設計一個線程池
- LRU 緩存設計
面試總結
感覺
面試官想多要些人填志愿,基礎知識沒有深挖,所有的知識點都考察了一下
不足之處
- 基礎知識還要再扎實
- 場景題需要結合實際開發,有些過于書本知識了,實際不用