什么是缓存雪崩、击穿、穿透?
缓存雪崩
- 大量缓存数据在同一时间过期(失效)或者 Redis 故障宕机,Redis同时无法处理大量请求,请求全部访问数据,可能造成数据库崩溃等一系列问题
- 避免大量数据同时过期
- 均匀设置过期时间:设置过期时间时加上一个随机数,保证不会同时过期
- 互斥锁:保证同一时间一个请求读数据库,更新缓存
- 未获取到锁的请求可以选择返回空值/默认值/已过期的值,或者等待缓存构建后读取
- 需要设置超时时间,一个请求一直构建不完成,其他请求可以构建
- 双 key 策略:
- 后台更新缓存;
- 避免Redis宕机:
- 服务熔断:暂停业务对缓存服务的访问,直接返回错误,不会冲击数据库
- 服务限流:部分业务可以到数据库完成请求,其他的直接拒绝访问
- 构建 Redis 集群避免单机问题
缓存击穿
- 缓存中热点数据过期,大量请求打到数据库
- 可以看成是缓存雪崩的子集,使用避免大量数据同时过期的策略即可
缓存穿透
- 用户访问的数据不在缓存也不在数据库中,大量请求到来,缓存构建不出来,导致问题
- 原因一般有两种:
- 业务误操作,将缓存和数据库中的数据删除了
- 黑客攻击,大量读取不存在的数据
- 解决方案:
- 非法请求限制:判定请求参数是否合理,恶意请求直接返回错误
- 缓存空值或默认值:数据库中查不到的字段,缓存一个空值或者默认值,后续就可以直接从缓存获取了
- 布隆过滤器:存在的数据才会查数据库,否则直接返回错误
数据库和缓存如何保证一致性?
更新的顺序
- 先更新数据库,再更新缓存
- A 请求先将数据库的数据更新为 1
- 然后在更新缓存前,请求 B 将数据库的数据更新为 2
- 紧接着也把缓存更新为 2
- 然后 A 请求更新缓存为 1
- 数据不一致(数据库为2,缓存为1)
- 先更新缓存,再更新数据库
- A 请求先将数据库的数据更新为 1
- 然后在更新数据库前,请求 B 将数据库的数据更新为 2
- 紧接着也把缓存更新为 2
- 然后 A 请求将数据库的数据更新为 1
- 数据不一致(数据库为1,缓存为2)
可以看到更新顺序无论如何都可能出现不一致的问题 因此出现Cache Aside 策略(旁路缓存策略)。
写策略的步骤:
- 更新数据库中的数据;
- 删除缓存中的数据。
读策略的步骤:
- 如果读取的数据命中了缓存,则直接返回数据;
- 如果读取的数据没有命中缓存,则从数据库中读取数据,然后将数据写入到缓存,并且返回给用户。
更新删除的顺序
- 先删除缓存,再更新数据库
- A 请求先删缓存
- 在更新数据库前 B 请求删缓缓存,更新数据库为 2
- 获取数据请求到达,缓存中没有,从数据库读取数据为 2,放入缓存
- A 将数据库的数据更新为 1
- 数据不一致(数据库为1,缓存为2)
- 先更新数据库,再删除缓存
- A 先更新数据库为 1
- A 删除缓存,这个时候 C 请求获取数据,更新缓存
- 更新缓存成功前 B 更新数据库为 2
- B 删除缓存
- C 更新缓存为 1
- 数据不一致(数据库为2,缓存为1)
- 概率较小,更新缓存会更快
- 可以通过缓存过期时间,虽然数据不一致,但是时间长了会恢复
- 一般选择这个方式
- 但是需要保证两个操作都成功才行
- 需要重试机制保证
- 1、消息队列,将删除缓存放入消息队列,多次失败,需要报错告警
- 2、订阅 Mysql binlog,模拟自己是一个 mysql 从节点,解析 binlog 进行异步删除
- 两者都是异步删除
- 延迟双删
- 删除缓存,更新数据库,睡眠一段时间再删除缓存
- 这个睡眠时间很难把控,并且会阻塞当前请求,不建议
最建议先更新数据库,再删除缓存,但是更新数据库就会导致缓存被删除,会对缓存命中有影响,如果要求缓存命中率高,可以考虑
- 先更新数据库再更新缓存,可以通过分布式锁防止多写,但是性能有影响
- 更新缓存后,过期时间短一点(其实和直接删除差不多)
