缓存雪崩
发生
Redis主机挂了,redis全盘崩溃,比如redis中有大量数据同时过期
解决
Redis缓存集群实现高可用 -- (主从+哨兵、redis cluster)。
Ehcache本地缓存 + hystrix或者阿里sentinel限流&降级。
开启redis持久化机制aof、rdb,尽快恢复缓存集群。
缓存穿透
是什么
请求去查询一条数据,先redis后mysql发现都查询不到该条数据,但是每次都会打到数据库上面去,导致后台数据库压力暴增,这种现象我们称为缓存穿透,这个redis变成一个摆设。
简单来说就是本来无一物,既不在redis缓存中,也不在数据库中。
危害
第一次来查询后,一般我们有会写redis机制
第二次来查的时候redis就有了,偶尔出现穿透现象一般情况无关紧要。
解决
方案1 空对象缓存或者缺省值
一般情况下是没有问题的,一旦发生缓存穿透,我们就可以针对查询的数据,在redis中缓存一个空值或事和业务层协商确定的缺省值(例如,库存的缺省值可以设为0)。紧接着,应用发送的后续请求再进行查询时,就可以直接从redis中读取空值或缺省值,返回给业务应用了,避免了把大量请求发送给数据库处理,保持了数据库的正常运行。
但是,存在黑客或者恶意攻击的情况,
黑客会对你的系统进行攻击,拿一个不存在的ID去查询数据,会产生大量的请求到数据库去查询。可能导致你的数据库由于压力太大而宕掉。
ID相同打你的系统:第一次打到mysql,空对象缓存后第二次就返回nulll了,避免mysql被攻击,不用再到数据库去走一圈了。
ID不同打你的系统:由于存在空对象和缓存会写(看自己业务不限死),redis中的无关紧要的key也会越来越多(记得设置过期时间)。
方案2 :Google布隆过滤器guava解决缓存穿透
Guava中布隆过滤器的实现算是比较权威的,所以实际项目中我们不需要手动实现一个布隆过滤器。
Guava`s bloomFilter源码剖析
方案3:redis布隆过滤器解决缓存穿透
Guava缺点说明:
guava提供的布隆过滤器的实现还是很不错的(想要详细了解的可以看下他的源码实现),但是他有一个重大的缺陷就是只能单机使用,而现在互联网一般都是分布式的场景。
为了解决这个问题,我们就需要用到redis中的布隆过滤器。
案例:白名单过滤器
白名单架构说明
误判问题,但是概率小可以接受,不能从布隆过滤器删除
全部合法的key都需要放入过滤器+redis里面,不然数据就是返回null
代码:
public class RedissonBloomFilterDemo2 {
public static final int _1W = 10000;
//布隆过滤器里预计要插入多少数据
public static int size = 100 * _1W;
//误判率,它越小误判的个数也就越少
public static double fpp = 0.03;
static RedissonClient redissonClient = null;
static RBloomFilter rBloomFilter = null;
static {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.111.147:6379").setDatabase(0);
//构造redisson
redissonClient = Redisson.create(config);
//通过redisson构造rBloomFilter
rBloomFilter = redissonClient.getBloomFilter("phoneListBloomFilter", new StringCodec());
rBloomFilter.tryInit(size, fpp);
// 1测试 布隆过滤器有+redis有
rBloomFilter.add("10086");
redissonClient.getBucket("10086", new StringCodec()).set("chinamobile10086");
// 2测试 布隆过滤器有+redis无
//rBloomFilter.add("10087");
//3 测试 ,都没有
}
public static void main(String[] args) {
String phoneListById = getPhoneListById("10087");
System.out.println("------查询出来的结果: " + phoneListById);
//暂停几秒钟线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
redissonClient.shutdown();
}
private static String getPhoneListById(String IDNumber) {
String result = null;
if (IDNumber == null) {
return null;
}
//1 先去布隆过滤器里面查询
if (rBloomFilter.contains(IDNumber)) {
//2 布隆过滤器里有,再去redis里面查询
RBucket rBucket = redissonClient.getBucket(IDNumber, new StringCodec());
result = rBucket.get();
if (result != null) {
return "i come from redis: " + result;
} else {
result = getPhoneListByMySQL(IDNumber);
if (result == null) {
return null;
}
// 重新将数据更新回redis
redissonClient.getBucket(IDNumber, new StringCodec()).set(result);
}
return "i come from mysql: " + result;
}
return result;
}
private static String getPhoneListByMySQL(String IDNumber) {
return "chinamobile" + IDNumber;
}
}
总结:
黑名单使用
缓存击穿
是什么
大量的请求同时查询一个 key 时,此时这个key正好失效了,就会导致大量的请求都打到数据库上面去,简单说就是热点key突然失效了,暴打mysql。
危害
会造成某一时刻数据库请求量过大,压力剧增。
解决
方案1:对于访问频繁的热点key,干脆就不设置过期时间
方案2:互斥更新、随机避退、差异失效时间
方案3:互斥独占锁防止击穿
多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。
其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。