
1) 【一句话结论】:采用**分布式数据库(MySQL)+ 分布式缓存(Redis)+ WebSocket + 消息队列(如Kafka)**的最终一致性方案,通过乐观锁(版本号字段)和本地缓存容错机制,实现多端学习进度实时同步且一致性可控(允许短暂不一致,符合业务容忍度)。
2) 【原理/概念讲解】:老师口吻,先讲核心挑战:多端实时同步的核心矛盾是“并发写入冲突”与“网络延迟下的数据不一致”。传统强一致性(如分布式事务)在分布式场景下成本高、性能低,因此选择最终一致性(CAP定理中CA或CP,根据业务对一致性的容忍度调整)。具体架构设计如下:
user_progress:{user_id}),作为读源或同步源,减少数据库压力;user_id+course_id+chapter_id),再更新Redis缓存(写时更新),同时将变更消息推送到消息队列(如Kafka),其他端通过订阅消息队列或Redis Pub/Sub获取最新数据。user_progress表中添加版本号字段(version,自增整数),采用乐观锁机制:当更新进度时,先检查当前版本号是否等于预期版本号,若匹配则更新并递增版本号,否则回滚并提示用户重试,避免脏数据。3) 【对比与适用场景】:
| 方案类型 | 定义 | 特性 | 使用场景 | 注意点 |
|---|---|---|---|---|
| 强一致性(数据库级同步) | 通过分布式事务(如两阶段提交)确保多端写入原子性 | 数据立即同步,无延迟 | 对数据一致性要求极高(如金融交易、订单系统),但性能低 | 分布式事务成本高,网络故障易导致阻塞,不适合高并发场景 |
| 最终一致性(应用级消息队列) | 用户端写入数据库,服务端通过MQ异步同步其他端 | 延迟在秒级,允许短暂不一致 | 对实时性要求高,可容忍短暂不一致(如学习进度、社交动态) | 需处理消息丢失、重试逻辑,保证幂等性,避免数据重复 |
4) 【示例】:
数据模型设计(学习进度表):
CREATE TABLE user_progress (
user_id INT PRIMARY KEY,
course_id INT NOT NULL,
chapter_id INT NOT NULL,
progress INT NOT NULL DEFAULT 0, -- 当前学习进度(如章节完成百分比)
version INT NOT NULL AUTO_INCREMENT, -- 乐观锁版本号
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
伪代码(iOS客户端更新进度):
func updateStudyProgress(progress: Int, completion: @escaping (Result<Void, Error>) -> Void) {
// 1. 检查网络连接,若断网则缓存到本地(如Core Data)
if !isNetworkReachable {
localDB.updateProgress(userId: userId, courseId: courseId, chapterId: chapterId, progress: progress)
completion(.success)
return
}
// 2. 发送WebSocket请求到服务端
let request = ["action": "updateProgress", "userId": userId, "courseId": courseId, "chapterId": chapterId, "progress": progress]
socket.send(request) { result in
switch result {
case .success(let response):
// 3. 服务端处理:更新数据库(主库)并刷新Redis
let sql = "UPDATE user_progress SET progress = ?, version = version + 1 WHERE user_id = ? AND course_id = ? AND chapter_id = ? AND version = ?"
db.execute(sql, [progress, userId, courseId, chapterId, currentVersion]) { success, err in
if success {
redis.set("user_progress:\(userId):\(courseId):\(chapterId)", value: progress)
completion(.success)
} else {
completion(.failure(DatabaseConflictError()))
}
}
case .failure(let err):
// 网络错误:重试逻辑(指数退避)
retryUpdate(progress: progress, completion: completion, attempt: 1)
}
}
}
func retryUpdate(progress: Int, completion: @escaping (Result<Void, Error>) -> Void, attempt: Int) {
let delay = 2 << (attempt - 1) // 指数退避(2^attempt秒)
DispatchQueue.global().after(deadline: .now() + delay) {
updateStudyProgress(progress: progress, completion: completion)
}
}
服务端处理WebSocket请求(Go语言伪代码):
wsHandler.HandleFunc("/progress", func(c *websocket.Conn) {
var msg map[string]interface{}
if err := json.NewDecoder(c).Decode(&msg); err != nil {
return
}
userId := msg["userId"].(string)
courseId := msg["courseId"].(string)
chapterId := msg["chapterId"].(string)
progress := msg["progress"].(int)
db.Exec("UPDATE user_progress SET progress = ?, version = version + 1 WHERE user_id = ? AND course_id = ? AND chapter_id = ? AND version = ?", progress, userId, courseId, chapterId, currentVersion)
redis.Set("user_progress:" + userId + ":" + courseId + ":" + chapterId, progress)
kafkaProducer.Produce("progress_updates", []byte(userId + ":" + courseId + ":" + chapterId + ":" + strconv.Itoa(progress)))
})
5) 【面试口播版答案】:
“面试官您好,针对多端学习进度实时同步,我设计的方案是采用**分布式数据库(MySQL)+ 分布式缓存(Redis)+ WebSocket + 消息队列(如Kafka)**的最终一致性方案,通过乐观锁和本地缓存容错机制,实现多端数据同步。具体来说,用户在任意端更新进度时,先通过WebSocket向服务端发送请求;服务端先更新MySQL主库(带版本号字段),再刷新Redis缓存,同时将变更推送到消息队列;其他端通过WebSocket或消息队列实时获取最新数据。对于并发冲突,使用数据库的乐观锁(版本号),当更新失败时回滚并提示用户重试,避免数据冲突。网络不稳定时,客户端会缓存到本地,恢复后自动同步,保证数据不丢失。”
6) 【追问清单】:
7) 【常见坑/雷区】: