缓存的设计
什么是缓存
缓存,是一种存储数据的组件,它的作用是让对数据的请求更快地返回。
缓存存在的意义
提升访问速度,提升性能,从而能够抗住更高的并发 。因为在系统中大部分的系统瓶颈出现在数据库中,所以我们可以减少数据库的访问来提高系统的并发量。
缓存中存在的问题
1.缓存一致性问题
2.缓存是适用于读多写少的场景
缓存的设计
1.主动覆盖更新策略
获取数据只依赖缓存, 不依赖db, db数据更新时, 会更新缓存数据.
读策略:
- 缓存读取数据, 无论是否命中, 均返回, 不会穿透到db
写策略:
- 业务事件/任务, 监听业务数据变化、主动更新缓存数据
优点:
- 适用于大量并发读的场景, 利用缓存快速读取特性, 提供高性能.
缺点:
- 存在数据不一致问题, 业务db数据已更新, 但缓存数据未更新
- 高可用性问题, 因为客户端读只和缓存交互, 不会穿透到db, 缓存服务出问题,等于服务不可用.
2.过期更新策略
读策略:
- 缓存读取数据、未命中 → 查询数据库 → 更新缓存数据
写策略:
- 业务直接操作db, 不更新缓存
- 缓存数据失效后, 读操作会加载db、主动更新缓存数据.
优点
- 实现简单
缺点
-
存在数据不一致问题, 业务db数据已更新,但缓存数据未过期, 无法更新缓存数据.
-
使用时: 需要根据业务场景设置缓存过期时间.
3、同步更新策略:
同步更新缓存和数据库db
- 缺陷: 存在数据不一致问题.
- 过程: 请求1发起更新db → 请求2发起更新db → 请求2更新缓存 → 请求1更新缓存
- 原因: db、缓存两个动作独立,线程并发写入顺序不同,造成数据不一致.
解决: 我们可以在更新数据时不更新缓存,而是删除缓存中的数据,在读取数据时,发现缓存中没了数据之后,再从数据库中读取数据,更新到缓存中。
该解决方案就是Cache Aside旁路缓存策略
4、Cache Aside旁路缓存策略
读策略:
- 从缓存中读取数据
- 如果缓存命中,则直接返回数据;
- 如果缓存不命中,则从数据库中查询数据;
- 查询到数据后,将数据写入到缓存中
写策略:
- 更新数据库db
- 删除缓存记录
先删除缓存,后更新数据库行不行 ?
答: 不行, 存在数据不一致问题
ps: 请求 1 先把 cache 中的数据删除 -> 请求 2 从 DB 中读取数据→ 请求 1 再把 DB 中的数据更新
Cache Aside旁路缓存策略不足 ?
理论上还是有缺陷
1、当缓存未命中时, 请求 1 从 DB 读数据 A → 请求 2 写更新数据 A 到数据库并把删除 cache 中的 A 数据 → 请求 1 将数据 A 写入 cache
不过这种问题出现的几率并不高,原因是缓存的写入通常远远快于数据库的写入,所以在实际中很难出现,请求2已经更新了数据库并且清空了缓存,请求1才更新完缓存的情况
2、写入比较频繁时,缓存中的数据会被频繁地清理,这样会对缓存的命中率有一些影响
如果对缓存命中率有高的要求, 可以
- 采用同步更新策略 (更新数据时也更新缓存), 只是在更新操作先加一个分布式锁,解决并发问题导致的数据不一致问题, 当然这么做影响写入性能.
- 采用同步更新策略 (更新数据时也更新缓存), 只是给缓存加一个较短的过期时间,允许短暂的缓存数据不一致问题, 减少业务影响.
5、Read/Write Through读写穿透策略
用户只与缓存打交道,缓存在和数据库通信 ,写入or读取.
Read/Write Through 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中
读策略:
- 先查询缓存中数据是否存在
- 如果存在则直接返回
- 如果不存在,则由缓存组件负责从数据库中同步加载数据
写策略:
- 先查询要写入的数据在缓存中是否已经存在
- 如果已经存在,则更新缓存中的数据,并且由缓存组件同步更新到数据库中
- 如果缓存中数据不存在,直接同步更新数据库
Read/Write Through 策略缺陷:
1、极端情况仍有问题:
- 请求1写数据缓存、未命中 → 请求2读数据、缓存未命中 → 请求2 db查询,更新缓存 → 请求1完成数据库db更新操作
此时缓存中数据,不是请求1最新数据 - 这种case极少出现, 正常来说请求A的数据库写入操作, 不会慢于请求2的数据库查询操作
2、数据可靠性缺陷, 先写入缓存,在从同步缓存到db, 存在业务重要数据不能落db风险
3、Read/Write Through较少使用
- 因为没有现成的缓存同步db组件 ,例如redis 无组件直接提供cache到db的同步功能, 需要自行开发.
- 重要数据需要先落db, cache到db存在数据丢失风险