面试题答案
一键面试同步算法
- 读写分离策略:
- 读操作:优先从Redis读取数据,因为Redis读写性能极高,能快速响应读请求,减轻MySQL压力。若Redis中无对应数据,则从MySQL读取,读取后将数据写入Redis,设置合适的过期时间,以便后续读请求可直接从Redis获取。
- 写操作:同时写入MySQL和Redis。为保证数据一致性,先写MySQL,成功后再写Redis。若写Redis失败,需有重试机制,可设定重试次数和时间间隔,如最多重试3次,每次间隔1秒。
- 基于时间戳的同步:
- 在MySQL表中添加一个时间戳字段,每次数据更新时,同时更新该时间戳。
- Redis中也记录数据的时间戳。同步时,对比MySQL和Redis中数据的时间戳,若MySQL中的时间戳更新,则将MySQL数据同步到Redis。
锁机制
- 分布式锁:
- 使用Redis的SETNX(SET if Not eXists)命令实现分布式锁。在进行关键同步操作(如写MySQL和写Redis)前,先获取锁。例如,使用
SETNX lock_key unique_value
命令,若返回1,表示获取锁成功,可进行同步操作;若返回0,表示锁已被其他进程持有,等待一段时间后重试。 - 为防止死锁,需给锁设置过期时间,如使用
EXPIRE lock_key expire_time
命令,设置锁在一定时间(如10秒)后自动释放。
- 使用Redis的SETNX(SET if Not eXists)命令实现分布式锁。在进行关键同步操作(如写MySQL和写Redis)前,先获取锁。例如,使用
- 悲观锁与乐观锁:
- 悲观锁:在MySQL中,对于关键数据的更新操作,使用
SELECT... FOR UPDATE
语句,锁定数据行,防止其他事务并发修改,确保同步操作的原子性。 - 乐观锁:在MySQL表中添加版本号字段,每次更新数据时,版本号加1。在更新数据前,先对比当前版本号与数据库中的版本号,若一致则更新,并将版本号加1;若不一致,则说明数据已被其他事务修改,需重新读取数据并进行同步操作。
- 悲观锁:在MySQL中,对于关键数据的更新操作,使用
队列应用
- 消息队列:
- 引入消息队列(如Kafka、RabbitMQ等),将写操作的请求发送到消息队列中。消费者从队列中取出消息,按顺序执行写MySQL和写Redis操作,保证操作的顺序性,避免并发写入导致的数据不一致问题。
- 消息队列可以起到削峰填谷的作用,在高并发时,将大量请求暂存于队列中,消费者按一定速率处理,减轻数据库和缓存的压力,保障同步过程的稳定性。
- 延迟队列:
- 对于一些特殊场景,如数据删除操作,可使用延迟队列。先将删除请求发送到延迟队列中,延迟一定时间(如5分钟)后,消费者取出请求,先删除Redis中的数据,再删除MySQL中的数据。这样可以避免在高并发时,因先删除MySQL数据,而Redis数据还未删除,导致读请求从Redis获取到已删除数据的情况。
数据校验与补偿机制
- 定期数据校验:
- 定时任务定期对比Redis和MySQL中的数据,可采用抽样对比或全量对比的方式。例如,每天凌晨对部分关键数据进行全量对比,若发现数据不一致,记录差异并进行修复。
- 对比时可使用哈希算法,计算数据的哈希值进行快速比对,若哈希值不一致,则进一步详细对比数据内容。
- 补偿机制:
- 当检测到数据不一致时,根据不一致的类型和原因进行补偿操作。如若是Redis数据未更新,可重新从MySQL读取数据并写入Redis;若是MySQL数据未更新,可重新执行写MySQL操作。
- 记录所有的补偿操作,以便后续分析和监控,确保数据一致性问题得到彻底解决。