文章目录
一、Redis的共享session应用
二、分布式缓存
1、缓存
-
缓存的作用:
-
缓存的成本
- 数据一致性成本。
- 代码维护成本。
- 运维成本。
-
如何使用缓存
2、缓存一致性问题解决方案(缓存更新策略)
(1)作用
缓存更新策略是缓存系统中的重要组成部分,用于确定何时以及如何更新缓存中的数据。
(2)三种策略
- 内存淘汰:Redis自带的内存淘汰机制。
- 过期淘汰:利用expire命令给数据设置过期时间。
- 主动更新:主动完成数据库与缓存的同时更新。
总结:
(3)主动更新策略(数据库、缓存不一致解决方案)
-
Cache Aside Pattern
-
Read/Write Through Pattern
缓存与数据库整合为一个服务,由服务来维护一致性。调用者调用该服务,无需关心缓存一致性:- 一致性优秀。
- 实现复杂。
- 性能一般。
-
Write Behind Caching Pattern
调用者只操作缓存,由其他线程异步的将缓存数据持久化到数据库,保证最终一致性。- 一致性差。
- 性能好。
- 实现复杂。
-
缓存更新策略的最佳实践方案
3、缓存使用过程中产生的问题
(1)缓存穿透(缓存和数据库都不存在)
-
解决方案
(2)缓存击穿(热点key)
-
产生原因
-
解决方案
- 互斥锁
-
思路:给缓存重建过程加锁,确保重建过程只有一个线程执行,其他线程等待。因为锁能实现互斥性。假设线程过来,只能一个人一个人的来访问数据库,从而避免对于数据库访问压力过大,但这也会影响查询性能,因为此时会让查询性能从并行改成串行,我们可以采用tryLock方法+double check来解决这样的问题。假设现在线程1过来访问,他查询缓存没有命中,但是此时他获得到了锁资源,那么线程1就会一个人去执行逻辑,假设现在线程2过来,线程2在执行过程中,并没有获得到锁,那么线程了就可以进行休眠,休眠后再去查询缓存。
-
实现逻辑
-
优点
- 实现简单。
- 没有额外内存消耗。
- 一致性好。
-
缺点
- 保障了一致性,会存在不何用的情况。
- 等待导致性能下降。
- 有死锁的风险。
-
- 逻辑过期
-
思路
- 热点key缓存永不过期,而是设置一个逻辑过期时间,查询到数据库时通过对逻辑过期时间判断,来决定是否需要重建缓存。我们之所以会出现这个缓存击穿问题,主要原因是在于我们对key设置了过期时间,假设我们不设置过期时间,其实就不会有缓存击穿的问题,但是不设置过期时间,这样数据不就一直占用我们内存了吗,我们可以采用逻辑过期方案。我们把过期时间设置在 redis的value中,注意:这个过期时间并不会直接作用于redis,而是我们后续通过逻辑去处理。假设线程1去查询缓存,然后从value中判断出来当前的数据已经过期了,此时线程1去获得互斥锁,那么其他线程会进行阻塞,获得了锁的线程他会开启一个 线程去进行 以前的重构数据的逻辑,直到新开的线程完成这个逻辑后,才释放锁, 而线程1直接进行返回,假设现在线程3过来访问,由于线程线程2持有着锁,所以线程3无法获得锁,线程3也直接返回数据,只有等到新开的线程2把重建数据构建完后,其他线程才能走返回正确的数据。
这种方案巧妙在于,异步的构建缓存,缺点在于在构建完缓存之前,返回的都是脏数据。 - 重建过程也通过互斥锁保证单线程执行
- 重建缓存利用独立线程异步执行
- 其他线程无需等待,直接查询到旧数据即可
- 热点key缓存永不过期,而是设置一个逻辑过期时间,查询到数据库时通过对逻辑过期时间判断,来决定是否需要重建缓存。我们之所以会出现这个缓存击穿问题,主要原因是在于我们对key设置了过期时间,假设我们不设置过期时间,其实就不会有缓存击穿的问题,但是不设置过期时间,这样数据不就一直占用我们内存了吗,我们可以采用逻辑过期方案。我们把过期时间设置在 redis的value中,注意:这个过期时间并不会直接作用于redis,而是我们后续通过逻辑去处理。假设线程1去查询缓存,然后从value中判断出来当前的数据已经过期了,此时线程1去获得互斥锁,那么其他线程会进行阻塞,获得了锁的线程他会开启一个 线程去进行 以前的重构数据的逻辑,直到新开的线程完成这个逻辑后,才释放锁, 而线程1直接进行返回,假设现在线程3过来访问,由于线程线程2持有着锁,所以线程3无法获得锁,线程3也直接返回数据,只有等到新开的线程2把重建数据构建完后,其他线程才能走返回正确的数据。
-
实现逻辑
-
优点
线程无需等待,性能较好 -
缺点
保障了可用性,但会存在不一致的情况:不保证一致性、有额外内存消耗、实现复杂。
-
- 对比
- 互斥锁
(3)缓存雪崩
-
解决方案:
三、分布式锁
1、概述
在分布式系统中,单纯的Java API并不能提供分布式锁的能力,为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题。
2、分布式锁主流的实现方案
(1)基于数据库实现分布式锁
(2)基于缓存(Redis等)
(3)基于Zookeeper
(4) 每一种分布式锁解决方案各自的优缺点
- 性能:redis最高。
- 可靠性:zookeeper最高。
3、基于Redis的分布式锁实现思路
- 利用set nx ex 获取锁,并设置过期时间,保存线程标识。
- 释放锁时先判断线程标识是否与自己一致,一致则删除锁:
- 利用set nx满足互斥性。
- 利用set ex保证故障时锁依然能释放,避免死锁,提高安全性。
- 利用Redis集群保证高可用和高并发特性。
- 为保证比锁删锁是一个原子性,通过lua表达式来解决这个问题。
- 存在的问题:由于锁的过期时间的存在,有可能存在业务处理时间大于锁的过期时间,导致其他线程抢到锁。