一、缓存
缓存是数据交换的缓冲区,是存储数据的临时地方,一般读写性能较高。
如浏览器会把静态资源先加载到浏览器缓存中,tomcat中有应用层缓存等,则数据库也有数据库缓存。
缓存的作用:
- 降低后端负载
- 提高读写效率,降低响应时间
缓存的成本:
- 数据一致性成本
- 代码维护成本
- 运维成本等更高
二、redis缓存
在不添加缓存的情况下则是客户端发送请求,请求到数据库中获取所需要的数据,然后返回给客户端。
而添加缓存则在客户端和数据库中添加了一个中间层,客户端的请求会优先到达Redis缓存中,如果在缓存中查到数据则直接返回给客户端,缓存中没有数据则再往数据库中查找,这样大大减轻了数据的压力。
缓存更新策略:
- 低一致性需求:使用内存淘汰机制,例如店铺类型的查询缓存
- 高一致性需求:主动更新,并以超时剔除作为兜底方案,例如店铺详情查询的缓存。
双写一致性: - 当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
主动更新:
- 由缓存的调用者,在更新数据库的同时更新缓存。
- 缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性问题。
- 调用者只操作缓存,由其它线程异步的将缓存数据持久化到数据库,保持一致性。
操作缓存和数据库时有三个问题需要考虑:
- 更新缓存:每次更新数据库都更新缓存,无效写操作较多
- 删除缓存:更新数据时让缓存失效,查询时再更新缓存
- 删除缓存相比更新缓存减少许多无效的写操作。即,更新数据时之间把缓存中的数据删除,直到有用户访问时去数据库查询并存储到缓存中。
如何保证缓存与数据库的操作的同时成功或失败:
- 单体系统,将缓存与数据库操作放在一个事务中
- 分布式系统,利用TCC等分布式事务方案
先操作缓存还是先操作数据库:
- 先操作数据库,再删除缓存,防止在操作数据库的时候有大量请求访问数据库。
三、缓存穿透
缓存穿透是指客户端请求的数据在缓存中都不存在,导致所有的请求都落在数据库中造成数据库短时间内承受大量请求而崩掉。
解决办法:
- 缓存空对象:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟。
优点:实现简单,维护方便
缺点:造成额外的内存消耗,可能造成短期的不一致。 - 布隆过滤:是指在客户端和redis之间又加了一层布隆过滤器,将所有可能存在的数据哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储查询压力,但不一定是准确的,返回存在却不一定是存在。
优点:内存占用较少,没有多余key
缺点:实现复杂,存在误判可能
四、缓存雪崩
缓存雪崩是指在同一时间段大量的缓存key同时失效或者redis服务宕机,导致大量的请求落在数据库上,造成数据库短时间内承受大量请求而崩掉。
解决办法:
- 给不同key的TTL(过期时间)添加随机值,将失效时间分散开来。
- 利用redis集群提高服务的可用性
- 给缓存业务添加降级限流策略,提前做好容错处理,当出现故障时及时的进行服务降级拒绝访问。
- 给业务添加多级缓存,不仅使用redis一种缓存。
五、缓存击穿
缓存击穿问题也叫热点key问题,就是被一个高并发访问并且缓存重建业务较复杂的key突然失效了,无数请求访问会在瞬间给数据库带来巨大的冲击。
解决方案:
- 互斥锁:用锁的方式只让一个线程来重建缓存数据,其他线程等待缓存构建。
- 逻辑过期:设置热点key永不过期,一个线程来获取互斥锁开启写入线程,其他线程获取互斥锁失败,则获取缓存中的旧数据。适用于不严格要求数据一致性。