什麼情況下你能接受 996

王中阳Go1發表於2024-08-01

在當下的職場環境中,996 工作制一直是一個備受爭議的話題

“996”是一種工作制度的代稱,指的是工作日早上 9 點上班,晚上 9 點下班,中午和傍晚休息 1 小時(或不到),總計工作 10 小時以上,並且一週工作 6 天的工作制度。

有人對其深惡痛絕,認為它嚴重影響了生活質量;而也有人在某些情況下,能夠接受這種工作模式。那麼,到底在什麼情況下,人們會願意接受 996 呢?

最近在一個平臺上,熱榜第一就是這個話題,引來了好多人的討論。在這話題裡,有人說專案到了關鍵時候,得加班加點趕進度,就願意接受 996,好保證專案能交付。就像後端開發的時候,碰到緊急上線或者重大系統最佳化,為了系統穩當、效能好,可能就得花更多時間和精力。

還有人覺得要是公司給的回報和發展機會夠多,996 也能接受。比如說在一些技術領域,參加有挑戰性的專案,能得到寶貴經驗,技能也能提升,對職業發展有好處。
那你呢?你啥時候能接受 996 ?是為了職業目標,還是因為別的?

好了,這個話題就討論到這裡,今天接著更新粉絲投稿的最新面經,應粉絲要求,我把問題和答案一個一個整理好了

內容整理如下(刪除了跟專案相關的內容):

遇到過kafka訊息積壓或者訊息丟失的問題嗎, 說一下怎麼處理?

訊息積壓

  1. 線上有時因為傳送方傳送訊息速度過快,或者消費方處理訊息過慢,可能會導致broker積壓大量未消費訊息。
  • 解決方案:此種情況如果積壓了上百萬未消費訊息需要緊急處理,可以修改消費端程式,讓其將收到的訊息快速轉發到其他topic(可以設定很多分割槽),然後再啟動多個消費者同時消費新主題的不同分割槽。如圖所示:

  1. 由於訊息資料格式變動或消費者程式有bug,導致消費者一直消費不成功,也可能導致broker積壓大量未消費訊息。
  • 解決方案:此種情況可以將這些消費不成功的訊息轉發到其它佇列裡去(類似死信佇列),後面再慢慢分析死信佇列裡的訊息處理問題。這個死信佇列,kafka並沒有提供,需要整合第三方外掛!

訊息丟失

kafka在生產端傳送訊息 和 消費端消費訊息 時都可能會丟失一些訊息

①:生產者訊息丟失

  • 生產者在傳送訊息時,會有一個ack機制,當ack=0 或者 ack=1時,都可能會丟訊息。如下所示:
  1. acks=0

表示producer不需要等待任何broker確認收到訊息的回覆,就可以繼續傳送下一條訊息。效能最高,但是最容易丟訊息。大資料統計報表場景,對效能要求很高,對資料丟失不敏感的情況可以用這種。

  1. acks=1

至少要等待leader已經成功將資料寫入本地log,但是不需要等待所有follower是否成功寫入。就可以繼續傳送下一條訊息。這種情況下,如果follower沒有成功備份資料,而此時leader又掛掉,則訊息會丟失。

  1. acks=-1或all

這意味著leader需要等待所有備份(min.insync.replicas配置的備份個數)都成功寫入日誌,這種策略會保證只要有一個備份存活就不會丟失資料。這是最強的資料保證。一般除非是金融級別,或跟錢打交道的場景才會使用這種配置。當然如果min.insync.replicas配置的是1則也可能丟訊息,跟acks=1情況類似。

②:消費端訊息丟失

  • 消費端丟訊息最主要體現在消費端offset的自動提交,如果開啟了自動提交,萬一消費到資料還沒處理完,此時你consumer直接當機了,未處理完的資料 丟失了,下次也消費不到了,因為offset已經提交完畢,下次會從offset出開始消費新訊息。

解決辦法就是採用消費端的手動提交。

切片, map, channel的底層結構?

切片:

type slice struct { 
    array unsafe.Pointer // 指向底層陣列 

    len int // 長度 

    cap int // 容量 
}

map:

type hmap struct {
   // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.
   // Make sure this stays in sync with the compiler's definition.
   count     int // # live cells == size of map.  Must be first (used by len() builtin)
   flags     uint8
   B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
   noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
   hash0     uint32 // hash seed

   buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
   oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
   nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

   extra *mapextra // optional fields
}

channel:

type hchan struct {
   qcount   uint           // total data in the queue
   dataqsiz uint           // size of the circular queue
   buf      unsafe.Pointer // points to an array of dataqsiz elements
   elemsize uint16
   closed   uint32
   elemtype *_type // element type
   sendx    uint   // send index
   recvx    uint   // receive index
   recvq    waitq  // list of recv waiters
   sendq    waitq  // list of send waiters

   // lock protects all fields in hchan, as well as several
   // fields in sudogs blocked on this channel.
   //
   // Do not change another G's status while holding this lock
   // (in particular, do not ready a G), as this can deadlock
   // with stack shrinking.
   lock mutex
}

切片它能作為 map 結構的 key 嗎?

答案顯然是不能的,因為 slice 是不能使用 “==” 進行比較的,所以是不能做為 map 的 key 的。

而且官方文件中也說明了 https://go.dev/blog/maps

As mentioned earlier, map keys may be of any type that is comparable. The language spec defines this precisely, but in short, comparable types are boolean, numeric, string, pointer, channel, and interface types, and structs or arrays that contain only those types. Notably absent from the list are slices, maps, and functions; these types cannot be compared using ==, and may not be used as map keys.

一段程式碼片段裡面有多個 defer的時候,它的執行順序是什麼樣?

在 Go 語言中,如果一個函式中有多個defer語句,它們會按照“後進先出”(Last In First Out,LIFO)的順序執行。也就是說,最後註冊的defer函式會第一個執行,而第一個註冊的defer函式會最後執行。

defer,它在什麼樣的情況下能去修改返回值?在什麼樣的情況下

defer在return之後執行,但在函式退出之前,defer可以修改返回值。下面是一個例子:

func test() int {
        i := 0
        defer func() {
                fmt.Println("defer1")
        }()
        defer func() {
                i += 1
                fmt.Println("defer2")
        }()
        return i
}

func main() {
        fmt.Println("return", test())
}
// defer2
// defer1
// return 0

上面這個例子中,test返回值並沒有修改,這是由於Go的返回機制決定的,執行Return語句後,Go會建立一個臨時變數儲存返回值。如果是有名返回(也就是指明返回值func test() (i int)

func test() (i int) {
        i = 0
        defer func() {
                i += 1
                fmt.Println("defer2")
        }()
        return i
}

func main() {
        fmt.Println("return", test())
}
// defer2
// return 1

這個例子中,返回值被修改了。對於有名返回值的函式,執行 return 語句時,並不會再建立臨時變數儲存,因此,defer 語句修改了 i,即對返回值產生了影響。

map 它是執行緒安全的嗎?

在 Go 語言中,內建的 map 不是執行緒安全的。

如果在多個執行緒中同時對一個普通的 map 進行讀寫操作,可能會導致不可預測的結果,比如資料不一致、程式崩潰等。

例如,如果一個執行緒正在讀取 map 中的資料,而另一個執行緒正在刪除或新增元素,就可能會出現問題。

如果需要在多執行緒環境中安全地使用 map ,可以使用一些同步機制,比如使用 sync.RWMutex 來實現加鎖保護,或者使用第三方的執行緒安全的 map 實現。

Redis 實現一個實時更新的排行榜

在 Redis 中,可以使用 Sorted Set(有序集合)來實現實時更新的排行榜。

Sorted Set 中的每個元素都關聯一個分數(score),元素會按照分數進行排序。

以下是一個基本的實現思路:

  1. 插入資料
    • 當有新的資料需要加入排行榜時,將資料作為元素,相關的數值(比如得分)作為分數,插入到 Sorted Set 中。
  2. 更新資料
    • 如果需要更新某個元素的分數(比如使用者的得分發生了變化),可以使用 ZINCRBY 命令來增加或減少元素的分數,從而實現實時更新。
  3. 獲取排行榜
    • 使用 ZRANGE 命令可以獲取排行榜的前若干名。
    • 例如, ZRANGE sorted_set 0 9 可以獲取排行前 10 名的資料。
  4. 倒序排行榜
    • 如果需要獲取倒序的排行榜,使用 ZREVRANGE 命令。

跳錶的節點高度是由什麼決定的?

在 Redis 中,跳躍表節點的高度是根據冪次定律隨機生成的,其值介於 1 和 32 之間。

每次建立一個新跳躍表節點時,Redis 會使用隨機演算法生成一個介於 1 和 32 之間的值作為level陣列的大小,這個大小就是該節點的“高度”,即層數。

這種隨機確定節點高度的方式有一定的機率性。透過使用冪次定律,使得較高的層數出現的機率相對較低,從而在保證查詢效率的同時,避免了過多的額外記憶體開銷。這樣可以在平均情況下實現對數時間複雜度的查詢、插入和刪除操作,同時又不會因固定的多層結構而導致過高的空間浪費。

這種設計的優點在於,它不需要嚴格維護相鄰兩層連結串列之間的節點個數關係,新插入的節點能根據自身隨機生成的層數決定在哪些層的連結串列上出現。既能提高查詢效率,又能較好地應對動態的插入和刪除操作,避免了複雜的調整操作,使時間複雜度不會退化到 O(n)。

分庫分表

分庫分表是一種資料庫架構最佳化技術,主要是為了解決資料庫在資料量過大、併發訪問過高時出現的效能瓶頸和管理難題。

分庫指的是將一個大型資料庫按照業務或其他規則拆分成多個獨立的資料庫,每個庫可以部署在不同的伺服器上,以減輕單個資料庫的負載和提高資料處理能力。

分表則是將一張大資料表按照特定規則拆分成多個小表,比如按照時間、地域、業務型別等進行劃分。分表可以是水平分表,即將表中的資料按照行進行分割,分佈到多個表中;也可以是垂直分表,即將表中的列按照業務相關性分割到不同的表中。

實現分庫分表的方法主要有以下幾種:

  1. 基於雜湊的分庫分表:透過對資料的某個欄位進行雜湊計算,根據雜湊值將資料分配到不同的庫表中。
  2. 基於範圍的分庫分表:按照資料欄位的取值範圍,如按照時間區間、數值大小範圍等進行分庫分表。
  3. 基於列舉值的分庫分表:根據資料欄位的列舉值,如地區、業務型別等進行分庫分表。

分庫分表的好處包括:

  1. 提高系統的效能和響應速度,減少資料查詢和操作的時間。
  2. 增強系統的可擴充套件性,便於應對不斷增長的資料量和業務需求。
  3. 方便資料的管理和維護,降低單個庫或表的複雜度。

歡迎關注 ❤

我們搞了一個免費的面試真題共享群,互通有無,一起刷題進步。

沒準能讓你能刷到自己意向公司的最新面試題呢。

感興趣的朋友們可以加我微信:wangzhongyang1993,備註:csdn面試群。

相關文章