文章目錄
- 寫在前面
- 筆試
- 一面
- 程序和線程的差別?
- 虛拟位址是什麼?
- 記憶體分段分頁講講?
- http 1.0,1.1,2.0 差別?
- post和get的差別
- TCP 連接配接是怎麼樣的?
- 為什麼是三次?斷開為什麼是四次?
- 三次握手
- 四次揮手
- 2MSL有什麼用?
- chan用過吧?對一個關閉的 chan 讀寫會怎麼樣?
- redis 緩存擊穿 ?雪崩?有了解過嗎?
- 跳表說一下?
- 算法:最長公共子串
- 二面
- 用兩個協程列印交替列印A1B2C3D4E5....
- 慢查詢如何排查?
- es 用過是吧?簡單說一下原理?
- mysql 索引結構?和其他資料結構對比?
- 講講 聚集索引 和 非聚集索引?
- 參考連結
寫在前面
知乎面試下來感覺還行吧,畢竟知乎挺小的,不過也是挺注重基礎的,面試官水準也很好。
筆試
略
一面
程序和線程的差別?
程序是作業系統資源配置設定的基本機關、而線程是任務排程和執行的基本機關。
每個程序都有獨立的代碼和資料空間,程式之間的切換會有較大的開銷;
線程可以看做
輕量級的程序
,同一類
線程共享代碼和資料空間
,每個線程都有自己
獨立的運作棧
和
程式計數器
。線程之間切換的開銷小。
虛拟位址是什麼?
虛拟位址空間劃分為多個
固定大小的虛拟頁(VP)
,實體位址空間 (DRAM記憶體) 劃分為多個固定大小的實體頁(PP),虛拟頁和實體頁的大小是一樣的,通常為4KB。
對于程序來說,使用的都是虛拟位址。
每個程序維護一個單獨的頁表。
頁表是一種數組結構,存放着各虛拟頁的狀态,是否映射,是否緩存。
記憶體分段分頁講講?
分段:将程式分為代碼段、資料段、堆棧段等;
分段位址通過段表,轉換成線性位址; 分段位址包括段号和段内位址; 段表,包括短号、段長、基址;
分頁:将段分成均勻的小塊,通過頁表映射實體記憶體;
分頁機制就是将虛拟位址空間分為大小相等的頁;實體位址空間也分為若幹個實體塊(頁框);頁和頁框大小相等。實作離散配置設定; 分頁機制的實作需要 MMU 硬體實作;負責分頁位址轉換; 頁大小(粒度)太大浪費;太小,影響配置設定效率。
http 1.0,1.1,2.0 差別?
HTTP1.0:中主要使用header裡的If-Modified-Since,Expires來做為緩存判斷的标準。
HTTP1.1:
- 在請求頭引入了range頭域,它允許隻請求資源的某個部分,友善了開發者自由的選擇以便于充分利用帶寬和連接配接。
- 新增了多個錯誤狀态響應碼,如:413
- 長連接配接
HTTP2.0:
- 新的二進制格式
- 多路複用:
發出的多個請求可以在同一個連接配接上并行處理,當請求數量較大時,不會因為某一個請求任務過重而導緻其他任務無法正常執行
- 頭部資料壓縮:encoder 以減少需要傳輸的 header 大小
post和get的差別
GET用于擷取資訊,是幂等的,且可緩存。
POST用于修改伺服器上的資料,非幂等,不可緩存。
TCP 連接配接是怎麼樣的?
為什麼是三次?斷開為什麼是四次?
三次握手
因為通信的前提是確定雙方都是
接收和發送資訊是正常
的。
三次握手是為了建立可靠的資料傳輸通道,
第一次握手就是
讓 接收方 知道 發送方 有發送資訊的能力
第二次握手就是
讓 發送方 知道 接收方 有發送和接受資訊的能力
第三次握手就是
讓 接收方 知道 發送方 有接受資訊的能力
四次揮手
四次揮手則是為了保證等資料完成的被接收完再關閉連接配接。
既然提到需要保證資料完整的傳輸完,那就需要保證雙方
都達到關閉連接配接的條件才能斷開。
第一次揮手:用戶端
發起關閉連接配接
的請求給服務端;
第二次揮手:服務端收到關閉請求的時候可能這個時候資料還沒發送完,是以服務端會先回複一個
确認封包
,表示
自己知道用戶端想要關閉連接配接了
,但是因為資料還沒傳輸完,是以還需要等待;
第三次揮手:當資料傳輸完了,服務端會主動發送一個
FIN 封包
,告訴用戶端,表示資料
已經發送完了
,服務端這邊
準備關閉連接配接了
。
第四次揮手:當用戶端收到服務端的 FIN 封包過後,會回複一個
ACK 封包
,告訴服務端自己知道了,再等待一會就關閉連接配接。
2MSL有什麼用?
2MSL 即兩倍的 MSL,TCP 的 TIME_WAIT 狀态也稱為 2MSL 等待狀态,當 TCP 的一端發起主動關閉,在發出最後一個 ACK 包後,即第3次握手完成後發送了第四次握手的 ACK 包後就進入了 TIME_WAIT 狀态,必須在此狀态上停留兩倍的 MSL 時間,等待 2MSL 時間主要目的是怕最後一個 ACK 包對方沒收到, 那麼對方在逾時後将重發第三次握手的FIN包,主動關閉端接到重發的 FIN包 後可以再發一個 ACK應答包。
chan用過吧?對一個關閉的 chan 讀寫會怎麼樣?
- 空chan
- 讀會讀取到該chan類型的
。零值
- 寫會直接寫到chan中。
- 關閉的chan
- 讀已經關閉的 chan 能一直讀到東西,但是讀到的内容根據通道内關閉前是否有元素而不同。
。如果有元素,就繼續讀剩下的元素,如果沒有就是這個chan類型的零值,比如整型是 int,字元串是""
- 寫已經關閉的 chan 會 panic。因為源碼上面就是這樣寫的,可以看
src/runtime/chan.go
redis 緩存擊穿 ?雪崩?有了解過嗎?
其實對于這些緩沖擊穿,緩沖穿透,緩存雪崩都是由于中譯之後比較容易混淆,如果我們還原回英文,那麼就會很容易了解了。
- 緩存穿透:
。表示程式要通路的緩存key不在 cache penetration
裡。緩存 key 的取值範圍
- 緩存擊穿:
。表示緩存資料失效了。和 hotspot invalid
的差別是緩存的 key 還是存在的,隻是這個 key 的資料失效了。cache penetration
- 緩存雪崩:
。表示大量緩存都失效了,像雪崩一樣。cache avalanche
跳表說一下?
Redis 的跳躍表由
redis.h/zskiplistNode
和
redis.h/zskiplist
兩個結構定義,其中
zskiplistNode
結構用于表示跳躍表節點,而
zskiplist
結構則用于儲存跳躍表節點的相關資訊,比如節點的數量,以及指向表頭節點和表尾節點的指針等等。
上圖中展示了一個跳躍表示例,最左邊的就是 zskiplist 結構。
- header:指向跳躍表的表頭節點。
- tail:指向跳躍表的表尾節點。
- level:記錄目前跳躍表内,層數
。最大的那個節點的層數
- 記錄跳躍表的長度,也就是,跳躍表目前包含節點的數量。
- 層(level):節點中用L1、L2、L3等字樣标記節點的各個層,L1表示第一層,L2代表第二層,以此類推。每層都帶有兩個屬性:
。前進指針用于方位位于表尾方向的其他節點。而跨度則記錄了前進指針所指向節點和目前節點的距離。前進指針和跨度
- 後退(backward)指針:
節點中用BW字樣标記的後退指針,他指向目前節點的前一個節點。後退指針在程式從表尾向表頭周遊時使用。
- 分值(score):各個節點中的 1.0、2.0、3.0是節點所儲存的分值。在跳躍表中,節點按各個所儲存的分值從小到大排序。
- 成員對象(obj):各個節點中的o1,o2 和 o3 是節點所儲存的成員對象。
算法:最長公共子串
二面
一上來就做題,難受,做完題就聊項目,聊完項目就聊聊八股。
用兩個協程列印交替列印A1B2C3D4E5…
用
兩個 chan +一個 wg
來控制。
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
wg.Add(1)
chNumber := make(chan struct{})
chLetter := make(chan struct{})
i:=0
index := 0
a := "abcdefghijklmnopqrskuvwxyz"
go func() {
for {
select {
case <-chNumber:
fmt.Print(i)
i++
fmt.Print(i)
i++
chLetter <- struct{}{}
break
default:
break
}
}
}()
go func(wg *sync.WaitGroup) {
for {
select {
case <-chLetter:
if index > len(a)-1 {
wg.Done()
return
}
fmt.Print(string(a[index]))
index++
fmt.Print(string(a[index]))
index++
chNumber<- struct{}{}
break
default:
break
}
}
}(&wg)
chNumber<- struct{}{}
wg.Wait()
}
慢查詢如何排查?
可以先檢查索引,再看看我們的 sql語句 是否足夠性能好。
然後可以用 pprof 進行檢測,排查慢的邏輯并優化。
es 用過是吧?簡單說一下原理?
ES是一個搜尋引擎,我所知道的是用到了反向索引來進行檢測的。
mysql 索引結構?和其他資料結構對比?
B 樹
B+樹
- B+ 樹内節點不存儲資料,所有 data 存儲在葉節點導緻查詢時間複雜度固定為
。而 B-樹 查詢時間複雜度不固定,與 key 在樹中的位置有關,最好為 log (n)
。O(1)
- B+ 樹葉節點兩兩相連可大大增加區間通路性,可使用在範圍查詢等,而 B- 樹 每個節點 key 和 data 在一起,則無法區間查找。同時我們通路某資料後,可以将
,這是利用了相近的資料預讀進記憶體
。空間局部性
- B+ 樹更适合
。由于内節點無 data 域,每個節點能索引的範圍更大更精确。外部存儲
講講 聚集索引 和 非聚集索引?
聚簇索引:将資料存儲與索引放到了一塊,找到索引也就找到了資料。