面试题答案
一键面试可能出现的并发问题
- 数据竞争:大量写操作可能导致数据不一致,比如多个写操作同时修改同一个 key 的值,最终结果取决于操作顺序。
- 缓存击穿:高并发情况下,一个热点 key 在缓存中失效时,大量请求同时穿透到后端数据库,可能压垮数据库。
- 缓存雪崩:大量 key 同时过期,导致大量请求直接打到后端数据库,引起系统性能问题甚至崩溃。
解决方案
- 事件队列的管理
- 写队列:创建一个写操作队列,将所有写操作放入队列中。Redis 本身是单线程处理命令,利用这一特性,依次从队列中取出写操作并执行,保证写操作顺序执行,避免数据竞争。例如可以使用 Redis 的
RPUSH
和LPOP
命令实现简单的队列。 - 读队列(可选):对于一些对实时性要求不高的读操作,可以放入读队列,按顺序处理,以减轻 Redis 瞬间压力。
- 写队列:创建一个写操作队列,将所有写操作放入队列中。Redis 本身是单线程处理命令,利用这一特性,依次从队列中取出写操作并执行,保证写操作顺序执行,避免数据竞争。例如可以使用 Redis 的
- 读写锁的运用
- 乐观锁:在写操作前,先获取当前 key 的值及版本号。写操作时,带上版本号,只有版本号匹配时才执行写操作,否则重试。在 Redis 中可通过
WATCH
命令实现乐观锁机制,例如:
- 乐观锁:在写操作前,先获取当前 key 的值及版本号。写操作时,带上版本号,只有版本号匹配时才执行写操作,否则重试。在 Redis 中可通过
WATCH key
val = GET key
// 处理业务逻辑,修改 val
MULTI
SET key val
EXEC
- **悲观锁**:对于关键数据的读写,使用 `SETNX` 命令实现悲观锁。在写操作前,通过 `SETNX lock_key 1` 获取锁,如果获取成功则执行写操作,完成后使用 `DEL lock_key` 释放锁;读操作同理,获取锁后再读数据。但要注意设置合理的锁过期时间,防止死锁。
3. 应对缓存击穿和雪崩
- 缓存击穿:使用互斥锁(如上文悲观锁方式),在热点 key 失效时,只允许一个请求去后端数据库加载数据并回设到缓存,其他请求等待,避免大量请求同时穿透。同时可以设置热点 key 永不过期,定期更新数据。
- 缓存雪崩:为不同 key 设置不同的过期时间,避免大量 key 同时过期。可以使用随机过期时间,例如 EXPIRE key (random(1800, 3600))
,让过期时间在一个范围内随机分布。