天天看點

【Golang開發面經】知乎(兩輪技術面)

文章目錄

  • ​​寫在前面​​
  • ​​筆試​​
  • ​​一面​​
  • ​​程序和線程的差別?​​
  • ​​虛拟位址是什麼?​​
  • ​​記憶體分段分頁講講?​​
  • ​​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:

  1. 在請求頭引入了range頭域,它允許隻請求資源的某個部分,友善了開發者自由的選擇以便于充分利用帶寬和連接配接。
  2. 新增了多個錯誤狀态響應碼,如:413
  3. 長連接配接

HTTP2.0:

  1. 新的二進制格式
  2. 多路複用:​

    ​發出的多個請求可以在同一個連接配接上并行處理,當請求數量較大時,不會因為某一個請求任務過重而導緻其他任務無法正常執行​

  3. 頭部資料壓縮: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
  1. 讀會讀取到該chan類型的​

    ​零值​

    ​。
  2. 寫會直接寫到chan中。
  • 關閉的chan
  1. 讀已經關閉的 chan 能一直讀到東西,但是讀到的内容根據通道内關閉前是否有元素而不同。​

    ​如果有元素,就繼續讀剩下的元素,如果沒有就是這個chan類型的零值,比如整型是 int,字元串是"" ​

    ​。
  2. 寫已經關閉的 chan 會 panic。因為源碼上面就是這樣寫的,可以看​

    ​src/runtime/chan.go​

redis 緩存擊穿 ?雪崩?有了解過嗎?

其實對于這些緩沖擊穿,緩沖穿透,緩存雪崩都是由于中譯之後比較容易混淆,如果我們還原回英文,那麼就會很容易了解了。

  • 緩存穿透:​

    ​cache penetration​

    ​​。表示程式要通路的緩存key不在 ​

    ​緩存 key 的取值範圍​

    ​ 裡。
  • 緩存擊穿:​

    ​hotspot invalid​

    ​​。表示緩存資料失效了。和 ​

    ​cache penetration​

    ​ 的差別是緩存的 key 還是存在的,隻是這個 key 的資料失效了。
  • 緩存雪崩:​

    ​cache avalanche​

    ​。表示大量緩存都失效了,像雪崩一樣。

跳表說一下?

Redis 的跳躍表由 ​

​redis.h/zskiplistNode​

​​ 和 ​

​redis.h/zskiplist​

​​ 兩個結構定義,其中 ​

​zskiplistNode​

​​ 結構用于表示跳躍表節點,而 ​

​zskiplist​

​ 結構則用于儲存跳躍表節點的相關資訊,比如節點的數量,以及指向表頭節點和表尾節點的指針等等。

【Golang開發面經】知乎(兩輪技術面)

上圖中展示了一個跳躍表示例,最左邊的就是 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 樹

【Golang開發面經】知乎(兩輪技術面)

B+樹

【Golang開發面經】知乎(兩輪技術面)
  1. B+ 樹内節點不存儲資料,所有 data 存儲在葉節點導緻查詢時間複雜度固定為​

    ​log (n)​

    ​​。而 B-樹 查詢時間複雜度不固定,與 key 在樹中的位置有關,最好為 ​

    ​O(1)​

    ​。
  2. B+ 樹葉節點兩兩相連可大大增加區間通路性,可使用在範圍查詢等,而 B- 樹 每個節點 key 和 data 在一起,則無法區間查找。同時我們通路某資料後,可以将​

    ​相近的資料預讀進記憶體​

    ​​,這是利用了​

    ​空間局部性​

    ​。
  3. B+ 樹更适合​

    ​外部存儲​

    ​。由于内節點無 data 域,每個節點能索引的範圍更大更精确。

講講 聚集索引 和 非聚集索引?

聚簇索引:将資料存儲與索引放到了一塊,找到索引也就找到了資料。