1. 缓存失效
1.1 缓存穿透
- 原理:每次访问都是一个数据库和缓存中都不存在的数据,所以每次的请求都会直接直接访问到数据库中。
- 解决方案:
- 接口效验:可以在最外层做一层身份效验或数据校验等。比如商品的ID都是正整数,则可以对非正整数直接过滤等等。
- 缓存空值:当缓存和数据库中都没有值时,将空值写入到缓存中,然后设置较短的过期时间。
- 布隆过滤器:
1.2 缓存击穿
- 原理:一个热点key,在缓存过期的一瞬间,同时有大量的请求过来,这时大量的请求都会直接进入到数据库中,造成数据库的压力剧增。
- 解决方案:
- 增加互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并到数据库执行数据库查询操作,其他线程就会拿不到锁就会陷入等待,直到第一个线程将数据写入到缓存后,直接走缓存。
- 逻辑过期:直接将缓存设置为不过期,然后增加一个逻辑过期字段。
- 当热点key过期后,就增加互斥锁只让重新开启一个线程去更新缓存,在更新之前的其它请求得到的是旧数据,更新完成后释放锁。
1.3 缓存雪崩
- 原理:大量热点key同时过期了,导致缓存同一时刻全部失效,造成数据库压力剧增,引起雪崩。
- 过期时间打散:给缓存的过期时间打上一个随机值,让每个key的过期时间分布开来,让大量的热点key不会同时失效。
- 增加互斥锁:在并发的多个请求中,只有第一个请求线程能拿到锁并到数据库执行数据库查询操作,其他线程就会拿不到锁就会陷入等待,直到第一个线程将数据写入到缓存后,直接走缓存。
- 多级缓存:给业务添加多级缓存。
- 给缓存业务添加降级限流策略。
- 与缓存击穿的区别:缓存击穿是一个热点 key,缓存雪崩是一组热点 key。
互斥锁
使用setnx:将 key 的值设为 value ,当且仅当 key 不存在。若给定的 key 已经存在,则 SETNX 不做任何动作
2. String结构和Hash结构
3. 缓存更新策略
- 低一致性需求:使用内存淘汰机制。例如商品类型的查询缓存,很少发生变化。
- 高一致性需求:主动更新,并以超时上出作为兜底方案。例如店铺详情查询缓存。
3.1 主动更新缓存时有三个问题需要考虑:
- 删除缓存还是更新缓存?
- 更新缓存:每次跟新数据都更新缓存。缺点:当写多读少时,无效操作会增加。
- 删除缓存:更新数据库时让缓存失效,查询时再更新数据。适合读多写少
- 如何保证缓存与数据库的操作同时成功或失败
- 单体系统:将缓存与数据库操作放在一个事务。
- 分布式系统:利用TCC等分布式事务方案。
- 先删除缓存还是操作数据库
- 先删除缓存,再操作数据库。
- 先操作数据库,再删除缓存。(更好)
为什么先操作数据库,再删除缓存更好?
- 因为先操作数据库,再删除缓存出现缓存不一致的概率更低。更新时删除缓存,没命中缓存时写入缓存,一个线程查,一个线程写。
- 想要出现缓存不一致的条件如下:
- 缓存正好失效。
- 两个线程需要并行执行。
- 当其中一个线程在查询数据库后写入缓存的极小时间内,另一个线程跟新了数据库,并删除缓存。
- 解决办法:超时剔除。
4. Redis 事务是否具备原子性?
- Redis事务执行过程中,如果一个命令执行出错,那么就返回错误,然后还是会接着继续执行下面的命令,之前执行成功的也不会回滚。
- Redis在执行事务之前会检查命令不存在或者是命令参数不对,如果出现这种情况就会直接返回错误,当时一些程序员的逻辑错误是不支持,例如对 String 类型的数据库键执行对 HashMap 类型的操作!