📍1. 數據庫的事務性質,InnoDB是如何實現的?
數據庫事務具有ACID特性,即原子性、一致性、隔離性和持久性。InnoDB通過以下機制實現這些特性:
🚀 實現細節:
- 原子性:通過undo log實現事務回滾。
- 一致性:通過事務的ACID屬性和數據庫約束保證。
- 隔離性:使用鎖和MVCC(多版本并發控制)實現不同隔離級別。
- 持久性:利用redo log確保數據在系統崩潰后能夠恢復。
🔧 MySQL事務示例:
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
📍2. MySQL中數據的存儲結構?
MySQL中的數據存儲結構涉及表空間、段、區、頁和行。InnoDB使用B+樹結構存儲數據和索引,聚簇索引將數據和主鍵索引存儲在一起。
🚀 存儲層次:
- 表空間:邏輯存儲單元。
- 段:表空間內的邏輯部分,如數據段、索引段。
- 區:由連續的頁組成。
- 頁:最小存儲單位,通常16KB。
- 行:實際數據記錄。
🔧 InnoDB數據存儲示例:
SHOW TABLE STATUS LIKE 'table_name';
📍3. MySQL的主從復制原理以及主從延遲的解決方案?
MySQL主從復制通過binlog和中繼日志實現數據同步。主從延遲可通過以下方法解決:
🚀 復制原理:
- 主庫記錄binlog。
- 從庫I/O線程獲取binlog并寫入中繼日志。
- SQL線程執行中繼日志中的SQL。
🚀 延遲解決方案:
- 優化網絡和硬件。
- 并行復制。
- 減少主庫負載。
- 使用半同步復制。
🔧 主從配置示例:
[mysqld]
log-bin=mysql-bin
server-id=1
📍4. Kafka怎么保證消息不丟、重復發了怎么辦?
Kafka通過生產者、broker和消費者的協調保證消息不丟失。重復消息通過冪等性和去重機制處理。
🚀 消息不丟:
- 生產者:設置acks=all。
- Broker:使用持久化和min.insync.replicas。
- 消費者:手動提交偏移量。
🚀 重復消息處理:
- 冪等性生產者。
- 消息去重。
🔧 Kafka生產者配置示例:
acks=all
retries=3
📍5. 你的項目中,接口調用如何保證冪等?
接口冪等性通過唯一標識符、樂觀鎖、分布式鎖、狀態機和Token機制實現,確保重復請求產生相同結果。
🚀 冪等實現:
- 唯一標識符:使用UUID和數據庫唯一索引。
- 樂觀鎖:版本號控制。
- 分布式鎖:Redisson或ZooKeeper。
🔧 冪等性示例代碼:
// 使用UUID生成唯一標識符
UUID uuid = UUID.randomUUID();
📍6. 你的項目中,如何保證分布式事務的一致性?
分布式事務一致性可通過兩階段提交、補償事務、基于消息的最終一致性、最大努力通知和Saga模式實現。
🚀 一致性策略:
- 兩階段提交(2PC):XA協議。
- 補償事務(TCC):Try-Confirm-Cancel。
- 基于消息的最終一致性:消息隊列。
🔧 Seata分布式事務示例:
<dependency><groupId>io.seata</groupId><artifactId>seata-all</artifactId><version>1.4.2</version>
</dependency>
📍7. 項目中的限流怎么做的,為什么這么做?
限流通過固定窗口、滑動窗口、令牌桶和漏桶算法實現,保護系統穩定性、防止資源耗盡和惡意攻擊。
🚀 限流算法:
- 固定窗口:Guava RateLimiter。
- 滑動窗口:Redis滑動窗口。
- 令牌桶:令牌生成和消耗。
🚀 限流原因:
- 保護系統穩定性。
- 防止資源耗盡。
🔧 Guava RateLimiter示例:
RateLimiter rateLimiter = RateLimiter.create(10.0); // 每秒10個請求
rateLimiter.acquire(); // 獲取令牌
📍8. 如何設計群消息已讀?
群消息已讀通過存儲已讀狀態、消息發送接收同步、已讀列表展示和批量更新實現,優化使用分頁加載和緩存。
🚀 設計思路:
- 已讀狀態存儲:數據庫或緩存。
- 消息發送和接收:更新已讀狀態。
- 已讀列表展示:展示已讀成員。
🔧 已讀狀態更新示例:
// 更新消息已讀狀態
updateMessageReadStatus(userId, messageId, true);
📍9. 多線程題目:10個線程模擬賽馬,所有馬就緒后才能開跑,所有馬到達終點后裁判宣布賽馬成績。
問題描述:
使用多線程模擬賽馬比賽,要求所有馬(線程)都準備好后才能開始比賽,所有馬到達終點后裁判宣布比賽結果。
解題思路:
- 使用
sync.WaitGroup
來同步多個線程。 - 使用
sync.Mutex
來保護共享資源的訪問。 - 每個馬(線程)在準備好后通知主線程,主線程在所有馬都準備好后發出開始信號。
- 所有馬到達終點后,裁判宣布比賽結果。
代碼實現(Golang):
package mainimport ("fmt""sync""time"
)type Horse struct {ID intReady boolFinish boolmu sync.Mutex
}func (h *Horse) run(start, finish *sync.WaitGroup) {defer finish.Done()// 馬準備好h.mu.Lock()h.Ready = trueh.mu.Unlock()fmt.Printf("Horse %d is ready!\n", h.ID)// 等待所有馬準備好start.Wait()// 模擬賽跑time.Sleep(time.Duration(h.ID) * time.Second)h.mu.Lock()h.Finish = trueh.mu.Unlock()fmt.Printf("Horse %d has finished!\n", h.ID)
}func main() {const numHorses = 10var start, finish sync.WaitGrouphorses := make([]*Horse, numHorses)// 初始化馬for i := 0; i < numHorses; i++ {horses[i] = &Horse{ID: i + 1}}// 設置WaitGroupstart.Add(1)finish.Add(numHorses)// 啟動賽馬線程for _, horse := range horses {go horse.run(&start, &finish)}// 等待所有馬準備好for {allReady := truefor _, horse := range horses {horse.mu.Lock()if !horse.Ready {allReady = false}horse.mu.Unlock()}if allReady {break}time.Sleep(100 * time.Millisecond)}// 所有馬準備好,開始比賽fmt.Println("All horses are ready! Start racing!")start.Done()// 等待所有馬到達終點finish.Wait()fmt.Println("All horses have finished! Race is over!")
}
代碼解析:
- Horse結構體:包含馬的ID、準備狀態、完成狀態和一個互斥鎖。
- run函數:每個馬(線程)的執行函數,模擬馬的準備、等待開始信號、賽跑和到達終點。
- 主函數:初始化馬、設置WaitGroup、啟動線程、等待所有馬準備好、發出開始信號、等待所有馬到達終點并宣布比賽結果。
📍10. LeetCode 394,給定一個經過編碼的字符串,返回它解碼后的字符串。
問題描述:
給定一個編碼字符串,格式為k[encoded_string]
,其中k
是一個正整數,encoded_string
是一個字符串。要求解碼這個字符串,返回解碼后的結果。
解題思路:
- 使用棧來處理嵌套的編碼字符串。
- 遍歷字符串,遇到數字、字母、
[
和]
時分別處理。 - 遇到數字時,解析完整的數字并壓入數字棧。
- 遇到
[
時,將當前的字符串壓入字符串棧,并重置當前字符串。 - 遇到
]
時,彈出數字棧和字符串棧,將當前字符串重復相應次數后與彈出的字符串拼接。 - 遇到字母時,直接拼接到當前字符串。
代碼實現(Golang):
package mainimport ("fmt""strconv""strings"
)func decodeString(s string) string {var numStack []intvar strStack []stringvar currentNum intvar currentStr strings.Builderfor i := 0; i < len(s); i++ {char := s[i]switch {case char >= '0' && char <= '9':// 解析數字num, _ := strconv.Atoi(string(char))currentNum = currentNum*10 + numcase char == '[':// 將當前數字和字符串壓入棧numStack = append(numStack, currentNum)strStack = append(strStack, currentStr.String())// 重置當前數字和字符串currentNum = 0currentStr.Reset()case char == ']':// 彈出數字和字符串num := numStack[len(numStack)-1]numStack = numStack[:len(numStack)-1]prevStr := strStack[len(strStack)-1]strStack = strStack[:len(strStack)-1]// 重復當前字符串并拼接到前一個字符串currentStr.WriteString(strings.Repeat(currentStr.String(), num))currentStr = strings.Builder{}currentStr.WriteString(prevStr)default:// 字母直接拼接到當前字符串currentStr.WriteByte(char)}}return currentStr.String()
}func main() {encoded := "3[a2[c]]"decoded := decodeString(encoded)fmt.Println(decoded) // 輸出: "accaccacc"
}
代碼解析:
- numStack:用于存儲數字的棧。
- strStack:用于存儲字符串的棧。
- currentNum:當前解析的數字。
- currentStr:當前解析的字符串。
- 遍歷字符串:根據字符類型分別處理數字、
[
、]
和字母。 - 解碼過程:通過棧的壓入和彈出操作,處理嵌套的編碼字符串。
歡迎關注我的小紅書一起來討論。