📍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 main
import (
"fmt"
"sync"
"time"
)
type Horse struct {
ID int
Ready bool
Finish bool
mu sync.Mutex
}
func (h *Horse) run(start, finish *sync.WaitGroup) {
defer finish.Done()
// 马准备好
h.mu.Lock()
h.Ready = true
h.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 = true
h.mu.Unlock()
fmt.Printf("Horse %d has finished!\n", h.ID)
}
func main() {
const numHorses = 10
var start, finish sync.WaitGroup
horses := make([]*Horse, numHorses)
// 初始化马
for i := 0; i < numHorses; i++ {
horses[i] = &Horse{ID: i + 1}
}
// 设置WaitGroup
start.Add(1)
finish.Add(numHorses)
// 启动赛马线程
for _, horse := range horses {
go horse.run(&start, &finish)
}
// 等待所有马准备好
for {
allReady := true
for _, 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 main
import (
"fmt"
"strconv"
"strings"
)
func decodeString(s string) string {
var numStack []int
var strStack []string
var currentNum int
var currentStr strings.Builder
for i := 0; i < len(s); i++ {
char := s[i]
switch {
case char >= '0' && char <= '9':
// 解析数字
num, _ := strconv.Atoi(string(char))
currentNum = currentNum*10 + num
case char == '[':
// 将当前数字和字符串压入栈
numStack = append(numStack, currentNum)
strStack = append(strStack, currentStr.String())
// 重置当前数字和字符串
currentNum = 0
currentStr.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:当前解析的字符串。
- 遍历字符串:根据字符类型分别处理数字、
[
、]
和字母。 - 解码过程:通过栈的压入和弹出操作,处理嵌套的编码字符串。
欢迎关注我的小红书一起来讨论。