1.什么是缓存
缓存就是数据交换的缓冲区,称为cache,是存储数据的临时地方,一般读写性能较高
典型例子就是在计算机的CPU和内存、磁盘。CPU的运算能力非常强大,运算速度已经远远超过内存或者磁盘读写数据的能力。但是先读到数据才能进行处理,那么数据读写能力较低就限制了CPU的性能。CPU内部添加缓存之后,就直接从缓存中读取数据进行计算,充分应用了CPU的性能。
缓存的应用场景
缓存的作用
- 降低后端负载
- 提高读写效率,降低响应时间(可应对更高并发的场景了)
缓存的成本
- 数据一致性成本(数据库数据更新了,缓存的数据还是旧数据,命中缓存就读到了旧数据,可能带来严重问题)
- 解决问题需要进行代码维护,维护成本较高
- 运维成本(集群模式的部署和维护、硬件成本)
添加redis缓存
2.缓存更新策略
针对 数据一致性 问题,提出了缓存更新策略,用来解决数据一致性问题、
更新策略主要分为三种
低一致性需求:使用内存淘汰机制
高一致性需求:使用主动更新,并以剔除超时时间兜底
主动更新策略:
Cache Aside Pattern:由缓存的调用者,在更新数据库时同时更新缓存
Read/Write Through Pattern:缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题
Write Behind Caching Pattern:调用者只操作缓存,用其他线程异步将缓存持久化到数据库,保证最终一致
一般使用的主动更新策略是第一种,操作缓存和数据库时还需要注意三个问题
1.删除缓存还是更新缓存?
更新缓存:每次更新数据库都更新缓存,无效写操作较多
删除缓存:更新数据库时让缓存失效,查询时再更新缓存(有人访问才更新,延迟加载,写的频率更低,有效更新更多,适合选择的方案)
2.如何保证缓存与数据库的操作的同时成功或失败?保证操作原子性
单体系统,将缓存与数据库操作放在一个事务
分布式系统,利用TTC等分布式事务方案
3.先操作缓存还是先操作数据库
先操作缓存再操作数据库
正常情况
异常情况:
删除缓存很快了,更新数据库相对较慢,查写操作也快,所以上述情况发生概率是不小的
再看先操作数据库,后删除缓存
正常情况
特殊情况:缓存失效
场景:并发、缓存失效
1和4步骤是写缓存的操作,相比线程进行数据库的更新速度(写操作)来说,速度是极快的,因此这种情况不易发生(如果发生了,可以在加一个超时时间,超时后就删除了)
综上所述,这两种方案都有可能的发生线程不安全问题,但是方案二(先操作数据库,再删除缓存)更加安全
3.缓存穿透
请求到达redis为命中,然后请求到达数据库,也为命中。如果大量的不存在于数据库中的数据同时打到数据库,数据库很可能会难以承载而崩溃。
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都打到数据库
解决方案:
缓存null值
优点:实现简单维护方便
缺点:额外的内存消耗(设置TTL )、可能造成短期不一致(假如数据库真的插入了对应数据,之前redis写入的缓存是null,就数据不一致了,TTL过期后才能正确查询)
布隆过滤
4.缓存雪崩
缓存雪崩是同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量的请求到达数据库,带来巨大的压力
解决方案:
给不同的key的TTL添加随机值(控制过期时间)
利用redis集群提高服务的可用性(哨兵机制)
给缓存业务添加限流策略(拒绝服务,牺牲部分服务,保护数据库)
给业务添加多级缓存
5.缓存击穿
缓存击穿问题也叫做热点key问题,就是一个被高并发访问的并且缓存重建业务较复杂的key突然失效了,无数的请求访问会瞬间非数据库带来巨大的冲击
缓存重建的时间比较长,后续的所有请求都会打到数据库上
解决方案:
互斥锁
只有获取锁成功的现线程才能重建,其它线程等待(保证了数据一致性)
其它线程都在等待,性能比较差
逻辑过期
发现数据过期后,获取锁,交由另一个线程进行重建。然后返回旧的过期数据,别的线程也是如此,在重建完成前都返回过期数据,在重建完成后返回最新的数据。所有的请求不会直接打到数据库上、不能保证数据的一致性
对比