新的一週又來了,今天分享的是訓練營的朋友在阿里的一面,看了一下面試的內容,感覺挺簡單的,你做一下試試:
Redis 資料消失的原因
Redis 中的資料如果既沒有設定過期時間也沒有被顯式刪除但仍然消失了,可能是因為配置了持久化策略(如RDB快照或AOF日誌),在重啟後資料未正確載入;
或者執行了清空命令如FLUSHDB
或FLUSHALL
。此外,當Redis達到其配置的最大記憶體限制時,會依據設定的驅逐策略(eviction policy)自動移除部分鍵值對以釋放記憶體空間。
還有可能是由於主從複製過程中發生故障切換,導致某些尚未同步的資料丟失。
Redis 的資料結構
字串(String)、雜湊表(Hash)、列表(List)、集合(Set)以及有序集合(Sorted Set)。
Redis Geo Hash 的使用和實現
是GeoHash演算法,
它可以將經緯度座標編碼成一個字串,從而簡化了位置資料的管理和檢索。
在內部,Redis 使用有序集合(zset)來儲存每個地點的位置資訊,其中元素的分數代表了該地點的GeoHash編碼值。這樣就可以高效地進行範圍查詢和最近鄰搜尋。
ZSET 的儲存方式
ZSET 允許為每個成員關聯一個浮點數分值,並按此分值升序排列。為了保證高效的插入、刪除和查詢操作。
Redis 在內部採用了跳躍表(skiplist)與雜湊表相結合的方式來組織資料。跳躍表確保了快速的順序訪問,而雜湊表提供了O(1)複雜度的隨機訪問能力。
RocketMQ 如何保障高效能
RocketMQ 設計了一系列機制以確保高吞吐量和低延遲的訊息傳遞服務。
其中包括非同步刷盤減少I/O阻塞,批次傳送訊息降低網路開銷,零複製技術提升檔案讀取效率,以及精心最佳化的檔案索引管理和多執行緒處理模型。
這些特性共同作用,使RocketMQ能夠在大規模分散式環境中保持卓越效能的同時維持較高的可靠性和可用性。
批次提交的好處
批次提交指的是將多個獨立的操作合併成一次請求傳送給伺服器。
- 這種方式可以顯著減少客戶端與服務端之間的互動次數,進而降低了通訊成本和整體延遲。
- 由於減少了每次請求的上下文切換和資源分配過程,系統能夠更加高效地利用計算資源。
- 批次處理還促進了資源共享,因為多個任務可以在單次呼叫中得到統一處理,減少了併發控制帶來的額外負擔。
固態硬碟和機械硬碟的區別
固態硬碟(SSD)和機械硬碟(HDD)之間最明顯的差異在於它們的工作原理。
SSD 採用快閃記憶體顆粒作為儲存介質,無任何移動部件,因此具有更快的速度、更低的功耗、更好的抗震性和更長的使用壽命。
HDD 利用旋轉磁碟片和機械臂來進行資料讀寫,雖然單位容量成本較低且適合長時間儲存大量資料,但在效能上通常遜色於SSD,尤其是在隨機讀寫方面。
隨機讀和順序讀
隨機讀是指按照不連續的地址序列訪問資料塊,這種模式下每次讀取都需要重新定位磁頭(針對HDD),所以會導致較高的延遲。
順序讀是指沿著預先確定的路徑連續讀取一系列資料塊,這種方式對於HDD來說更為有利,因為它不需要頻繁調整磁頭位置,而對於SSD而言,兩種讀取模式之間的效能差距相對較小,因為它們不受物理位置的影響。
MQ 訊息如何保障不丟失
持久化儲存、確認機制以及高可用部署方案。
- 持久化意味著訊息會被寫入磁碟,即使發生故障也能恢復;
- 確認機制要求消費者成功處理完訊息後才向MQ傳送確認訊號;
- 高可用部署則是指透過叢集化配置來增強系統的冗餘度,避免單點故障造成的服務中斷。
註冊中心掛了怎麼辦
- 應用程式應當具備一定的容錯能力,比如容忍短暫的服務發現失敗,並依賴本地快取繼續執行直到連線恢復正常。
- 應該建立健康檢查和重試邏輯,以便及時感知到註冊中心的狀態變化並在其恢復後迅速重新連線。
- 採用分散式部署策略,確保註冊中心自身也是高可用的,比如透過負載均衡器或多活資料中心等方式提高系統的穩定性和可靠性。
程式設計題:LRU演算法
- 結合雙向連結串列(由Go標準庫中的
container/list
包提供)和雜湊表,以確保O(1)的時間複雜度用於獲取和插入操作。 - LRU快取遵循“最近最少使用”的原則,即當快取滿時,移除最近最少使用的項。
package main
import (
"container/list"
"fmt"
)
// LRUCache defines a cache with Least Recently Used eviction policy.
type LRUCache struct {
capacity int // Cache capacity
cache map[int]*list.Element // Map to store key and its corresponding list element
lru *list.List // Doubly linked list to maintain order of usage
}
// Entry represents an entry in the cache.
type Entry struct {
key int // Key for the cache entry
value int // Value associated with the key
}
// Constructor initializes a new LRUCache instance.
func Constructor(capacity int) LRUCache {
return LRUCache{
capacity: capacity,
cache: make(map[int]*list.Element),
lru: list.New(),
}
}
// Get retrieves the value of the key if the key exists in the cache, otherwise returns -1.
func (cache *LRUCache) Get(key int) int {
if elem, ok := cache.cache[key]; ok {
cache.lru.MoveToFront(elem) // Move the accessed element to the front as it's most recently used
return elem.Value.(Entry).value
}
return -1 // Return -1 if the key is not found
}
// Put inserts or updates the value of the key. If the number of keys exceeds the capacity, it evicts the least recently used item.
func (cache *LRUCache) Put(key int, value int) {
if elem, ok := cache.cache[key]; ok {
cache.lru.MoveToFront(elem) // Update existing entry and move it to the front
elem.Value = Entry{key, value}
} else {
if cache.lru.Len() >= cache.capacity {
// Remove the oldest item from the cache when at capacity
old := cache.lru.Back()
if old != nil {
cache.lru.Remove(old)
delete(cache.cache, old.Value.(Entry).key)
}
}
// Add new entry to the front of the list
elem := cache.lru.PushFront(Entry{key, value})
cache.cache[key] = elem
}
}
func main() {
cache := Constructor(2 /* capacity */)
cache.Put(1, 1)
cache.Put(2, 2)
fmt.Println(cache.Get(1)) // returns 1
cache.Put(3, 3) // evicts key 2
fmt.Println(cache.Get(2)) // returns -1 (not found)
cache.Put(4, 4) // evicts key 1
fmt.Println(cache.Get(1)) // returns -1 (not found)
fmt.Println(cache.Get(3)) // returns 3
fmt.Println(cache.Get(4)) // returns 4
}
歡迎關注 ❤
我們搞了一個免費的面試真題共享群,互通有無,一起刷題進步。
沒準能讓你能刷到自己意向公司的最新面試題呢。
感興趣的朋友們可以加我微信:wangzhongyang1993,備註:sf面試群。