黑马点评商户缓存
一致性
缓存更新策略
- 内存淘汰:Redis 内存不足时自动淘汰部分数据,下次请求时更新缓存
- 超时剔除:给缓存数据添加 TTL 时间,到期后自动删除缓存。下次请求时更新缓存。
- 主动更新:在修改数据库的同时更新缓存。
缓存数据库一致性
有请求并发到来时,不管是先更新缓存后更新数据库,还是先更新数据库后更新缓存,都可能出现缓存不一致问题。
那么,使用 Cache Aside 策略,也就是只有在读数据的时候,cache 才有可能被更新。
写策略:当我们修改数据时,更新数据库里的数据,直接删除缓存中的数据。
读策略:当读数据时,如果发现缓存里没有该数据,则从数据库里面读数据更新到缓存中。
先更新后删除(延迟双删)
线程A遵循读策略,线程B同时更新数据
- 线程A查缓存未命中,查数据库x=20
- 线程B更新数据库,x=21
- 线程B删除缓存(第一次删除)
- 线程A把旧数据写入缓存,x=20
- 线程B延迟一段时间后主动执行第二次删除(非被动检测)
很小概率会出现这种情况。因为步骤一和步骤四之间,线程 B 查了数据库,但是实际上线程 A 查缓存写缓存都很快。
延迟时间(如500ms)需大于"查询DB+写入缓存"的耗时(通常 < 100ms)
只要延迟时间设置合理,可以覆盖线程A的旧数据写入操作(步骤4)
即使没有发生并发冲突,重复删除也是安全的幂等操作
如果删除缓存操作失败了怎么办?
@Transactional 注解修饰方法。如果删除缓存失败,那么数据库更新的操作也需要回滚。
其它方法
商铺缓存
采用主动更新+超时剔除的策略。
- 根据 ID 查商铺,如果缓存没有命中,则查数据库,将结果写入缓存并设置超时时间。
- 修改数据时,先把新的数据存入数据库,再删除缓存。
把 Shop 对象转换成 JSON 字符串,作为 value 存储到 Redis 中。这适用于对象的结构复杂,且更新操作较少的情况。
如果更新操作较多,可以在redis里面存hash
缓存工具类
|
|
StringRedisTemplate 是一个由 Spring Boot 自动配置的 Redis 访问工具类,它会在 Spring 启动时根据 application.yml 中的配置自动实例化。
Spring 会为每个 Redis 连接池和相关配置创建一个 StringRedisTemplate 的单例实例,并将它注入到需要的地方。
CacheClient:
CacheClient 是一个标注为 @Component 的类,这意味着 Spring 会自动将它注册为一个 bean 并将其注入到其他类中。
由于 CacheClient 使用构造器注入 StringRedisTemplate,因此 Spring 会先创建 StringRedisTemplate 的单例实例,然后将其传入 CacheClient 的构造函数,从而完成注入。
缓存穿透
用户请求的数据既不在缓存内,也不在数据库内。大量此类请求会给数据库造成巨大压力。
- 缓存空值。如果根据商铺 ID 去数据库里查这个商铺,但是返回为空时,则把null值作为 value存入Redis.
- 布隆过滤器: 将所有存在的商品ID预先加载到布隆过滤器中。
- 请求到达时,先检查布隆过滤器。如果不存在,直接返回“商品不存在”。如果存在,继续查询缓存或数据库。
缓存雪崩
同一时间大量缓存 key 失效/Redis 宕机,造成大量请求到达数据库。
解决方案:给 key 设置随机过期时间;采用 Redis 集群。
缓存击穿
预热
被高并发访问、且缓存重建复杂的key失效。同一时间会有无数请求来访问数据库请求这个key。
解决方案:逻辑过期+互斥锁。
从Redis查商户缓存。如果发现缓存过期,则试图抢占锁,抢占锁后开启新线程去重建缓存,当前线程立即返回旧的商户信息。