缓存

# 缓存雪崩

原因: 同一时间点,大量key过期,导致请求打到DB上,数据库过载宕机。

解决办法: 设置过期时间时采用固定时间+随机时间,防止同时过期。

# 缓存穿透

原因: 数据库和缓存中都没有相应数据,导致每次请求都会打到DB。

解决办法: 数据不存在时,缓存中保存空数据标识。如果客户端伪造大量不同的不存在的key同时请求,还是会有大量请求打到DB,还需要结合业务逻辑校验、限流、布隆过滤器等方法。

# 缓存击穿

原因: 对于一个热点key,在过期的瞬间,大量请求打到DB上。

解决办法: 当缓存中不存在,需要查询数据库时,通过加锁,只允许获得锁的请求查询数据库并更新缓存。其他请求在获得锁后再次检查缓存已存在,不用再请求数据库。

# 数据库与缓存数据一致性

# 先更新缓存,再更新数据库

这种方式会导致还未落库的数据被写到缓存中,被其他请求读取到,导致脏数据。不可取。

# 先更新数据库,再更新缓存

  1. 线程A更新数据库

  2. 线程B更新数据库

  3. 线程B更新缓存

  4. 线程A更新缓存

这种并发场景下,缓存中数据与数据库数据不一致。

且某些冷数据可能并不需要在更新时就缓存。

# 先删除缓存,再更新数据库

  1. 线程A删除缓存

  2. 线程B请求数据,未命中缓存,查数据库,并加载到缓存

  3. 线程A更新数据库

这种并发场景下,仍然存在缓存中数据与数据库数据不一致的情况。而且读请求一般数量较大,先删除缓存很可能立即被后续请求重新加载缓存, 所以这一步其实是无效的。

# 先更新数据库,再删除缓存

场景1:

  1. 缓存刚好过期

  2. 线程A查询数据库, 得到旧数据

  3. 线程B更新数据库

  4. 线程B删除缓存

  5. 线程A更新缓存, 写入旧数据

场景2:

  1. 线程A更新数据库

  2. 线程B查询数据,从缓存中获得旧数据

  3. 线程A删除缓存

场景3: 当使用了读写分离时

  1. 线程A更新主库数据,删除缓存

  2. 从库还未更新, 线程B读取从库得到旧数据, 更新缓存

这些场景下还是会存在缓存与数据库不一致的情况。 但是发生概率已经大大降低。

可以再加一个延时更新缓存的操作, 例如引入消息、监控binlog的canal等, 异步更新缓存。

# 强一致性保障

从上面四种处理方式可以看出, 先更新数据库, 再删除缓存是较为理想的方式。

但是在更新数据库与删除缓存之间, 读请求仍然有短暂的时间获取到旧数据。

读请求查询数据库与写入缓存之间, 写请求可能会更新数据, 导致读请求写入旧数据。

所以可以引入分布式读写锁将读请求与写请求隔离。 不过这种保障强一致性势必会提升系统的复杂性、而且会降低性能。

上次更新: 2023/04/09, 16:34:32
最近更新
01
go-admin-ui项目仿写练手1-登录页
06-29
02
maven依赖问题
06-17
03
JVM相关命令
02-21
更多文章>