数据以数据库中的数据为准,缓存中的数据是按需加载的。适用于一致性要求较高的场景
读数据:命中缓存直接返回,没有命中缓存则主动查询数据库更新缓存后再返回
写数据:更新数据库后删除缓存(事务内删除缓存)
存在并发问题导致数据不一致:
>A线程读到旧数据,挂起
>B线程写完新数据,删除缓存
>A线程拿到旧数据再次写入到缓存
外部只与缓存组件交互,以缓存中的数据为准,缓存组件和数据库交互。不常使用
读数据:命中缓存直接返回,没有命中缓存则由缓存组件查询数据库更新缓存后再返回
写数据:先读缓存,命中缓存先更新缓存,再由缓存组件去同步更新数据库,没有命中缓存则由缓存组件直接更新数据库
外部只与缓存组件交互,和2中不同的点在域异步更新数据库。适合提高写性能,但会丢数据
请求不存在的key,缓存没有命中,大量请求落在db
解决方案:
(1)提前校验入参,避免无效数据 (2)查到不存在的数据时,往缓存写一份为空的数据(适用于数据较少的情况,如共享数据) (3)布隆过滤器,不存在则一定不存在,存在则有可能不存在,过滤大量无效请求(适用于数据较多的情况,如用户维度的缓存)
短时间内大量key过期,大量请求落在db
解决方案:
(1)缓存失效时间不要设置在同一时间,常发生在和 “天” 有关的缓存
热点key过期后,瞬间大量请求落在db
解决方案:
(1)实际读库的时候,采用(本地锁+双重检测)控制查询流量 (2)热点key设置失效时间为晚上凌晨的流量低峰
缓存的数据过大,占用
进行数据切割分多份存储(推荐),或者压缩数据
根据场景选择合适的缓存策略
所有用户共享的数据,数量较少,例如投放计划、活动配置等
定期刷新适当的数据到缓存中,只读缓存
(1) 典型表结构:
id,startTime,endTime,state
(2) 需要定期缓存的数据:即将开始的数据 + 正在进行的数据 + 刚刚结束的数据
startTime <= (当前时间 + x天) and endTime >= (当前时间 - x天)
过期时间:x天
(3) 使用时再进行有效状态过滤
startTime <= now and endTime >= now and state = 有效
(4) 缓存模型
本地短期缓存(一级缓存,二级缓存回源)
redis缓存(二级缓存,定期刷新)
本地长期缓存(降级缓存,定期刷新)
(5) 异常告警
用户个人数据,数量巨大等
采用 cache aside 模式
java-jetcache
https://github.com/alibaba/jetcache
<dependency>
<groupId>com.alicp.jetcache</groupId>
<artifactId>jetcache-starter-redis</artifactId>
<version>${jetcache.latest.version}</version>
</dependency>